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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Added

- `TransactionMetadata::loaded_accounts_data_size`: the loaded accounts data size the transaction was metered at, as SIMD-0186 counts it. Code that builds `TransactionMetadata` with a struct literal needs to set it. Persistence snapshots do not store it, so restored history reports 0.

### Fixed

- Count the programdata of loader-v3 programs toward the loaded accounts data size, once per transaction, as Agave does.

## [0.17.0] - 2026-09-28

### Added
Expand Down
95 changes: 86 additions & 9 deletions crates/litesvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ much easier.
#[cfg(feature = "register-tracing")]
use crate::register_tracing::DefaultRegisterTracingCallback;
#[cfg(feature = "hashbrown")]
use hashbrown::{hash_map::Entry, HashMap};
use hashbrown::{hash_map::Entry, HashMap, HashSet};
#[cfg(feature = "persistence-internal")]
use indexmap::IndexMap;
#[cfg(feature = "precompiles")]
Expand All @@ -322,7 +322,7 @@ use solana_sysvar::recent_blockhashes::IterItem;
#[allow(deprecated)]
use solana_sysvar::{fees::Fees, recent_blockhashes::RecentBlockhashes};
#[cfg(not(feature = "hashbrown"))]
use std::collections::{hash_map::Entry, HashMap};
use std::collections::{hash_map::Entry, HashMap, HashSet};
use {
crate::{
accounts_db::{load_preverified, visible_deployment_slot, AccountsDb},
Expand Down Expand Up @@ -1265,6 +1265,7 @@ impl LiteSVM {
Option<TransactionContext<'b>>,
u64,
Option<Address>,
u32,
)
where
'a: 'b,
Expand Down Expand Up @@ -1305,10 +1306,18 @@ impl LiteSVM {
.num_lookup_tables()
.saturating_mul(ADDRESS_LOOKUP_TABLE_BASE_SIZE),
) {
return (Err(e), accumulated_consume_units, None, fee, payer_key);
return (
Err(e),
accumulated_consume_units,
None,
fee,
payer_key,
loaded_tx_data_size.size(),
);
}

let mut pre_rent_state_infos = Vec::with_capacity(account_keys.len());
let mut counted_programdata = HashSet::new();
let maybe_accounts = account_keys
.iter()
.enumerate()
Expand Down Expand Up @@ -1377,13 +1386,34 @@ impl LiteSVM {
));

loaded_tx_data_size.increase_calculated_data_size(loaded_size)?;
// A loader-v3 program also counts its programdata, once, unless the transaction
// loads that account itself:
// https://github.com/anza-xyz/agave/blob/v4.2.0/svm/src/account_loader.rs#L543-L563
if let Some(programdata_address) = programdata_address(&account).filter(|address| {
!account_keys.iter().any(|key| key == address)
&& !counted_programdata.contains(address)
}) {
if let Some(programdata) = self.accounts.get_account(&programdata_address) {
counted_programdata.insert(programdata_address);
loaded_tx_data_size.increase_calculated_data_size(
TRANSACTION_ACCOUNT_BASE_SIZE.saturating_add(programdata.data().len()),
)?;
}
}
Ok((*key, account))
})
.collect::<solana_transaction_error::TransactionResult<Vec<_>>>();
let accounts = match maybe_accounts {
Ok(accs) => accs,
Err(e) => {
return (Err(e), accumulated_consume_units, None, fee, payer_key);
return (
Err(e),
accumulated_consume_units,
None,
fee,
payer_key,
loaded_tx_data_size.size(),
);
}
};
if !validated_fee_payer {
Expand All @@ -1394,6 +1424,7 @@ impl LiteSVM {
None,
fee,
payer_key,
loaded_tx_data_size.size(),
);
}
let maybe_program_indices = tx
Expand Down Expand Up @@ -1492,9 +1523,17 @@ impl LiteSVM {
Some(context),
fee,
payer_key,
loaded_tx_data_size.size(),
)
}
Err(e) => (Err(e), accumulated_consume_units, None, fee, payer_key),
Err(e) => (
Err(e),
accumulated_consume_units,
None,
fee,
payer_key,
loaded_tx_data_size.size(),
),
}
}

