use std::collections::HashMap;
use litesvm::{types::SimulatedTransactionInfo, LiteSVM};
use serde::{Deserialize, Serialize};
use solana_hash::Hash;
use solana_message::Message;
use solana_pubkey::Pubkey;
use solana_transaction::Transaction;
use crate::cu_bench::InstructionBenchmark;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstructionExecutionContext {
pub svm_context: SVMContext,
pub program_context: ProgramContext,
pub execution_stats: ExecutionStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionExecutionContext {
pub svm_context: SVMContext,
pub workflow_context: WorkflowContext,
pub execution_stats: ExecutionStats,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SVMContext {
pub current_slot: u64,
#[serde(serialize_with = "serialize_hash")]
pub latest_blockhash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgramContext {
#[serde(serialize_with = "serialize_pubkey")]
pub program_id: Pubkey,
pub program_name: String,
pub cpi_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowContext {
pub workflow_name: String,
pub involved_programs: Vec<ProgramInfo>,
pub cpi_sequence: Vec<String>,
pub total_cpi_calls: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgramInfo {
#[serde(serialize_with = "serialize_pubkey")]
pub program_id: Pubkey,
pub program_name: String,
pub instruction_count: usize, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionStats {
pub logs: Vec<String>,
pub simulated_cu: u64,
}
pub fn discover_instruction_context<T: InstructionBenchmark>(
benchmark: &T,
svm: &mut LiteSVM,
) -> InstructionExecutionContext {
let (target_ix, signer_pubkeys) = benchmark.build_instruction(svm);
svm.expire_blockhash();
let message = Message::new(&[target_ix], Some(&signer_pubkeys[0]));
let mut unsigned_tx = Transaction::new_unsigned(message);
unsigned_tx.message.recent_blockhash = svm.latest_blockhash();
let signed_tx = benchmark.sign_transaction(unsigned_tx);
let simulation = svm.simulate_transaction(signed_tx.clone()).unwrap();
let address_book = benchmark.address_book();
InstructionExecutionContext {
svm_context: SVMContext {
current_slot: svm.get_sysvar::<solana_clock::Clock>().slot,
latest_blockhash: svm.latest_blockhash(),
},
program_context: extract_program_context(&signed_tx, &simulation, &address_book),
execution_stats: extract_execution_stats(&simulation),
}
}
fn extract_program_context(
transaction: &Transaction,
simulation: &SimulatedTransactionInfo,
address_book: &HashMap<Pubkey, String>,
) -> ProgramContext {
let target_instruction = &transaction.message.instructions[0]; let program_id = transaction.message.account_keys[target_instruction.program_id_index as usize];
ProgramContext {
program_id,
program_name: lookup_program_name(program_id, address_book),
cpi_count: simulation.meta.inner_instructions.len(),
}
}
fn extract_execution_stats(simulation: &SimulatedTransactionInfo) -> ExecutionStats {
ExecutionStats {
logs: simulation.meta.logs.clone(),
simulated_cu: simulation.meta.compute_units_consumed,
}
}
fn lookup_program_name(program_id: Pubkey, address_book: &HashMap<Pubkey, String>) -> String {
address_book
.get(&program_id)
.cloned()
.unwrap_or_else(|| program_id.to_string())
}
pub fn discover_transaction_context(
transaction: &Transaction,
workflow_name: String,
svm: &mut LiteSVM,
address_book: &HashMap<Pubkey, String>,
) -> TransactionExecutionContext {
let simulation = svm.simulate_transaction(transaction.clone()).unwrap();
let workflow_context =
extract_workflow_context(transaction, &simulation, workflow_name, address_book);
TransactionExecutionContext {
svm_context: SVMContext {
current_slot: svm.get_sysvar::<solana_clock::Clock>().slot,
latest_blockhash: svm.latest_blockhash(),
},
workflow_context,
execution_stats: extract_execution_stats(&simulation),
}
}
fn extract_workflow_context(
transaction: &Transaction,
simulation: &SimulatedTransactionInfo,
workflow_name: String,
address_book: &HashMap<Pubkey, String>,
) -> WorkflowContext {
let mut program_usage: HashMap<Pubkey, usize> = HashMap::new();
let mut cpi_sequence: Vec<String> = Vec::new();
for instruction in &transaction.message.instructions {
let program_id = transaction.message.account_keys[instruction.program_id_index as usize];
*program_usage.entry(program_id).or_insert(0) += 1;
let program_name = lookup_program_name(program_id, address_book);
cpi_sequence.push(program_name);
}
for inner_instruction_set in &simulation.meta.inner_instructions {
for inner_instruction in inner_instruction_set {
let program_id = transaction.message.account_keys
[inner_instruction.instruction.program_id_index as usize];
*program_usage.entry(program_id).or_insert(0) += 1;
let program_name = lookup_program_name(program_id, address_book);
cpi_sequence.push(format!("{}_cpi", program_name));
}
}
let involved_programs: Vec<ProgramInfo> = program_usage
.into_iter()
.map(|(program_id, instruction_count)| ProgramInfo {
program_id,
program_name: lookup_program_name(program_id, address_book),
instruction_count,
})
.collect();
WorkflowContext {
workflow_name,
involved_programs,
cpi_sequence,
total_cpi_calls: simulation.meta.inner_instructions.len(),
}
}
fn serialize_hash<S>(hash: &Hash, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&hash.to_string())
}
fn serialize_pubkey<S>(pubkey: &Pubkey, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&pubkey.to_string())
}