Skip to content

Commit 0ef24b5

Browse files
committed
feat: add transaction context crate
1 parent 0905923 commit 0ef24b5

10 files changed

Lines changed: 2537 additions & 3 deletions

File tree

‎Cargo.toml‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["solana/account"]
2+
members = ["solana/account", "solana/transaction-context"]
33
resolver = "3"
44

55
[workspace.package]
@@ -15,6 +15,7 @@ version = "0.1.0"
1515
magic-root-interface = { path = "programs/magic-root-interface" }
1616
magic-root-program = { path = "programs/magic-root-program" }
1717
solana-account = { path = "solana/account" }
18+
solana-transaction-context = { path = "solana/transaction-context" }
1819

1920
ahash = "0.8.12"
2021
arc-swap = "1.9.1"
@@ -34,18 +35,29 @@ tracing-subscriber = { version = "0.3.23", features = ["env-filter", "fmt"] }
3435
wincode = "0.5.1"
3536
zstd = { version = "0.13.3", default-features = false }
3637

37-
agave-feature-set = { version = "3.1.14", features = ["agave-unstable-api"] }
38-
agave-transaction-view = { version = "3.1.13", features = ["agave-unstable-api"] }
38+
agave-feature-set = "4.1.1"
39+
agave-precompiles = "4.1.1"
40+
agave-syscalls = { package = "solana-syscalls", version = "4.1.1", default-features = false }
41+
agave-transaction-view = "4.1.1"
3942
solana-account-info = "3.1.1"
4043
solana-clock = "3.1.0"
4144
solana-compute-budget-instruction = "4.1.1"
4245
solana-cpi = "3.1.0"
4346
solana-hash = "4.3.0"
47+
solana-instruction = "3.4.0"
4448
solana-instruction-error = "2.3.0"
49+
solana-instructions-sysvar = "4.0.0"
50+
solana-program-entrypoint = "3.1.1"
4551
solana-program-error = "3.0.1"
4652
solana-pubkey = "4.2.0"
53+
solana-rent = "4.0.0-rc.1"
54+
solana-sbpf = "0.13.1"
4755
solana-sdk-ids = "3.1.0"
56+
solana-sha256-hasher = "3.1.0"
57+
solana-short-vec = "3.2.1"
58+
solana-signature = "3.4.0"
4859
solana-sysvar = "4.0.0"
60+
solana-system-interface = "3.2.0"
4961
solana-transaction-error = "3.2.0"
5062

