Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["solana/account", "solana/transaction-view"]
members = ["solana/account", "solana/transaction-context", "solana/transaction-view"]
resolver = "3"

[workspace.package]
Expand All @@ -15,6 +15,7 @@ version = "0.1.0"
magic-root-interface = { path = "programs/magic-root-interface" }
magic-root-program = { path = "programs/magic-root-program" }
solana-account = { path = "solana/account" }
solana-transaction-context = { path = "solana/transaction-context" }

ahash = "0.8.12"
arc-swap = "1.9.1"
Expand All @@ -36,21 +37,27 @@ tracing-subscriber = { version = "0.3.23", features = ["env-filter", "fmt"] }
wincode = "0.5.1"
zstd = { version = "0.13.3", default-features = false }

agave-feature-set = { version = "3.1.14", features = ["agave-unstable-api"] }
agave-transaction-view = { version = "4.1.1", features = ["agave-unstable-api"] }
agave-feature-set = "4.1.1"
agave-precompiles = "4.1.1"
agave-syscalls = { package = "solana-syscalls", version = "=4.1.1", default-features = false }
agave-transaction-view = "4.1.1"
solana-account-info = "3.1.1"
solana-clock = "3.1.0"
solana-compute-budget-instruction = "=4.1.1"
solana-cpi = "3.1.0"
solana-hash = "4.3.0"
solana-instruction = "=3.4.0"
solana-instruction-error = "=2.4.0"
solana-instructions-sysvar = "4.0.0"
solana-keypair = "3.1.2"
solana-message = "4.1.1"
solana-packet = "=4.2.0"
solana-program-entrypoint = "3.1.1"
solana-program-runtime = { version = "=4.1.1", features = ["agave-unstable-api"] }
solana-program-error = "3.0.1"
solana-pubkey = "=4.2.0"
solana-rent = "4.0.0-rc.1"
solana-sbpf = "0.13.1"
solana-sdk-ids = "3.1.0"
solana-short-vec = "=3.2.2"
solana-signature = "=3.4.1"
Expand All @@ -59,7 +66,6 @@ solana-svm-transaction = "4.1.1"
solana-system-interface = { version = "=3.2.0", features = ["alloc", "bincode", "serde", "wincode"] }
solana-sysvar = "4.0.0"
solana-transaction = "4.1.1"
solana-transaction-context = "4.1.1"
solana-transaction-error = "=3.3.1"

[patch.crates-io]
Expand Down
29 changes: 14 additions & 15 deletions solana/transaction-context/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
[package]
name = "solana-transaction-context"

authors = { workspace = true }
description = "Solana data shared between program runtime and built-in programs as well as SBF programs."
documentation = "https://docs.rs/solana-transaction-context"
version = { workspace = true }
authors = { workspace = true }
repository = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
edition = "2024"
repository = { workspace = true }
version = { workspace = true }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
all-features = true
rustdoc-args = ["--cfg=docsrs"]
targets = ["x86_64-unknown-linux-gnu"]

[features]
# No-op stub retained only so external (patched-in) crates that reference
# `solana-transaction-context/agave-unstable-api` still resolve; the lib is no
# longer gated on it.
agave-unstable-api = []
bincode = ["dep:bincode", "serde", "solana-account/bincode"]
dev-context-only-utils = ["bincode", "solana-account/dev-context-only-utils", "dep:qualifier_attr"]
dev-context-only-utils = ["bincode"]
serde = ["serde/derive", "solana-pubkey/serde"]
wincode = ["dep:wincode", "solana-pubkey/wincode"]

[dependencies]
solana-account = { workspace = true }
solana-instruction = { workspace = true, features = ["std"] }
solana-instructions-sysvar = { workspace = true }
solana-pubkey = { workspace = true }
wincode = { workspace = true, optional = true }