Expand Down Expand Up @@ -1532,13 +1571,20 @@ impl LiteSVM {
},
fee,
payer_key,
loaded_accounts_data_size,
} = match self.check_and_process_transaction(sanitized_tx, log_collector) {
Ok(value) => value,
Err(value) => return value,
};
if let Some(ctx) = context {
let mut exec_result =
execution_result_if_context(sanitized_tx, ctx, result, compute_units_consumed, fee);
let mut exec_result = execution_result_if_context(
sanitized_tx,
ctx,
result,
compute_units_consumed,
fee,
loaded_accounts_data_size,
);

if let Some(payer) = payer_key.filter(|_| exec_result.tx_result.is_err()) {
exec_result.tx_result = self
Expand All @@ -1552,6 +1598,7 @@ impl LiteSVM {
tx_result: result,
compute_units_consumed,
fee,
loaded_accounts_data_size,
..Default::default()
}
}
Expand All @@ -1570,18 +1617,27 @@ impl LiteSVM {
context,
},
fee,
loaded_accounts_data_size,
..
} = match self.check_and_process_transaction(sanitized_tx, log_collector) {
Ok(value) => value,
Err(value) => return value,
};
if let Some(ctx) = context {
execution_result_if_context(sanitized_tx, ctx, result, compute_units_consumed, fee)
execution_result_if_context(
sanitized_tx,
ctx,
result,
compute_units_consumed,
fee,
loaded_accounts_data_size,
)
} else {
ExecutionResult {
tx_result: result,
compute_units_consumed,
fee,
loaded_accounts_data_size,
..Default::default()
}
}
Expand All @@ -1598,7 +1654,7 @@ impl LiteSVM {
self.maybe_blockhash_check(sanitized_tx)?;
let tx_config = get_transaction_config(sanitized_tx, &self.feature_set)?;
self.maybe_history_check(sanitized_tx)?;
let (result, compute_units_consumed, context, fee, payer_key) =
let (result, compute_units_consumed, context, fee, payer_key, loaded_accounts_data_size) =
self.process_transaction(sanitized_tx, tx_config, log_collector);
#[cfg(target_arch = "x86_64")]
unsafe {
Expand All @@ -1614,6 +1670,7 @@ impl LiteSVM {
},
fee,
payer_key,
loaded_accounts_data_size,
})
}

Expand Down Expand Up @@ -1677,6 +1734,7 @@ impl LiteSVM {
return_data,
included,
fee,
loaded_accounts_data_size,
} = if self.sigverify {
self.execute_transaction(vtx, log_collector.clone())
} else {
Expand All @@ -1692,6 +1750,7 @@ impl LiteSVM {
return_data,
signature,
fee,
loaded_accounts_data_size,
};

if let Err(tx_err) = tx_result {
Expand Down Expand Up @@ -1731,6 +1790,7 @@ impl LiteSVM {
inner_instructions,
return_data,
fee,
loaded_accounts_data_size,
..
} = if self.sigverify {
self.execute_transaction_readonly(tx.into(), log_collector.clone())
Expand All @@ -1747,6 +1807,7 @@ impl LiteSVM {
compute_units_consumed,
return_data,
fee,
loaded_accounts_data_size,
};

if let Err(tx_err) = tx_result {
Expand Down Expand Up @@ -2012,6 +2073,7 @@ struct CheckAndProcessTransactionSuccess<'ix_data> {
core: CheckAndProcessTransactionSuccessCore<'ix_data>,
fee: u64,
payer_key: Option<Address>,
loaded_accounts_data_size: u32,
}

fn execution_result_if_context(
Expand All @@ -2020,6 +2082,7 @@ fn execution_result_if_context(
result: Result<(), TransactionError>,
compute_units_consumed: u64,
fee: u64,
loaded_accounts_data_size: u32,
) -> ExecutionResult {
let (signature, return_data, inner_instructions, post_accounts) =
execute_tx_helper(sanitized_tx, ctx);
Expand All @@ -2032,6 +2095,20 @@ fn execution_result_if_context(
return_data,
included: true,
fee,
loaded_accounts_data_size,
}
}

/// The programdata account a loader-v3 program account points at.
fn programdata_address(account: &AccountSharedData) -> Option<Address> {
if !bpf_loader_upgradeable::check_id(account.owner()) {
return None;
}
match UpgradeableLoaderState::deserialize_from(account.data()) {
Ok(UpgradeableLoaderState::Program {
programdata_address,
}) => Some(programdata_address),
_ => None,
}
}

Expand Down
6 changes: 6 additions & 0 deletions crates/litesvm/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ pub struct TransactionMetadata {
pub compute_units_consumed: u64,
pub return_data: TransactionReturnData,
pub fee: u64,
/// The size of the accounts the transaction loaded, as SIMD-0186 counts it
/// against the transaction's loaded accounts data size limit.
#[cfg_attr(feature = "serde", serde(default))]
pub loaded_accounts_data_size: u32,
}

impl TransactionMetadata {
Expand Down Expand Up @@ -66,6 +70,7 @@ pub(crate) struct ExecutionResult {
/// Whether the transaction can be included in a block
pub(crate) included: bool,
pub(crate) fee: u64,
pub(crate) loaded_accounts_data_size: u32,
}

impl Default for ExecutionResult {
Expand All @@ -79,6 +84,7 @@ impl Default for ExecutionResult {
return_data: Default::default(),
included: false,
fee: 0,
loaded_accounts_data_size: 0,
}
}
}
5 changes: 5 additions & 0 deletions crates/litesvm/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ impl LoadedTransactionDataSize {
}
}

/// The loaded accounts data size counted so far.
pub(crate) fn size(&self) -> u32 {
self.loaded_accounts_data_size
}

/// Increases the loaded accounts data size by the given delta, and checks if it exceeds the requested limit.
pub(crate) fn increase_calculated_data_size(
&mut self,
Expand Down
Loading
Loading