SVM Series 04: TransactionBatchProcessor

What is TransactionBatchProcessor?
The TransactionBatchProcessor
handles validation and execution of sanitized
batched transactions within the SVM acting as the central interface for the whole transaction execution pipeline.
// TransactionBatchProcessor struct as declared in SVM code
pub struct TransactionBatchProcessor<FG: solana_program_runtime::loaded_programs::ForkGraph> {
/// Bank slot (i.e. block)
slot: Slot,
/// Bank epoch
epoch: Epoch,
/// SysvarCache is a collection of system variables that are
/// accessible from on chain programs. It is passed to SVM from
/// client code (e.g. Bank) and forwarded to process_message.
sysvar_cache: RwLock<SysvarCache>,
/// Programs required for transaction batch processing
pub program_cache: Arc<RwLock<ProgramCache<FG>>>,
/// Builtin program ids
pub builtin_program_ids: RwLock<HashSet<Pubkey>>,
execution_cost: SVMTransactionExecutionCost,
}
Fields of TransactionBatchProcessor struct
slot
- a fixed timeframe when a validator can produce a block (can be as low as 150ms for alpenglow consensus). It’s Rust data type is au64
.epoch
- the timeframe when a leader schedule is valid divided into slots. The leader schedule is the mapping of validator public keys to each slot within the given epoch. It’s Rust data type is also au64
.sysvar_cache
- defined by the Rust typeSysvarCache
, it contains system variables like theClock
(network time),Epoch Schedule
,Epoch Rewards
,Rent
,Stake History
andLast Network Restart Slot
.program_cache
- defined by the Rust typeProgramCache
, it contains loaded, compiled and verified programs required for transaction batch processing, optimizing shared data across validator forks. It tracks program information such as usage stats, verification status, and handles deployments, evictions, and tombstones for unloadable programs. Executable programs that are frequently used are kept in memory to improve execution speed through cooperative batch loading and eager preloading. The program cache isn’t saved to disk, so it must be rebuilt after each SVM restart.builtin_program_ids
- It contains builtin program IDs likeBPF Loader
that own and execute smart contract programs and program configs like the supported SBF versions (all SBF versions are enabled by default).execution_cost
- configures the CUs used by various operations like how much computing a SHA256 hash costs, cost of logging (msg!()
), CPI, verifying Ed25519 signatures, CUs used for additional heap allocations above the default and more.
Processing batched sanitized transactions
The method on TransactionBatchProcessor
for processing sanitized transaction batches is TransactionBatchProcessor:: load_and_execute_sanitized_transactions
. It acts as the entry point to the SVM and takes the following parameters:
callbacks
: This parameter allows the transaction processor to load information about accounts required for transaction execution. It requires the value supplied to this argument to implement theTransactionProcessingCallback
trait which is a supertrait ofInvokeContextCallback
trait (the callback used bysolana_program_runtime::invoke_context::InvokeContext
in SVM to manage the main pipeline from runtime to program execution). By defining this trait developers can maintain full control of their application while ensuring integration with SVM according to the specification. This trait has three methods:get_account_shared_data()
which takes&self
, a public key (the address to fetch information for) as parameters and returns an optionalAccountSharedData
struct which is the in-memory representation of an Account. AnOption::None
can be returned if the account does not exist.account_matches_owner
which takes&self
, a public key and a slice of public keys (&[Pubkey]
) representing potential owners of an account. The goal of this method is to get the index of the owner of thepublic key
within the slice of owners&[Pubkey]
, therefore, it returns anOption
of typeusize
. AnOption::None
can be returned if theaccount
public key does not exist in theBank
or if the lamports balance is zero.add_builtin_account
which takes a&self
,name
of type&str
and aprogram_id
public key. This method is used to add a program to the available programs in the execution pipeline, for example adding thenative loader
to the bank, that would load a smart contract program into the execution pipeline.
sanitized_txs
: This parameter contains a slice of sanitized Solana transactions (&[impl SVMTransaction]
). A sanitized Solana transaction can be any Rust type as long as it implements theSVMTransaction
trait which is a supertrait ofSVMMessage
. These traits allow any type implementing them to provide convenience methods that can fetch information such as the fee payer, the first signature, all the signatures in a transaction, number of transaction signatures, the recent blockhash of the transaction, the number of write locks, the account keys and more. An example of a type implementing this trait isSanitizedTransaction
.check_results
- this parameter contains a list of the results of checking the validity of a transaction. Some checks performed that produce these results include the validity of a blockhash or nonce, whether an account matches it’s owner and whether the number of sanitized transactions equals the number of transactions that underwent these checks.environment
- This parameter is the runtime environment for transaction batch processing instantiated byTransactionProcessingEnvironment
struct. It configures the blockhash to use for the current transaction batch, determine rent due for the account and collect rent, the runtime features to use, lamports per signature for nonce accounts and the total stake for the current epoch.config
- this parameter configures customization of transaction processing behavior usingTransactionProcessingConfig
struct. Custom configurations include account overrides, log message byte size limits, configuring of the recording capabilities for transaction execution, whether to limit the number of programs loaded for the transaction batch and whether or not to check a program’s modification slot when replenishing a program cache instance.
The load_and_execute_sanitized_transactions
returns a LoadAndExecuteSanitizedTransactionsOutput
struct which contains the fields:
error_metrics
- the error metrics for the processed transactionsexecute_timings
- timings for transaction batch executionprocessing_results
- a vector containing type alias TransactionProcessingResult indicating whether a transaction executed successfully or the transaction execution failed and needs to be rolled backbalance_collector
- aOption<
BalanceCollector
>
containing balances accumulated forTransactionStatusSender
(a channel within the Solana ledger) when transaction balance recording is enabled.
ForkGraph Trait
The struct TransactionBatchProcessor
contains a generic FG
which requires the passed type to implement the ForkGraph
trait. The trait maps the block’s relationship between two slots returning the BlockRelation enum.
// Relationship between two fork IDs
pub enum BlockRelation {
// The slot is on the same fork and is an ancestor of the other slot
Ancestor,
// The two slots are equal and are on the same fork
Equal,
// The slot is on the same fork and is a descendant of the other slot
Descendant,
// The slots are on two different forks and may have had a common ancestor at some point
Unrelated,
// Either one or both of the slots are either older than the latest root, or are in future
Unknown,
}
The ForkGraph
trait requires the relationship(&self, a: Slot, b: Slot)
method to be implemented for the generic type. This method requires the two slots arguments a: Slot
and b: Slot
to be compared in a manner that returns the relationship (BlockRelation
enum) between them. The Slot
is just a type alias for a Rust u64
representing an entry that estimates wallclock duration (a tick
) . An example of implementing this trait:
# Add `solana-clock` dependency used in SVM ecosystem, it contains the `Slot` type
cargo add solana-clock
pub struct SlotRelationships;
impl ForkGraph for SlotRelationships {
fn relationship(&self, slot_a: Slot, slot_b: Slot) -> BlockRelation {
// Since a `Slot` is just a Rust `u64`, and a `u64` implement
// Rust `PartialCmp` and `Cmp`, we can use `.cmp()` method
// on `slot_a` to compare against `slot_b` and derive a relationship
// checking if `slot_a` is less than, equal to or greater than `slot_b`
match slot_a.cmp(&slot_b) {
std::cmp::Ordering::Less => BlockRelation::Ancestor,
std::cmp::Ordering::Equal => BlockRelation::Equal,
std::cmp::Ordering::Greater => BlockRelation::Descendant,
}
}
}
The TransactionBatchProcessor
has some more methods to configure of get information about the transaction execution pipeline that you can explore.
< Previous Article: SVM Series 03: Compute Budget & Syscalls
Resources
Understanding Slots, Blocks, and Epochs on Solana
https://www.helius.dev/blog/solana-slots-blocks-and-epochsSolana SVM TransactionBatchProcessor module docs
https://docs.rs/solana-svm/latest/solana_svm/transaction_processor/struct.TransactionBatchProcessor.html
Subscribe to my newsletter
Read articles from 448-OG directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

448-OG
448-OG
<[Open Source, Rust, Decentralized Infrastructure]>::earth()