Transactions
Methods for handling transactions
send_transaction
pub fn send_transaction(&mut self, tx: impl Into<VersionedTransaction>) -> Result<TransactionMetadata, FailedTransactionMetadata>
Execute transaction and modify state.
let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&payer],
svm.latest_blockhash(),
);
match svm.send_transaction(tx) {
Ok(meta) => {
println!("Success! Signature: {}", meta.signature);
println!("Compute units: {}", meta.compute_units_consumed);
}
Err(e) => {
println!("Failed: {:?}", e.err);
println!("Logs: {:?}", e.meta.logs);
}
}
simulate_transaction
pub fn simulate_transaction(&self, tx: impl Into<VersionedTransaction>) -> Result<SimulatedTransactionInfo, FailedTransactionMetadata>
Simulate transaction without modifying state.
Returns SimulatedTransactionInfo
which contains:
meta
: Transaction metadata (logs, compute units, signature, etc.)post_accounts
: Account states after simulation
// State before simulation
let balance_before = svm.get_balance(&pubkey).unwrap_or(0);
// Simulate transaction
let result = svm.simulate_transaction(tx.clone());
// State unchanged
assert_eq!(svm.get_balance(&pubkey).unwrap_or(0), balance_before);
// Check simulation result
if let Ok(sim_info) = result {
println!("Would succeed");
println!("Compute units: {}", sim_info.meta.compute_units_consumed);
// Can inspect post_accounts to see what would change
for (pubkey, account) in sim_info.post_accounts {
println!("Account {} would have {} lamports", pubkey, account.lamports());
}
// Safe to send
svm.send_transaction(tx).unwrap();
}
When to Use Simulation
Use simulate_transaction()
to:
- Test transaction validity without modifying state
- Check compute unit consumption
- Verify transaction logs without commitment
- Test error handling paths
// Test if transaction would succeed
let result = svm.simulate_transaction(tx.clone());
match result {
Ok(sim_info) => {
println!("Would succeed");
println!("Compute units: {}", sim_info.meta.compute_units_consumed);
// Now execute for real
svm.send_transaction(tx).unwrap();
}
Err(e) => {
println!("Would fail: {:?}", e.err);
// Don't send transaction
}
}
Testing Error Cases
// Simulate expected failure
let result = svm.simulate_transaction(invalid_tx);
assert!(result.is_err());
match result.unwrap_err().err {
TransactionError::InsufficientFundsForFee => {
println!("Expected error occurred");
}
_ => panic!("Unexpected error"),
}
State Remains Unchanged
let balance_before = svm.get_balance(&pubkey).unwrap_or(0);
// Simulate multiple times
for _ in 0..10 {
svm.simulate_transaction(tx.clone()).unwrap();
}
// Balance unchanged
assert_eq!(svm.get_balance(&pubkey).unwrap_or(0), balance_before);
Transaction Metadata
send_transaction()
returns TransactionMetadata
directly, while simulate_transaction()
returns SimulatedTransactionInfo
which contains meta: TransactionMetadata
:
pub struct TransactionMetadata {
pub signature: Signature,
pub logs: Vec<String>,
pub compute_units_consumed: u64,
pub return_data: TransactionReturnData,
pub inner_instructions: InnerInstructionsList,
}
Accessing Transaction Data
let result = svm.send_transaction(tx).unwrap();
// Signature
println!("Signature: {}", result.signature);
// Logs
for log in &result.logs {
println!("{}", log);
}
// Compute units
println!("CU consumed: {}", result.compute_units_consumed);
// Return data
if let Some(data) = result.return_data.data {
println!("Program returned: {:?}", data);
}
// Inner instructions (CPIs)
for inner in &result.inner_instructions {
println!("CPI: {:?}", inner);
}
get_transaction
pub fn get_transaction(&self, signature: &Signature) -> Option<Result<TransactionMetadata, FailedTransactionMetadata>>
Query transaction from history. Requires with_transaction_history()
to be set.
let mut svm = LiteSVM::default()
.with_transaction_history(100);
let result = svm.send_transaction(tx).unwrap();
let signature = result.signature;
// Query later
if let Some(tx_result) = svm.get_transaction(&signature) {
match tx_result {
Ok(meta) => println!("Found: {:?}", meta),
Err(e) => println!("Failed: {:?}", e.err),
}
}
latest_blockhash
pub fn latest_blockhash(&self) -> Hash
Get current blockhash for transactions.
let blockhash = svm.latest_blockhash();
let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&payer],
blockhash,
);
expire_blockhash
pub fn expire_blockhash(&mut self)
Force current blockhash to expire.
let old_hash = svm.latest_blockhash();
svm.expire_blockhash();
let new_hash = svm.latest_blockhash();
assert_ne!(old_hash, new_hash);
// Transactions with old_hash will now fail
Common Patterns
Validate Before Executing
// Simulate first to check validity
if svm.simulate_transaction(tx.clone()).is_ok() {
// Execute if simulation passes
svm.send_transaction(tx).unwrap();
} else {
println!("Transaction would fail, skipping execution");
}
Track Compute Units
let result = svm.send_transaction(tx).unwrap();
// Assert within budget
assert!(
result.compute_units_consumed < 100_000,
"Transaction used too many compute units: {}",
result.compute_units_consumed
);
Check Transaction Logs
let result = svm.send_transaction(tx).unwrap();
// Check for specific log messages
assert!(result.logs.iter().any(|log|
log.contains("Transfer successful")
));