5163

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[package]
2+
name = "solana-transaction-context"
3+
4+
authors = { workspace = true }
5+
description = "Solana data shared between program runtime and built-in programs as well as SBF programs."
6+
documentation = "https://docs.rs/solana-transaction-context"
7+
edition = { workspace = true }
8+
homepage = { workspace = true }
9+
license = { workspace = true }
10+
repository = { workspace = true }
11+
version = { workspace = true }
12+
13+
[package.metadata.docs.rs]
14+
all-features = true
15+
rustdoc-args = ["--cfg=docsrs"]
16+
targets = ["x86_64-unknown-linux-gnu"]
17+
18+
[features]
19+
# No-op stub retained only so external (patched-in) crates that reference
20+
# `solana-transaction-context/agave-unstable-api` still resolve; the lib is no
21+
# longer gated on it.
22+
agave-unstable-api = []
23+
bincode = ["dep:bincode", "serde", "solana-account/bincode"]
24+
dev-context-only-utils = ["bincode", "dep:qualifier_attr"]
25+
serde = ["serde/derive", "solana-pubkey/serde"]
26+
27+
[dependencies]
28+
solana-account = { workspace = true }
29+
solana-instruction = { workspace = true, features = ["std"] }
30+
solana-instructions-sysvar = { workspace = true }
31+
solana-pubkey = { workspace = true }
32+
33+
[target.'cfg(not(any(target_arch = "sbf", target_arch = "bpf")))'.dependencies]
34+
bincode = { workspace = true, optional = true }
35+
qualifier_attr = { workspace = true, optional = true }
36+
serde = { workspace = true, optional = true }
37+
solana-rent = { workspace = true }
38+
solana-sbpf = { workspace = true }
39+
solana-sdk-ids = { workspace = true }
40+
solana-signature = { workspace = true, optional = true }
41+
42+
[dev-dependencies]
43+
solana-account-info = { workspace = true }
44+
solana-program-entrypoint = { workspace = true }
45+
solana-system-interface = { workspace = true }
46+
solana-transaction-context = { path = ".", features = ["dev-context-only-utils"] }
47+
static_assertions = "1.1.0"
48+
49+
[lints.rust]
50+
unexpected_cfgs = "allow"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# `solana-transaction-context`
2+
3+
This Agave fork defines the account and instruction state used by one executing
4+
transaction. Workspace `[patch.crates-io]` entries force the dependency graph to
5+
use this copy.
6+
7+
`TransactionAccounts` stores account cells behind runtime borrow counters.
8+
`AccountRef` and `AccountRefMut` enforce those counters while VM access handlers
9+
can resize and remap an account's directly mapped data. The context also tracks
10+
touched accounts, resize and lamport deltas, return data, instruction state, and
11+
execution limits.
12+
13+
The direct-mapping and access-violation contracts are documented in
14+
[`../README.md`](../README.md). All account references must be released before a
15+
`TransactionContext` is deconstructed.
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
use {
2+
crate::{
3+
IndexOfAccount,
4+
instruction_accounts::{BorrowedInstructionAccount, InstructionAccount},
5+
transaction::TransactionContext,
6+
vm_addresses::{
7+
GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS, GUEST_INSTRUCTION_DATA_BASE_ADDRESS,
8+
GUEST_REGION_SIZE,
9+
},
10+
vm_slice::VmSlice,
11+
},
12+
solana_account::ReadableAccount,
13+
solana_instruction::error::InstructionError,
14+
solana_pubkey::Pubkey,
15+
std::collections::HashSet,
16+
};
17+
18+
/// Instruction shared between runtime and programs.
19+
#[repr(C)]
20+
#[derive(Debug)]
21+
pub struct InstructionFrame {
22+
/// Reserved field for alignment and potential future usage.
23+
pub reserved: u16,
24+
pub program_account_index_in_tx: u16,
25+
pub nesting_level: u16,
26+
/// This is the index of the parent instruction if this is a CPI and u16::MAX if this is a
27+
/// top-level instruction
28+
pub index_of_caller_instruction: u16,
29+
pub instruction_accounts: VmSlice<InstructionAccount>,
30+
pub instruction_data: VmSlice<u8>,
31+
}
32+
33+
impl Default for InstructionFrame {
34+
fn default() -> Self {
35+
InstructionFrame {
36+
nesting_level: 0,
37+
program_account_index_in_tx: 0,
38+
index_of_caller_instruction: u16::MAX,
39+
// Using u64::MAX as the default pointer value, since it shall never be accessible.
40+
instruction_accounts: VmSlice::new(0, 0),
41+
instruction_data: VmSlice::new(0, 0),
42+
reserved: 0,
43+
}
44+
}
45+
}
46+
47+
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
48+
impl InstructionFrame {
49+
pub fn configure_vm_slices(
50+
&mut self,
51+
instruction_index: u64,
52+
instruction_accounts_len: usize,
53+
instruction_data_len: u64,
54+
) {
55+
let common_offset = GUEST_REGION_SIZE.saturating_mul(instruction_index);
56+
57+
// Instruction data slice
58+
self.instruction_data = VmSlice::new(
59+
GUEST_INSTRUCTION_DATA_BASE_ADDRESS.saturating_add(common_offset),
60+
instruction_data_len,
61+
);
62+
63+
// Instruction accounts slice
64+
self.instruction_accounts = VmSlice::new(
65+
GUEST_INSTRUCTION_ACCOUNT_BASE_ADDRESS.saturating_add(common_offset),
66+
instruction_accounts_len as u64,
67+
);
68+
}
69+
}
70+
71+
/// View interface to read instructions.
72+
#[derive(Debug)]
73+
pub struct InstructionContext<'a, 'ix_data> {
74+
pub(crate) transaction_context: &'a TransactionContext<'ix_data>,
75+
// The rest of the fields are redundant shortcuts
76+
pub(crate) index_in_trace: usize,
77+
pub(crate) nesting_level: usize,
78+
pub(crate) index_of_caller_instruction: usize,
79+
pub(crate) program_account_index_in_tx: IndexOfAccount,
80+
pub(crate) instruction_accounts: &'a [InstructionAccount],
81+
pub(crate) dedup_map: &'a [u16],
82+
pub(crate) instruction_data: &'ix_data [u8],
83+
}
84+
85+
impl<'a> InstructionContext<'a, '_> {
86+
/// How many Instructions were on the trace before this one was pushed
87+
pub fn get_index_in_trace(&self) -> usize {
88+
self.index_in_trace
89+
}
90+
91+
/// Returns the index of the instruction that called into this one.
92+
pub fn get_index_of_caller(&self) -> usize {
93+
self.index_of_caller_instruction
94+
}
95+
96+
/// How many Instructions were on the stack after this one was pushed
97+
///
98+
/// That is the number of nested parent Instructions plus one (itself).
99+
pub fn get_stack_height(&self) -> usize {
100+
self.nesting_level.saturating_add(1)
101+
}
102+
103+
/// Number of accounts in this Instruction (without program accounts)
104+
pub fn get_number_of_instruction_accounts(&self) -> IndexOfAccount {
105+
self.instruction_accounts.len() as IndexOfAccount
106+
}
107+
108+
/// Assert that enough accounts were supplied to this Instruction
109+
pub fn check_number_of_instruction_accounts(
110+
&self,
111+
expected_at_least: IndexOfAccount,
112+
) -> Result<(), InstructionError> {
113+
if self.get_number_of_instruction_accounts() < expected_at_least {
114+
Err(InstructionError::MissingAccount)
115+
} else {
116+
Ok(())
117+
}
118+
}
119+
120+
/// Data parameter for the programs `process_instruction` handler
121+
pub fn get_instruction_data(&self) -> &[u8] {
122+
self.instruction_data
123+
}
124+
125+
/// Translates the given instruction wide program_account_index into a transaction wide index
126+
pub fn get_index_of_program_account_in_transaction(
127+
&self,
128+
) -> Result<IndexOfAccount, InstructionError> {
129+
if self.program_account_index_in_tx == u16::MAX {
130+
Err(InstructionError::MissingAccount)
131+
} else {
132+
Ok(self.program_account_index_in_tx)
133+
}
134+
}
135+
136+
/// Translates the given instruction wide instruction_account_index into a transaction wide index
137+
pub fn get_index_of_instruction_account_in_transaction(
138+
&self,
139+
instruction_account_index: IndexOfAccount,
140+
) -> Result<IndexOfAccount, InstructionError> {
141+
Ok(self
142+
.instruction_accounts
143+
.get(instruction_account_index as usize)
144+
.ok_or(InstructionError::MissingAccount)?
145+
.index_in_transaction as IndexOfAccount)
146+
}
147+
148+
/// Get the index of account in instruction from the index in transaction
149+
pub fn get_index_of_account_in_instruction(
150+
&self,
151+
index_in_transaction: IndexOfAccount,
152+
) -> Result<IndexOfAccount, InstructionError> {
153+
self.dedup_map
154+
.get(index_in_transaction as usize)
155+
.and_then(|idx| {
156+
if *idx as usize >= self.instruction_accounts.len() {
157+
None
158+
} else {
159+
Some(*idx as IndexOfAccount)
160+
}
161+
})
162+
.ok_or(InstructionError::MissingAccount)
163+
}
164+
165+
/// Returns `Some(instruction_account_index)` if this is a duplicate
166+
/// and `None` if it is the first account with this key
167+
pub fn is_instruction_account_duplicate(
168+
&self,
169+
instruction_account_index: IndexOfAccount,
170+
) -> Result<Option<IndexOfAccount>, InstructionError> {
171+
let index_in_transaction =
172+
self.get_index_of_instruction_account_in_transaction(instruction_account_index)?;
173+
let first_instruction_account_index =
174+
self.get_index_of_account_in_instruction(index_in_transaction)?;
175+
176+
Ok(
177+
if first_instruction_account_index == instruction_account_index {
178+
None
179+
} else {
180+
Some(first_instruction_account_index)
181+
},
182+
)
183+
}
184+
185+
/// Gets the key of the last program account of this Instruction
186+
pub fn get_program_key(&self) -> Result<&'a Pubkey, InstructionError> {
187+
self.get_index_of_program_account_in_transaction()
188+
.and_then(|index_in_transaction| {
189+
self.transaction_context.get_key_of_account_at_index(index_in_transaction)
190+
})
191+
}
192+
193+
/// Get the owner of the program account of this instruction
194+
pub fn get_program_owner(&self) -> Result<Pubkey, InstructionError> {
195+
self.get_index_of_program_account_in_transaction()
196+
.and_then(|index_in_transaction| {
197+
self.transaction_context.accounts.try_borrow(index_in_transaction)
198+
})
199+
.map(|acc| *acc.owner())
200+
}
201+
202+
/// Gets an instruction account of this Instruction
203+
pub fn try_borrow_instruction_account(
204+
&self,
205+
index_in_instruction: IndexOfAccount,
206+
) -> Result<BorrowedInstructionAccount<'_, '_>, InstructionError> {
207+
let instruction_account = *self
208+
.instruction_accounts
209+
.get(index_in_instruction as usize)
210+
.ok_or(InstructionError::MissingAccount)?;
211+
212+
let account = self
213+
.transaction_context
214+
.accounts
215+
.try_borrow_mut(instruction_account.index_in_transaction)?;
216+
217+
Ok(BorrowedInstructionAccount {
218+
transaction_context: self.transaction_context,
219+
instruction_account,
220+
account,
221+
index_in_transaction_of_instruction_program: self.program_account_index_in_tx,
222+
})
223+
}
224+
225+
/// Returns whether an instruction account is a signer
226+
pub fn is_instruction_account_signer(
227+
&self,
228+
instruction_account_index: IndexOfAccount,
229+
) -> Result<bool, InstructionError> {
230+
Ok(self
231+
.instruction_accounts
232+
.get(instruction_account_index as usize)
233+
.ok_or(InstructionError::MissingAccount)?
234+
.is_signer())
235+
}
236+
237+
/// Returns whether an instruction account is writable
238+
pub fn is_instruction_account_writable(
239+
&self,
240+
instruction_account_index: IndexOfAccount,
241+
) -> Result<bool, InstructionError> {
242+
Ok(self
243+
.instruction_accounts
244+
.get(instruction_account_index as usize)
245+
.ok_or(InstructionError::MissingAccount)?
246+
.is_writable())
247+
}
248+
249+
/// Calculates the set of all keys of signer instruction accounts in this Instruction
250+
pub fn get_signers(&self) -> Result<HashSet<Pubkey>, InstructionError> {
251+
let mut result = HashSet::new();
252+
for instruction_account in self.instruction_accounts.iter() {
253+
if instruction_account.is_signer() {
254+
result.insert(
255+
*self
256+
.transaction_context
257+
.get_key_of_account_at_index(instruction_account.index_in_transaction)?,
258+
);
259+
}
260+
}
261+
Ok(result)
262+
}
263+
264+
pub fn instruction_accounts(&self) -> &[InstructionAccount] {
265+
self.instruction_accounts
266+
}
267+
268+
pub fn get_key_of_instruction_account(
269+
&self,
270+
index_in_instruction: IndexOfAccount,
271+
) -> Result<&'a Pubkey, InstructionError> {
272+
self.get_index_of_instruction_account_in_transaction(index_in_instruction)
273+
.and_then(|idx| self.transaction_context.get_key_of_account_at_index(idx))
274+
}
275+
}

0 commit comments

Comments
 (0)