[target.'cfg(not(any(target_arch = "sbf", target_arch = "bpf")))'.dependencies]
bincode = { workspace = true, optional = true }
Expand All @@ -39,11 +41,8 @@ solana-sdk-ids = { workspace = true }
solana-account-info = { workspace = true }
solana-program-entrypoint = { workspace = true }
solana-system-interface = { workspace = true }
solana-transaction-context = { path = ".", features = [
"agave-unstable-api",
"dev-context-only-utils",
] }
static_assertions = { workspace = true }
solana-transaction-context = { path = ".", features = ["dev-context-only-utils"] }
static_assertions = "1.1.0"

[lints]
workspace = true
[lints.rust]
unexpected_cfgs = "allow"
15 changes: 15 additions & 0 deletions solana/transaction-context/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# `solana-transaction-context`

This Agave fork defines the account and instruction state used by one executing
transaction. Workspace `[patch.crates-io]` entries force the dependency graph to
use this copy.

`TransactionAccounts` stores account cells behind runtime borrow counters.
`AccountRef` and `AccountRefMut` enforce those counters while VM access handlers
can resize and remap an account's directly mapped data. The context also tracks
touched accounts, resize and lamport deltas, return data, instruction state, and
execution limits.

The direct-mapping and access-violation contracts are documented in
[`../README.md`](../README.md). All account references must be released before a
`TransactionContext` is deconstructed.
7 changes: 2 additions & 5 deletions solana/transaction-context/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,15 @@ impl<'a> InstructionContext<'a, '_> {
pub fn get_program_key(&self) -> Result<&'a Pubkey, InstructionError> {
self.get_index_of_program_account_in_transaction()
.and_then(|index_in_transaction| {
self.transaction_context
.get_key_of_account_at_index(index_in_transaction)
self.transaction_context.get_key_of_account_at_index(index_in_transaction)
})
}

/// Get the owner of the program account of this instruction
pub fn get_program_owner(&self) -> Result<Pubkey, InstructionError> {
self.get_index_of_program_account_in_transaction()
.and_then(|index_in_transaction| {
self.transaction_context
.accounts
.try_borrow(index_in_transaction)
self.transaction_context.accounts.try_borrow(index_in_transaction)
})
.map(|acc| *acc.owner())
}
Expand Down
54 changes: 24 additions & 30 deletions solana/transaction-context/src/instruction_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use {
IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION, transaction::TransactionContext,
transaction_accounts::AccountRefMut,
},
solana_account::{ReadableAccount, WritableAccount},
solana_account::{CoWAccount, ReadableAccount, WritableAccount},
solana_instruction::error::InstructionError,
solana_pubkey::Pubkey,
};
Expand Down Expand Up @@ -133,9 +133,7 @@ impl BorrowedInstructionAccount<'_, '_> {
}

let lamports_balance = (lamports as i128).saturating_sub(old_lamports as i128);
self.transaction_context
.accounts
.add_lamports_delta(lamports_balance)?;
self.transaction_context.accounts.add_lamports_delta(lamports_balance)?;

self.touch()?;
self.account.set_lamports(lamports);
Expand Down Expand Up @@ -225,26 +223,19 @@ impl BorrowedInstructionAccount<'_, '_> {
Ok(())
}

/// Returns whether the underlying AccountSharedData is shared.
/// Returns whether account data must be mapped through the CoW handler.
///
/// The data is shared if the account has been loaded from the accounts database and has never
/// been written to. Writing to an account unshares it.
///
/// During account serialization, if an account is shared it'll get mapped as CoW, else it'll
/// get mapped directly as writable.
/// Owned shared buffers and borrowed account images both need first-write
/// translation before the VM can mutate them.
pub fn is_shared(&self) -> bool {
self.account.is_shared()
self.account.is_shared() || matches!(self.account.cow(), CoWAccount::Borrowed(_))
}

fn make_data_mut(&mut self) {
// if the account is still shared, it means this is the first time we're
// about to write into it. Make the account mutable by copying it in a
// buffer with MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION capacity so that if the
// transaction reallocs, we don't have to copy the whole account data a
// second time to fullfill the realloc.
if self.account.is_shared() {
self.account
.reserve(MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION);
// Reserve the maximum per-instruction growth before mutating shared buffers or borrowed
// account images. Borrowed images with enough spare capacity can remain borrowed.
if self.is_shared() {
self.account.reserve(MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION);
}
}

Expand All @@ -270,15 +261,14 @@ impl BorrowedInstructionAccount<'_, '_> {
// Returns whether or the lamports currently in the account is sufficient for rent exemption should the
// data be resized to the given size
pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
self.transaction_context
.rent
.is_exempt(self.get_lamports(), data_length)
self.transaction_context.rent.is_exempt(self.get_lamports(), data_length)
}

/// Returns whether this account is executable (transaction wide)
#[inline]
#[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
pub fn is_executable(&self) -> bool {
#[allow(deprecated)]
self.account.executable()
}

Expand All @@ -301,7 +291,7 @@ impl BorrowedInstructionAccount<'_, '_> {
return Err(InstructionError::ExecutableModified);
}
// don't touch the account if the executable flag does not change
#[expect(deprecated)]
#[allow(deprecated)]
if self.is_executable() == is_executable {
return Ok(());
}
Expand Down Expand Up @@ -350,13 +340,17 @@ impl BorrowedInstructionAccount<'_, '_> {
/// Returns an error if the account data can not be resized to the given length
pub fn can_data_be_resized(&self, new_len: usize) -> Result<(), InstructionError> {
let old_len = self.get_data().len();
// Only the owner can change the length of the data
if new_len != old_len && !self.is_owned_by_current_program() {
return Err(InstructionError::AccountDataSizeChanged);
if new_len != old_len {
use solana_account::AccountMode;
if !self.is_owned_by_current_program() {
// Only the owner can change the length of the data
return Err(InstructionError::AccountDataSizeChanged);
} else if self.account.is(AccountMode::Ephemeral) {
// Ephemeral accounts can only be resized with special builtin instruction
return Err(InstructionError::InvalidRealloc);
}
}
self.transaction_context
.accounts
.can_data_be_resized(old_len, new_len)?;
self.transaction_context.accounts.can_data_be_resized(old_len, new_len)?;
self.can_data_be_changed()
}

Expand All @@ -379,7 +373,7 @@ fn is_zeroed(buf: &[u8]) -> bool {
const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
let mut chunks = buf.chunks_exact(ZEROS_LEN);

#[expect(clippy::indexing_slicing)]
#[allow(clippy::indexing_slicing)]
{
chunks.all(|chunk| chunk == &ZEROS[..])
&& chunks.remainder() == &ZEROS[..chunks.remainder().len()]
Expand Down
10 changes: 5 additions & 5 deletions solana/transaction-context/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(feature = "agave-unstable-api")]
//! Data shared between program runtime and built-in programs as well as SBF programs.
#![allow(clippy::disallowed_methods)]
#![deny(clippy::indexing_slicing)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]

pub mod instruction;
pub mod instruction_accounts;
Expand All @@ -17,9 +17,9 @@ pub const MAX_ACCOUNTS_PER_TRANSACTION: usize = 256;
pub const MAX_ACCOUNTS_PER_INSTRUCTION: usize = 255;
pub const MAX_INSTRUCTION_DATA_LEN: usize = 10 * 1024;
pub const MAX_ACCOUNT_DATA_LEN: u64 = 10 * 1024 * 1024;
// Note: With virtual_address_space_adjustments programs can grow accounts
// faster than they intend to, because the AccessViolationHandler might grow
// an account up to MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION at once.
// Note: Direct account-region mapping lets programs grow accounts through the
// AccessViolationHandler, which might grow an account up to
// MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION at once.
pub const MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION: i64 = MAX_ACCOUNT_DATA_LEN as i64 * 2;
pub const MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION: usize = 10 * 1_024;
// Maximum cross-program invocation and instructions per transaction
Expand Down
Loading