From 53ac0c38b48fc900a5e38f9949a6f5a27d695a9e Mon Sep 17 00:00:00 2001 From: Jack May Date: Thu, 13 Jan 2022 13:34:36 -0800 Subject: [PATCH 1/4] Add get_processed_sibling_instruction syscall --- program-runtime/src/invoke_context.rs | 53 +++-- programs/bpf/Cargo.lock | 14 ++ programs/bpf/Cargo.toml | 2 + programs/bpf/build.rs | 74 +++--- .../rust/sibling_inner_instruction/Cargo.toml | 23 ++ .../rust/sibling_inner_instruction/src/lib.rs | 68 ++++++ .../bpf/rust/sibling_instruction/Cargo.toml | 23 ++ .../bpf/rust/sibling_instruction/src/lib.rs | 98 ++++++++ programs/bpf/tests/programs.rs | 76 ++++++ programs/bpf_loader/src/syscalls.rs | 218 +++++++++++++++++- runtime/src/bank.rs | 34 ++- sdk/program/src/instruction.rs | 135 ++++++++++- sdk/program/src/program_stubs.rs | 17 ++ sdk/src/feature_set.rs | 5 + sdk/src/transaction_context.rs | 91 ++++++-- 15 files changed, 848 insertions(+), 83 deletions(-) create mode 100644 programs/bpf/rust/sibling_inner_instruction/Cargo.toml create mode 100644 programs/bpf/rust/sibling_inner_instruction/src/lib.rs create mode 100644 programs/bpf/rust/sibling_instruction/Cargo.toml create mode 100644 programs/bpf/rust/sibling_instruction/src/lib.rs diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 8793284fa45a8b..99a57affead1ab 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -19,13 +19,15 @@ use { tx_wide_compute_cap, FeatureSet, }, hash::Hash, - instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, + instruction::{AccountMeta, Instruction, InstructionError}, keyed_account::{create_keyed_accounts_unified, KeyedAccount}, native_loader, pubkey::Pubkey, rent::Rent, saturating_add_assign, - transaction_context::{InstructionAccount, TransactionAccount, TransactionContext}, + transaction_context::{ + InstructionAccount, InstructionContext, TransactionAccount, TransactionContext, + }, }, std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc}, }; @@ -798,10 +800,12 @@ impl<'a> InvokeContext<'a> { .map(|index| *self.transaction_context.get_key_of_account_at_index(*index)) .unwrap_or_else(native_loader::id); - let is_lowest_invocation_level = self + let stack_height = self .transaction_context - .get_instruction_context_stack_height() - == 0; + .get_instruction_context_stack_height(); + + let is_lowest_invocation_level = stack_height == 0; + if !is_lowest_invocation_level { // Verify the calling program hasn't misbehaved let mut verify_caller_time = Measure::start("verify_caller_time"); @@ -816,20 +820,10 @@ impl<'a> InvokeContext<'a> { ); verify_caller_result?; - // Record instruction - let compiled_instruction = CompiledInstruction { - program_id_index: self - .transaction_context - .find_index_of_account(&program_id) - .unwrap_or(0) as u8, - data: instruction_data.to_vec(), - accounts: instruction_accounts - .iter() - .map(|instruction_account| instruction_account.index_in_transaction as u8) - .collect(), - }; - self.transaction_context - .record_compiled_instruction(compiled_instruction); + self.transaction_context.record_inner_instruction( + stack_height.saturating_add(1), + InstructionContext::new(program_indices, instruction_accounts, instruction_data), + ); } let result = self @@ -995,6 +989,27 @@ impl<'a> InvokeContext<'a> { pub fn get_sysvar_cache(&self) -> &SysvarCache { &self.sysvar_cache } + + pub fn get_inner_instruction_trace(&self) -> &[Vec<(usize, InstructionContext)>] { + self.transaction_context.get_inner_instruction_trace() + } + + /// Used by the runtime when a new CPI instruction begins + pub fn get_top_level_instruction_trace(&self) -> &[InstructionContext] { + self.transaction_context.get_top_level_instruction_trace() + } + + // TODO bounds checking required here + pub fn get_key_of_account_at_index(&self, index_in_transaction: usize) -> &Pubkey { + self.transaction_context + .get_key_of_account_at_index(index_in_transaction) + } + + pub fn get_instruction_context_at(&self, level: usize) -> Option<&InstructionContext> { + self.transaction_context + .get_instruction_context_at(level) + .ok() + } } pub struct MockInvokeContextPreparation { diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index e2e2aad9720f0c..c4c80ee66cc6ba 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -3017,6 +3017,20 @@ dependencies = [ "solana-program 1.10.0", ] +[[package]] +name = "solana-bpf-rust-sibling-instructions" +version = "1.10.0" +dependencies = [ + "solana-program 1.10.0", +] + +[[package]] +name = "solana-bpf-rust-sibling_inner-instructions" +version = "1.10.0" +dependencies = [ + "solana-program 1.10.0", +] + [[package]] name = "solana-bpf-rust-spoof1" version = "1.10.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 24187b1ac03f98..6be6b123608269 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -81,6 +81,8 @@ members = [ "rust/sanity", "rust/secp256k1_recover", "rust/sha", + "rust/sibling_inner_instruction", + "rust/sibling_instruction", "rust/spoof1", "rust/spoof1_system", "rust/sysvar", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 3285481538346f..49d00671a110bf 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -58,44 +58,46 @@ fn main() { "target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); let rust_programs = [ - "128bit", - "alloc", - "call_depth", - "caller_access", - "custom_heap", - "dep_crate", - "deprecated_loader", - "dup_accounts", - "error_handling", - "log_data", - "external_spend", - "finalize", - "instruction_introspection", - "invoke", - "invoke_and_error", - "invoke_and_ok", + // "128bit", + // "alloc", + // "call_depth", + // "caller_access", + // "custom_heap", + // "dep_crate", + // "deprecated_loader", + // "dup_accounts", + // "error_handling", + // "log_data", + // "external_spend", + // "finalize", + // "instruction_introspection", + // "invoke", + // "invoke_and_error", + // "invoke_and_ok", "invoke_and_return", - "invoked", - "iter", - "many_args", - "mem", - "membuiltins", + // "invoked", + // "iter", + // "many_args", + // "mem", + // "membuiltins", "noop", - "panic", - "param_passing", - "rand", - "realloc", - "realloc_invoke", - "ro_modify", - "ro_account_modify", - "sanity", - "secp256k1_recover", - "sha", - "spoof1", - "spoof1_system", - "upgradeable", - "upgraded", - "zk_token_elgamal", + // "panic", + // "param_passing", + // "rand", + // "realloc", + // "realloc_invoke", + // "ro_modify", + // "ro_account_modify", + // "sanity", + // "secp256k1_recover", + // "sha", + "sibling_inner_instruction", + "sibling_instruction", + // "spoof1", + // "spoof1_system", + // "upgradeable", + // "upgraded", + // "zk_token_elgamal", ]; for program in rust_programs.iter() { println!( diff --git a/programs/bpf/rust/sibling_inner_instruction/Cargo.toml b/programs/bpf/rust/sibling_inner_instruction/Cargo.toml new file mode 100644 index 00000000000000..9ac9b6839681df --- /dev/null +++ b/programs/bpf/rust/sibling_inner_instruction/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "solana-bpf-rust-sibling_inner-instructions" +version = "1.10.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-bpf-rust-log-data" +edition = "2021" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=1.10.0" } + +[features] +default = ["program"] +program = [] + +[lib] +crate-type = ["lib", "cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/sibling_inner_instruction/src/lib.rs b/programs/bpf/rust/sibling_inner_instruction/src/lib.rs new file mode 100644 index 00000000000000..3edfbd59d63daf --- /dev/null +++ b/programs/bpf/rust/sibling_inner_instruction/src/lib.rs @@ -0,0 +1,68 @@ +//! Example Rust-based BPF program that queries sibling instructions + +#![cfg(feature = "program")] + +use solana_program::{ + account_info::AccountInfo, + entrypoint, + entrypoint::ProgramResult, + instruction::{get_invoke_depth, get_processed_sibling_instruction, AccountMeta, Instruction}, + msg, + program::invoke, + pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("sibling inner"); + + // account 0 is mint + // account 1 is noop + // account 2 is invoke_and_return + + // Check sibling instructions + + let sibling_instruction2 = Instruction::new_with_bytes( + *accounts[2].key, + &[3], + vec![AccountMeta::new_readonly(*accounts[1].key, false)], + ); + let sibling_instruction1 = Instruction::new_with_bytes( + *accounts[1].key, + &[2], + vec![ + AccountMeta::new_readonly(*accounts[0].key, true), + AccountMeta::new_readonly(*accounts[1].key, false), + ], + ); + let sibling_instruction0 = Instruction::new_with_bytes( + *accounts[1].key, + &[1], + vec![ + AccountMeta::new_readonly(*accounts[1].key, false), + AccountMeta::new_readonly(*accounts[0].key, true), + ], + ); + + // TODO shouldn't this be 1? + assert_eq!(2, get_invoke_depth()); + assert_eq!( + get_processed_sibling_instruction(0), + Some((2, sibling_instruction0)) + ); + assert_eq!( + get_processed_sibling_instruction(1), + Some((2, sibling_instruction1)) + ); + assert_eq!( + get_processed_sibling_instruction(2), + Some((2, sibling_instruction2)) + ); + assert_eq!(get_processed_sibling_instruction(3), None); + + Ok(()) +} diff --git a/programs/bpf/rust/sibling_instruction/Cargo.toml b/programs/bpf/rust/sibling_instruction/Cargo.toml new file mode 100644 index 00000000000000..951a3f799fdbda --- /dev/null +++ b/programs/bpf/rust/sibling_instruction/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "solana-bpf-rust-sibling-instructions" +version = "1.10.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +documentation = "https://docs.rs/solana-bpf-rust-log-data" +edition = "2021" + +[dependencies] +solana-program = { path = "../../../../sdk/program", version = "=1.10.0" } + +[features] +default = ["program"] +program = [] + +[lib] +crate-type = ["lib", "cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/sibling_instruction/src/lib.rs b/programs/bpf/rust/sibling_instruction/src/lib.rs new file mode 100644 index 00000000000000..9c74044269d187 --- /dev/null +++ b/programs/bpf/rust/sibling_instruction/src/lib.rs @@ -0,0 +1,98 @@ +//! Example Rust-based BPF program that queries sibling instructions + +#![cfg(feature = "program")] + +use solana_program::{ + account_info::AccountInfo, + entrypoint, + entrypoint::ProgramResult, + instruction::{get_invoke_depth, get_processed_sibling_instruction, AccountMeta, Instruction}, + msg, + program::invoke, + pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("sibling"); + + // account 0 is mint + // account 1 is noop + // account 2 is invoke_and_return + // account 3 is sibling_inner + + // Invoke child instructions + + let instruction3 = Instruction::new_with_bytes( + *accounts[2].key, + &[3], + vec![AccountMeta::new_readonly(*accounts[1].key, false)], + ); + let instruction2 = Instruction::new_with_bytes( + *accounts[1].key, + &[2], + vec![ + AccountMeta::new_readonly(*accounts[0].key, true), + AccountMeta::new_readonly(*accounts[1].key, false), + ], + ); + let instruction1 = Instruction::new_with_bytes( + *accounts[1].key, + &[1], + vec![ + AccountMeta::new_readonly(*accounts[1].key, false), + AccountMeta::new_readonly(*accounts[0].key, true), + ], + ); + let instruction0 = Instruction::new_with_bytes( + *accounts[3].key, + &[0], + vec![ + AccountMeta::new_readonly(*accounts[0].key, false), + AccountMeta::new_readonly(*accounts[1].key, false), + AccountMeta::new_readonly(*accounts[2].key, false), + AccountMeta::new_readonly(*accounts[3].key, false), + ], + ); + invoke(&instruction3, accounts)?; + invoke(&instruction2, accounts)?; + invoke(&instruction1, accounts)?; + invoke(&instruction0, accounts)?; + + // Check sibling instructions + + let instruction1 = Instruction::new_with_bytes( + *accounts[1].key, + &[43], + vec![ + AccountMeta::new_readonly(*accounts[1].key, false), + AccountMeta::new(*accounts[0].key, true), + ], + ); + let instruction0 = Instruction::new_with_bytes( + *accounts[1].key, + &[42], + vec![ + AccountMeta::new(*accounts[0].key, true), + AccountMeta::new_readonly(*accounts[1].key, false), + ], + ); + + // TODO shouldn't this be 0? + assert_eq!(1, get_invoke_depth()); + assert_eq!( + get_processed_sibling_instruction(0), + Some((1, instruction0)) + ); + assert_eq!( + get_processed_sibling_instruction(1), + Some((1, instruction1)) + ); + assert_eq!(get_processed_sibling_instruction(2), None); + + Ok(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index f2abc0cc6dd929..27f78565f34e57 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -3343,3 +3343,79 @@ fn test_program_bpf_realloc_invoke() { TransactionError::InstructionError(0, InstructionError::InvalidRealloc) ); } + +#[test] +#[cfg(any(feature = "bpf_rust"))] +fn test_program_bpf_processed_inner_instruction() { + solana_logger::setup(); + + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_genesis_config(50); + let mut bank = Bank::new_for_tests(&genesis_config); + let (name, id, entrypoint) = solana_bpf_loader_program!(); + bank.add_builtin(&name, &id, entrypoint); + let bank = Arc::new(bank); + let bank_client = BankClient::new_shared(&bank); + + let sibling_program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_sibling_instructions", + ); + let sibling_inner_program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_sibling_inner_instructions", + ); + let noop_program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_noop", + ); + let invoke_and_return_program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_invoke_and_return", + ); + + let instruction2 = Instruction::new_with_bytes( + noop_program_id, + &[43], + vec![ + AccountMeta::new_readonly(noop_program_id, false), + AccountMeta::new(mint_keypair.pubkey(), true), + ], + ); + let instruction1 = Instruction::new_with_bytes( + noop_program_id, + &[42], + vec![ + AccountMeta::new(mint_keypair.pubkey(), true), + AccountMeta::new_readonly(noop_program_id, false), + ], + ); + let instruction0 = Instruction::new_with_bytes( + sibling_program_id, + &[1, 2, 3, 0, 4, 5, 6], + vec![ + AccountMeta::new(mint_keypair.pubkey(), true), + AccountMeta::new_readonly(noop_program_id, false), + AccountMeta::new_readonly(invoke_and_return_program_id, false), + AccountMeta::new_readonly(sibling_inner_program_id, false), + ], + ); + let message = Message::new( + &[instruction2, instruction1, instruction0], + Some(&mint_keypair.pubkey()), + ); + assert!(bank_client + .send_and_confirm_message(&[&mint_keypair], message) + .is_ok()); +} diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 0c66c321e3ebde..778e4e3d87ce6b 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -22,14 +22,14 @@ use { blake3, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, entrypoint::{BPF_ALIGN_OF_U128, MAX_PERMITTED_DATA_INCREASE, SUCCESS}, feature_set::{ - self, blake3_syscall_enabled, disable_fees_sysvar, do_support_realloc, - fixed_memcpy_nonoverlapping_check, libsecp256k1_0_5_upgrade_enabled, - prevent_calling_precompiles_as_programs, return_data_syscall_enabled, - secp256k1_recover_syscall_enabled, sol_log_data_syscall_enabled, - update_syscall_base_costs, + self, add_get_processed_sibling_instruction_syscall, blake3_syscall_enabled, + disable_fees_sysvar, do_support_realloc, fixed_memcpy_nonoverlapping_check, + libsecp256k1_0_5_upgrade_enabled, prevent_calling_precompiles_as_programs, + return_data_syscall_enabled, secp256k1_recover_syscall_enabled, + sol_log_data_syscall_enabled, update_syscall_base_costs, }, hash::{Hasher, HASH_BYTES}, - instruction::{AccountMeta, Instruction, InstructionError}, + instruction::{AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction}, keccak, native_loader, precompiles::is_precompile, program::MAX_RETURN_DATA, @@ -222,6 +222,24 @@ pub fn register_syscalls( syscall_registry.register_syscall_by_name(b"sol_log_data", SyscallLogData::call)?; } + if invoke_context + .feature_set + .is_active(&add_get_processed_sibling_instruction_syscall::id()) + { + syscall_registry.register_syscall_by_name( + b"sol_get_processed_sibling_instruction", + SyscallGetProcessedSiblingInstruction::call, + )?; + } + + if invoke_context + .feature_set + .is_active(&add_get_processed_sibling_instruction_syscall::id()) + { + syscall_registry + .register_syscall_by_name(b"sol_get_invoke_depth", SyscallGetInvokeDepth::call)?; + } + Ok(syscall_registry) } @@ -262,6 +280,9 @@ pub fn bind_syscall_context_objects<'a, 'b>( let is_zk_token_sdk_enabled = invoke_context .feature_set .is_active(&feature_set::zk_token_sdk_enabled::id()); + let add_get_processed_sibling_instruction_syscall = invoke_context + .feature_set + .is_active(&add_get_processed_sibling_instruction_syscall::id()); let loader_id = invoke_context .transaction_context @@ -444,6 +465,24 @@ pub fn bind_syscall_context_objects<'a, 'b>( }), ); + // processed inner instructions + bind_feature_gated_syscall_context_object!( + vm, + add_get_processed_sibling_instruction_syscall, + Box::new(SyscallGetProcessedSiblingInstruction { + invoke_context: invoke_context.clone(), + }), + ); + + // invoke depth + bind_feature_gated_syscall_context_object!( + vm, + add_get_processed_sibling_instruction_syscall, + Box::new(SyscallGetInvokeDepth { + invoke_context: invoke_context.clone(), + }), + ); + // Cross-program invocation syscalls vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedC { @@ -2955,6 +2994,173 @@ impl<'a, 'b> SyscallObject for SyscallLogData<'a, 'b> { } } +pub struct SyscallGetProcessedSiblingInstruction<'a, 'b> { + invoke_context: Rc>>, +} +impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<'a, 'b> { + fn call( + &mut self, + index: u64, + meta_addr: u64, + program_id_addr: u64, + data_addr: u64, + accounts_addr: u64, + memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + let invoke_context = question_mark!( + self.invoke_context + .try_borrow() + .map_err(|_| SyscallError::InvokeContextBorrowFailed), + result + ); + let loader_id = question_mark!( + invoke_context + .transaction_context + .get_loader_key() + .map_err(SyscallError::InstructionError), + result + ); + + let budget = invoke_context.get_compute_budget(); + question_mark!( + invoke_context + .get_compute_meter() + .consume(budget.syscall_base_cost), + result + ); + + let invoke_depth = invoke_context.get_invoke_depth(); + let instruction_context = if invoke_depth == 1 { + let trace = invoke_context.get_top_level_instruction_trace(); + trace + .len() + .checked_sub((index as usize).saturating_add(1).saturating_add(1)) + .map(|index| trace.get(index)) + .flatten() + } else { + invoke_context + .get_inner_instruction_trace() + .last() + .map(|inners| { + let mut current_index = 0; + inners + .iter() + .rev() + .skip(1) + .find(|(stack_depth, _)| { + if invoke_depth == *stack_depth { + if index == current_index { + return true; + } else { + current_index += 1; + } + } + false + }) + .map(|(_, instruction_context)| instruction_context) + }) + .flatten() + }; + + if let Some(instruction_context) = instruction_context { + let ProcessedSiblingInstruction { + data_len, + accounts_len, + depth, + } = question_mark!( + translate_type_mut::( + memory_mapping, + meta_addr, + &loader_id + ), + result + ); + + if *data_len >= instruction_context.get_instruction_data().len() + && *accounts_len == instruction_context.get_number_of_instruction_accounts() + { + let program_id = question_mark!( + translate_type_mut::(memory_mapping, program_id_addr, &loader_id), + result + ); + let data = question_mark!( + translate_slice_mut::( + memory_mapping, + data_addr, + *data_len as u64, + &loader_id, + ), + result + ); + let accounts = question_mark!( + translate_slice_mut::( + memory_mapping, + accounts_addr, + *accounts_len as u64, + &loader_id, + ), + result + ); + + *program_id = + instruction_context.get_program_id(invoke_context.transaction_context); + data.clone_from_slice(instruction_context.get_instruction_data()); + let account_metas = instruction_context + .get_instruction_accounts() + .iter() + .map(|instruction_account| AccountMeta { + pubkey: *invoke_context + .get_key_of_account_at_index(instruction_account.index_in_transaction), + is_signer: instruction_account.is_signer, + is_writable: instruction_account.is_writable, + }) + .collect::>(); + accounts.clone_from_slice(account_metas.as_slice()); + } + *data_len = instruction_context.get_instruction_data().len(); + *accounts_len = instruction_context.get_number_of_instruction_accounts(); + *depth = invoke_depth; + *result = Ok(true as u64); + return; + } + *result = Ok(false as u64); + } +} + +pub struct SyscallGetInvokeDepth<'a, 'b> { + invoke_context: Rc>>, +} +impl<'a, 'b> SyscallObject for SyscallGetInvokeDepth<'a, 'b> { + fn call( + &mut self, + _arg1: u64, + _arg2: u64, + _arg3: u64, + _arg4: u64, + _arg5: u64, + _memory_mapping: &MemoryMapping, + result: &mut Result>, + ) { + let invoke_context = question_mark!( + self.invoke_context + .try_borrow() + .map_err(|_| SyscallError::InvokeContextBorrowFailed), + result + ); + + let budget = invoke_context.get_compute_budget(); + question_mark!( + invoke_context + .get_compute_meter() + .consume(budget.syscall_base_cost), + result + ); + + *result = Ok(invoke_context.get_invoke_depth() as u64); + } +} + #[cfg(test)] mod tests { #[allow(deprecated)] diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 64ed3736e7314f..e687121fd61a2a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3890,14 +3890,38 @@ impl Bank { let (accounts, instruction_trace) = transaction_context.deconstruct(); loaded_transaction.accounts = accounts; + let inner_instructions = if enable_cpi_recording { + Some( + instruction_trace + .iter() + .map(|inner_instructions| { + inner_instructions + .iter() + .map(|(_, instruction_context)| + // TODO this could be a helper + CompiledInstruction { + program_id_index: instruction_context.get_program_id_index() as u8, + data: instruction_context.get_instruction_data().to_vec(), + accounts: instruction_context + .get_instruction_accounts() + .iter() + .map(|instruction_account| { + instruction_account.index_in_transaction as u8 + }) + .collect(), + }) + .collect() + }) + .collect(), + ) + } else { + None + }; + TransactionExecutionResult::Executed(TransactionExecutionDetails { status, log_messages, - inner_instructions: if enable_cpi_recording { - Some(instruction_trace) - } else { - None - }, + inner_instructions, durable_nonce_fee, }) } diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 587ecee5ffcb5c..80ae1c8d9c401d 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -528,7 +528,8 @@ pub fn checked_add(a: u64, b: u64) -> Result { /// default [`AccountMeta::new`] constructor creates writable accounts, this is /// a minor hazard: use [`AccountMeta::new_readonly`] to specify that an account /// is not writable. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +#[repr(C)] +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] pub struct AccountMeta { /// An account's public key. pub pubkey: Pubkey, @@ -648,3 +649,135 @@ impl CompiledInstruction { &program_ids[self.program_id_index as usize] } } + +/// Use to query and convey information about the sibling instruction components +/// when calling the `sol_get_processed_sibling_instruction` syscall. +#[repr(C)] +#[derive(Default, Debug, Clone, Copy)] +pub struct ProcessedSiblingInstruction { + pub data_len: usize, + pub accounts_len: usize, + pub depth: usize, +} + +/// Returns a sibling instruction from the processed sibling instruction list. +/// +/// The processed sibling instruction list is a reverse-ordered list of +/// successfully processed sibling instructions. For example, given the call flow: +/// +/// A +/// B -> C -> D +/// B -> E +/// B -> F +/// +/// Then B's processed sibling instruction list is: [(1, A)] +/// Then F's processed sibling instruction list is: [(2, E), (2, C)] +pub fn get_processed_sibling_instruction(index: usize) -> Option<(usize, Instruction)> { + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_get_processed_sibling_instruction( + index: u64, + meta: *mut ProcessedSiblingInstruction, + program_id: *mut Pubkey, + data: *mut u8, + accounts: *mut AccountMeta, + ) -> u64; + } + + let mut meta = ProcessedSiblingInstruction::default(); + let mut program_id = Pubkey::default(); + + if 1 == unsafe { + sol_get_processed_sibling_instruction( + index as u64, + &mut meta, + &mut program_id, + &mut u8::default(), + &mut AccountMeta::default(), + ) + } { + let mut data = Vec::new(); + let mut accounts = Vec::new(); + data.resize_with(meta.data_len, u8::default); + accounts.resize_with(meta.accounts_len, AccountMeta::default); + + let _ = unsafe { + sol_get_processed_sibling_instruction( + index as u64, + &mut meta, + &mut program_id, + data.as_mut_ptr(), + accounts.as_mut_ptr(), + ) + }; + + Some(( + meta.depth, + Instruction::new_with_bytes(program_id, &data, accounts), + )) + } else { + None + } + } + + #[cfg(not(target_arch = "bpf"))] + crate::program_stubs::sol_get_processed_sibling_instruction(index) +} + +/// Get the current invocation depth, transaction-level instructions are depth +/// 0, fist invoked inner instruction is depth 1, etc... +pub fn get_invoke_depth() -> usize { + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_get_invoke_depth() -> u64; + } + + unsafe { sol_get_invoke_depth() as usize } + } + + #[cfg(not(target_arch = "bpf"))] + { + crate::program_stubs::sol_get_invoke_depth() as usize + } +} + +#[test] +fn test_account_meta_layout() { + #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] + struct AccountMetaRust { + pub pubkey: Pubkey, + pub is_signer: bool, + pub is_writable: bool, + } + + let account_meta_rust = AccountMetaRust::default(); + let base_rust_addr = &account_meta_rust as *const _ as u64; + let pubkey_rust_addr = &account_meta_rust.pubkey as *const _ as u64; + let is_signer_rust_addr = &account_meta_rust.is_signer as *const _ as u64; + let is_writable_rust_addr = &account_meta_rust.is_writable as *const _ as u64; + + let account_meta_c = AccountMeta::default(); + let base_c_addr = &account_meta_c as *const _ as u64; + let pubkey_c_addr = &account_meta_c.pubkey as *const _ as u64; + let is_signer_c_addr = &account_meta_c.is_signer as *const _ as u64; + let is_writable_c_addr = &account_meta_c.is_writable as *const _ as u64; + + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::() + ); + assert_eq!( + pubkey_rust_addr - base_rust_addr, + pubkey_c_addr - base_c_addr + ); + assert_eq!( + is_signer_rust_addr - base_rust_addr, + is_signer_c_addr - base_c_addr + ); + assert_eq!( + is_writable_rust_addr - base_rust_addr, + is_writable_c_addr - base_c_addr + ); +} diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index bc96ab5276ca9f..b20a3e151623c6 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -91,6 +91,12 @@ pub trait SyscallStubs: Sync + Send { fn sol_log_data(&self, fields: &[&[u8]]) { println!("data: {}", fields.iter().map(base64::encode).join(" ")); } + fn sol_get_processed_sibling_instruction(&self, _index: usize) -> Option<(usize, Instruction)> { + None + } + fn sol_get_invoke_depth(&self) -> u64 { + 0 + } } struct DefaultSyscallStubs {} @@ -177,6 +183,17 @@ pub(crate) fn sol_log_data(data: &[&[u8]]) { SYSCALL_STUBS.read().unwrap().sol_log_data(data) } +pub(crate) fn sol_get_processed_sibling_instruction(index: usize) -> Option<(usize, Instruction)> { + SYSCALL_STUBS + .read() + .unwrap() + .sol_get_processed_sibling_instruction(index) +} + +pub(crate) fn sol_get_invoke_depth() -> u64 { + SYSCALL_STUBS.read().unwrap().sol_get_invoke_depth() +} + /// Check that two regions do not overlap. /// /// Adapted from libcore, hidden to share with bpf_loader without being part of diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 002a32487e49a1..36112e06e0a729 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -311,6 +311,10 @@ pub mod reject_vote_account_close_unless_zero_credit_epoch { solana_sdk::declare_id!("ALBk3EWdeAg2WAGf6GPDUf1nynyNqCdEVmgouG7rpuCj"); } +pub mod add_get_processed_sibling_instruction_syscall { + solana_sdk::declare_id!("CFK1hRCNy8JJuAAY8Pb2GjLFNdCThS2qwZNe3izzBMgn"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -383,6 +387,7 @@ lazy_static! { (vote_withdraw_authority_may_change_authorized_voter::id(), "vote account withdraw authority may change the authorized voter #22521"), (spl_associated_token_account_v1_0_4::id(), "SPL Associated Token Account Program release version 1.0.4, tied to token 3.3.0 #22648"), (reject_vote_account_close_unless_zero_credit_epoch::id(), "fail vote account withdraw to 0 unless account earned 0 credits in last completed epoch"), + (add_get_processed_sibling_instruction_syscall::id(), "add add_get_processed_sibling_instruction_syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/transaction_context.rs b/sdk/src/transaction_context.rs index bef162d96f762b..db416869e34021 100644 --- a/sdk/src/transaction_context.rs +++ b/sdk/src/transaction_context.rs @@ -2,7 +2,7 @@ use crate::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, - instruction::{CompiledInstruction, InstructionError}, + instruction::InstructionError, lamports::LamportsError, pubkey::Pubkey, }; @@ -32,7 +32,8 @@ pub struct TransactionContext { instruction_context_capacity: usize, instruction_context_stack: Vec, number_of_instructions_at_transaction_level: usize, - instruction_trace: Vec>, + top_level_instruction_trace: Vec, + inner_instruction_trace: Vec>, return_data: (Pubkey, Vec), } @@ -54,13 +55,23 @@ impl TransactionContext { instruction_context_capacity, instruction_context_stack: Vec::with_capacity(instruction_context_capacity), number_of_instructions_at_transaction_level, - instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level), + top_level_instruction_trace: Vec::with_capacity( + number_of_instructions_at_transaction_level, + ), + inner_instruction_trace: Vec::with_capacity( + number_of_instructions_at_transaction_level, + ), return_data: (Pubkey::default(), Vec::new()), } } /// Used by the bank in the runtime to write back the processed accounts and recorded instructions - pub fn deconstruct(self) -> (Vec, Vec>) { + pub fn deconstruct( + self, + ) -> ( + Vec, + Vec>, + ) { ( Vec::from(Pin::into_inner(self.account_keys)) .into_iter() @@ -70,7 +81,7 @@ impl TransactionContext { .map(|account| account.into_inner()), ) .collect(), - self.instruction_trace, + self.inner_instruction_trace, ) } @@ -151,17 +162,23 @@ impl TransactionContext { if self.instruction_context_stack.len() >= self.instruction_context_capacity { return Err(InstructionError::CallDepth); } + + let instruction_context = InstructionContext { + program_accounts: program_accounts.to_vec(), + instruction_accounts: instruction_accounts.to_vec(), + instruction_data: instruction_data.to_vec(), + }; if self.instruction_context_stack.is_empty() { debug_assert!( - self.instruction_trace.len() < self.number_of_instructions_at_transaction_level + self.inner_instruction_trace.len() + < self.number_of_instructions_at_transaction_level ); - self.instruction_trace.push(Vec::new()); + self.inner_instruction_trace.push(Vec::new()); + self.top_level_instruction_trace + .push(instruction_context.clone()); } - self.instruction_context_stack.push(InstructionContext { - program_accounts: program_accounts.to_vec(), - instruction_accounts: instruction_accounts.to_vec(), - instruction_data: instruction_data.to_vec(), - }); + + self.instruction_context_stack.push(instruction_context); Ok(()) } @@ -204,17 +221,31 @@ impl TransactionContext { } /// Used by the runtime when a new CPI instruction begins - pub fn record_compiled_instruction(&mut self, instruction: CompiledInstruction) { - if let Some(records) = self.instruction_trace.last_mut() { - records.push(instruction); + pub fn record_inner_instruction( + &mut self, + stack_height: usize, + instruction: InstructionContext, + ) { + if let Some(records) = self.inner_instruction_trace.last_mut() { + records.push((stack_height, instruction)); } } + + /// Returns inner instruction trace + pub fn get_inner_instruction_trace(&self) -> &[Vec<(usize, InstructionContext)>] { + &self.inner_instruction_trace + } + + /// Returns the top-level instruction trace + pub fn get_top_level_instruction_trace(&self) -> &[InstructionContext] { + &self.top_level_instruction_trace + } } /// Loaded instruction shared between runtime and programs. /// /// This context is valid for the entire duration of a (possibly cross program) instruction being processed. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct InstructionContext { program_accounts: Vec, instruction_accounts: Vec, @@ -222,16 +253,44 @@ pub struct InstructionContext { } impl InstructionContext { + /// New + pub fn new( + program_accounts: &[usize], + instruction_accounts: &[InstructionAccount], + instruction_data: &[u8], + ) -> Self { + InstructionContext { + program_accounts: program_accounts.to_vec(), + instruction_accounts: instruction_accounts.to_vec(), + instruction_data: instruction_data.to_vec(), + } + } + /// Number of program accounts pub fn get_number_of_program_accounts(&self) -> usize { self.program_accounts.len() } + /// Get the index of the instruction's program id + pub fn get_program_id_index(&self) -> usize { + self.program_accounts.last().cloned().unwrap_or_default() + } + + /// Get the instruction's program id + pub fn get_program_id(&self, transaction_context: &TransactionContext) -> Pubkey { + transaction_context.account_keys[self.program_accounts.last().cloned().unwrap_or_default()] + } + /// Number of accounts in this Instruction (without program accounts) pub fn get_number_of_instruction_accounts(&self) -> usize { self.instruction_accounts.len() } + /// Get the instruction's accounts + pub fn get_instruction_accounts(&self) -> &[InstructionAccount] { + &self.instruction_accounts + } + /// Number of accounts in this Instruction pub fn get_number_of_accounts(&self) -> usize { self.program_accounts From bc2a4930bc6be6ace5fb811a66df8bc6c35668c8 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 1 Feb 2022 10:23:16 -0800 Subject: [PATCH 2/4] rename to stack_height --- program-runtime/src/invoke_context.rs | 7 ++-- program-test/src/lib.rs | 4 +-- .../rust/sibling_inner_instruction/src/lib.rs | 14 ++++---- .../bpf/rust/sibling_instruction/src/lib.rs | 16 +++++---- programs/bpf_loader/src/lib.rs | 6 ++-- programs/bpf_loader/src/syscalls.rs | 33 +++++++++---------- programs/zk-token-proof/src/lib.rs | 4 +-- runtime/src/bank.rs | 24 +++++++++++++- runtime/src/builtins.rs | 2 +- sdk/program/src/instruction.rs | 30 +++++++++-------- sdk/program/src/program_stubs.rs | 10 +++--- sdk/src/transaction_context.rs | 7 +++- 12 files changed, 96 insertions(+), 61 deletions(-) diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 99a57affead1ab..eb001cdeb73489 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -410,8 +410,9 @@ impl<'a> InvokeContext<'a> { self.transaction_context.pop() } - /// Current depth of the invocation stack - pub fn get_invoke_depth(&self) -> usize { + /// Current height of the invocation stack, top level instructions are height + /// `solana_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT` + pub fn get_stack_height(&self) -> usize { self.transaction_context .get_instruction_context_stack_height() } @@ -990,6 +991,7 @@ impl<'a> InvokeContext<'a> { &self.sysvar_cache } + /// Get trace of inner instructions pub fn get_inner_instruction_trace(&self) -> &[Vec<(usize, InstructionContext)>] { self.transaction_context.get_inner_instruction_trace() } @@ -1005,6 +1007,7 @@ impl<'a> InvokeContext<'a> { .get_key_of_account_at_index(index_in_transaction) } + /// Get an instruction context pub fn get_instruction_context_at(&self, level: usize) -> Option<&InstructionContext> { self.transaction_context .get_instruction_context_at(level) diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index c344459d402559..f962ba92d7d8f2 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -105,7 +105,7 @@ pub fn builtin_process_instruction( stable_log::program_invoke( &log_collector, program_id, - invoke_context.get_invoke_depth(), + invoke_context.get_stack_height(), ); // Copy indices_in_instruction into a HashSet to ensure there are no duplicates @@ -255,7 +255,7 @@ impl solana_sdk::program_stubs::SyscallStubs for SyscallStubs { stable_log::program_invoke( &log_collector, &instruction.program_id, - invoke_context.get_invoke_depth(), + invoke_context.get_stack_height(), ); let signers = signers_seeds diff --git a/programs/bpf/rust/sibling_inner_instruction/src/lib.rs b/programs/bpf/rust/sibling_inner_instruction/src/lib.rs index 3edfbd59d63daf..066022393291cc 100644 --- a/programs/bpf/rust/sibling_inner_instruction/src/lib.rs +++ b/programs/bpf/rust/sibling_inner_instruction/src/lib.rs @@ -6,7 +6,10 @@ use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, - instruction::{get_invoke_depth, get_processed_sibling_instruction, AccountMeta, Instruction}, + instruction::{ + get_processed_sibling_instruction, get_stack_height, AccountMeta, Instruction, + TRANSACTION_LEVEL_STACK_HEIGHT, + }, msg, program::invoke, pubkey::Pubkey, @@ -48,19 +51,18 @@ fn process_instruction( ], ); - // TODO shouldn't this be 1? - assert_eq!(2, get_invoke_depth()); + assert_eq!(TRANSACTION_LEVEL_STACK_HEIGHT + 1, get_stack_height()); assert_eq!( get_processed_sibling_instruction(0), - Some((2, sibling_instruction0)) + Some(sibling_instruction0) ); assert_eq!( get_processed_sibling_instruction(1), - Some((2, sibling_instruction1)) + Some(sibling_instruction1) ); assert_eq!( get_processed_sibling_instruction(2), - Some((2, sibling_instruction2)) + Some(sibling_instruction2) ); assert_eq!(get_processed_sibling_instruction(3), None); diff --git a/programs/bpf/rust/sibling_instruction/src/lib.rs b/programs/bpf/rust/sibling_instruction/src/lib.rs index 9c74044269d187..5b62c6ee95ac52 100644 --- a/programs/bpf/rust/sibling_instruction/src/lib.rs +++ b/programs/bpf/rust/sibling_instruction/src/lib.rs @@ -6,7 +6,10 @@ use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, - instruction::{get_invoke_depth, get_processed_sibling_instruction, AccountMeta, Instruction}, + instruction::{ + get_processed_sibling_instruction, get_stack_height, AccountMeta, Instruction, + TRANSACTION_LEVEL_STACK_HEIGHT, + }, msg, program::invoke, pubkey::Pubkey, @@ -65,7 +68,7 @@ fn process_instruction( // Check sibling instructions - let instruction1 = Instruction::new_with_bytes( + let sibling_instruction1 = Instruction::new_with_bytes( *accounts[1].key, &[43], vec![ @@ -73,7 +76,7 @@ fn process_instruction( AccountMeta::new(*accounts[0].key, true), ], ); - let instruction0 = Instruction::new_with_bytes( + let sibling_instruction0 = Instruction::new_with_bytes( *accounts[1].key, &[42], vec![ @@ -82,15 +85,14 @@ fn process_instruction( ], ); - // TODO shouldn't this be 0? - assert_eq!(1, get_invoke_depth()); + assert_eq!(TRANSACTION_LEVEL_STACK_HEIGHT, get_stack_height()); assert_eq!( get_processed_sibling_instruction(0), - Some((1, instruction0)) + Some(sibling_instruction0) ); assert_eq!( get_processed_sibling_instruction(1), - Some((1, instruction1)) + Some(sibling_instruction1) ); assert_eq!(get_processed_sibling_instruction(2), None); diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index cd78190e982dbc..629525411ec54f 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -307,7 +307,7 @@ fn process_instruction_common( if program.executable()? { debug_assert_eq!( first_instruction_account, - 1 - (invoke_context.get_invoke_depth() > 1) as usize, + 1 - (invoke_context.get_stack_height() > 1) as usize, ); if !check_loader_id(&program.owner()?) { @@ -1045,7 +1045,7 @@ impl Executor for BpfExecutor { ) -> Result<(), InstructionError> { let log_collector = invoke_context.get_log_collector(); let compute_meter = invoke_context.get_compute_meter(); - let invoke_depth = invoke_context.get_invoke_depth(); + let stack_height = invoke_context.get_stack_height(); let mut serialize_time = Measure::start("serialize"); let program_id = *invoke_context.transaction_context.get_program_key()?; @@ -1074,7 +1074,7 @@ impl Executor for BpfExecutor { create_vm_time.stop(); execute_time = Measure::start("execute"); - stable_log::program_invoke(&log_collector, &program_id, invoke_depth); + stable_log::program_invoke(&log_collector, &program_id, stack_height); let mut instruction_meter = ThisInstructionMeter::new(compute_meter.clone()); let before = compute_meter.borrow().get_remaining(); let result = if use_jit { diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 778e4e3d87ce6b..89488268702a79 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -29,7 +29,10 @@ use { sol_log_data_syscall_enabled, update_syscall_base_costs, }, hash::{Hasher, HASH_BYTES}, - instruction::{AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction}, + instruction::{ + AccountMeta, Instruction, InstructionError, ProcessedSiblingInstruction, + TRANSACTION_LEVEL_STACK_HEIGHT, + }, keccak, native_loader, precompiles::is_precompile, program::MAX_RETURN_DATA, @@ -237,7 +240,7 @@ pub fn register_syscalls( .is_active(&add_get_processed_sibling_instruction_syscall::id()) { syscall_registry - .register_syscall_by_name(b"sol_get_invoke_depth", SyscallGetInvokeDepth::call)?; + .register_syscall_by_name(b"sol_get_stack_height", SyscallGetStackHeight::call)?; } Ok(syscall_registry) @@ -474,11 +477,11 @@ pub fn bind_syscall_context_objects<'a, 'b>( }), ); - // invoke depth + // Get stack height bind_feature_gated_syscall_context_object!( vm, add_get_processed_sibling_instruction_syscall, - Box::new(SyscallGetInvokeDepth { + Box::new(SyscallGetStackHeight { invoke_context: invoke_context.clone(), }), ); @@ -3030,26 +3033,25 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<' result ); - let invoke_depth = invoke_context.get_invoke_depth(); - let instruction_context = if invoke_depth == 1 { + let stack_height = invoke_context.get_stack_height(); + let instruction_context = if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT { let trace = invoke_context.get_top_level_instruction_trace(); trace .len() .checked_sub((index as usize).saturating_add(1).saturating_add(1)) - .map(|index| trace.get(index)) - .flatten() + .and_then(|index| trace.get(index)) } else { invoke_context .get_inner_instruction_trace() .last() - .map(|inners| { + .and_then(|inners| { let mut current_index = 0; inners .iter() .rev() .skip(1) - .find(|(stack_depth, _)| { - if invoke_depth == *stack_depth { + .find(|(this_stack_height, _)| { + if stack_height == *this_stack_height { if index == current_index { return true; } else { @@ -3060,14 +3062,12 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<' }) .map(|(_, instruction_context)| instruction_context) }) - .flatten() }; if let Some(instruction_context) = instruction_context { let ProcessedSiblingInstruction { data_len, accounts_len, - depth, } = question_mark!( translate_type_mut::( memory_mapping, @@ -3120,7 +3120,6 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<' } *data_len = instruction_context.get_instruction_data().len(); *accounts_len = instruction_context.get_number_of_instruction_accounts(); - *depth = invoke_depth; *result = Ok(true as u64); return; } @@ -3128,10 +3127,10 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<' } } -pub struct SyscallGetInvokeDepth<'a, 'b> { +pub struct SyscallGetStackHeight<'a, 'b> { invoke_context: Rc>>, } -impl<'a, 'b> SyscallObject for SyscallGetInvokeDepth<'a, 'b> { +impl<'a, 'b> SyscallObject for SyscallGetStackHeight<'a, 'b> { fn call( &mut self, _arg1: u64, @@ -3157,7 +3156,7 @@ impl<'a, 'b> SyscallObject for SyscallGetInvokeDepth<'a, 'b> { result ); - *result = Ok(invoke_context.get_invoke_depth() as u64); + *result = Ok(invoke_context.get_stack_height() as u64); } } diff --git a/programs/zk-token-proof/src/lib.rs b/programs/zk-token-proof/src/lib.rs index ab8d3706c8c867..2635e8d770bf39 100644 --- a/programs/zk-token-proof/src/lib.rs +++ b/programs/zk-token-proof/src/lib.rs @@ -3,7 +3,7 @@ use { bytemuck::Pod, solana_program_runtime::{ic_msg, invoke_context::InvokeContext}, - solana_sdk::instruction::InstructionError, + solana_sdk::instruction::{InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT}, solana_zk_token_sdk::zk_token_proof_instruction::*, std::result::Result, }; @@ -28,7 +28,7 @@ pub fn process_instruction( input: &[u8], invoke_context: &mut InvokeContext, ) -> Result<(), InstructionError> { - if invoke_context.get_invoke_depth() != 1 { + if invoke_context.get_stack_height() != TRANSACTION_LEVEL_STACK_HEIGHT { // Not supported as an inner instruction return Err(InstructionError::UnsupportedProgramId); } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e687121fd61a2a..fff9f8c7e16bb9 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3890,7 +3890,9 @@ impl Bank { let (accounts, instruction_trace) = transaction_context.deconstruct(); loaded_transaction.accounts = accounts; - let inner_instructions = if enable_cpi_recording { + // TODO verify + let enable_cpi_recording = true; + let inner_instructions: Option>> = if enable_cpi_recording { Some( instruction_trace .iter() @@ -3917,6 +3919,26 @@ impl Bank { } else { None }; + println!("trace start"); + for i in instruction_trace.iter() { + if i.is_empty() { + println!("trace: []"); + } + for x in i.iter() { + println!("trace {:?}", x); + } + } + println!("inner start"); + if let Some(ref inner) = inner_instructions { + for i in inner.iter() { + if i.is_empty() { + println!("inner: []"); + } + for x in i.iter() { + println!("inner {:?}", x); + } + } + } TransactionExecutionResult::Executed(TransactionExecutionDetails { status, diff --git a/runtime/src/builtins.rs b/runtime/src/builtins.rs index 68ba99670dded9..0610afe17a4561 100644 --- a/runtime/src/builtins.rs +++ b/runtime/src/builtins.rs @@ -20,7 +20,7 @@ fn process_instruction_with_program_logging( ) -> Result<(), InstructionError> { let logger = invoke_context.get_log_collector(); let program_id = invoke_context.transaction_context.get_program_key()?; - stable_log::program_invoke(&logger, program_id, invoke_context.get_invoke_depth()); + stable_log::program_invoke(&logger, program_id, invoke_context.get_stack_height()); let result = process_instruction(first_instruction_account, instruction_data, invoke_context); diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 80ae1c8d9c401d..d5ac87cfa8a18d 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -655,9 +655,10 @@ impl CompiledInstruction { #[repr(C)] #[derive(Default, Debug, Clone, Copy)] pub struct ProcessedSiblingInstruction { + /// Length of the instruction data pub data_len: usize, + /// Number of AccountMeta structures pub accounts_len: usize, - pub depth: usize, } /// Returns a sibling instruction from the processed sibling instruction list. @@ -670,9 +671,9 @@ pub struct ProcessedSiblingInstruction { /// B -> E /// B -> F /// -/// Then B's processed sibling instruction list is: [(1, A)] -/// Then F's processed sibling instruction list is: [(2, E), (2, C)] -pub fn get_processed_sibling_instruction(index: usize) -> Option<(usize, Instruction)> { +/// Then B's processed sibling instruction list is: `[A]` +/// Then F's processed sibling instruction list is: `[E, C]` +pub fn get_processed_sibling_instruction(index: usize) -> Option { #[cfg(target_arch = "bpf")] { extern "C" { @@ -712,10 +713,7 @@ pub fn get_processed_sibling_instruction(index: usize) -> Option<(usize, Instruc ) }; - Some(( - meta.depth, - Instruction::new_with_bytes(program_id, &data, accounts), - )) + Some(Instruction::new_with_bytes(program_id, &data, accounts)) } else { None } @@ -725,21 +723,25 @@ pub fn get_processed_sibling_instruction(index: usize) -> Option<(usize, Instruc crate::program_stubs::sol_get_processed_sibling_instruction(index) } -/// Get the current invocation depth, transaction-level instructions are depth -/// 0, fist invoked inner instruction is depth 1, etc... -pub fn get_invoke_depth() -> usize { +// Stack height when processing transaction-level instructions +pub const TRANSACTION_LEVEL_STACK_HEIGHT: usize = 1; + +/// Get the current stack height, transaction-level instructions are height +/// TRANSACTION_LEVEL_STACK_HEIGHT, fist invoked inner instruction is height +/// TRANSACTION_LEVEL_STACK_HEIGHT + 1, etc... +pub fn get_stack_height() -> usize { #[cfg(target_arch = "bpf")] { extern "C" { - fn sol_get_invoke_depth() -> u64; + fn sol_get_stack_height() -> u64; } - unsafe { sol_get_invoke_depth() as usize } + unsafe { sol_get_stack_height() as usize } } #[cfg(not(target_arch = "bpf"))] { - crate::program_stubs::sol_get_invoke_depth() as usize + crate::program_stubs::sol_get_stack_height() as usize } } diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index b20a3e151623c6..ca31ba04b1a4c5 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -91,10 +91,10 @@ pub trait SyscallStubs: Sync + Send { fn sol_log_data(&self, fields: &[&[u8]]) { println!("data: {}", fields.iter().map(base64::encode).join(" ")); } - fn sol_get_processed_sibling_instruction(&self, _index: usize) -> Option<(usize, Instruction)> { + fn sol_get_processed_sibling_instruction(&self, _index: usize) -> Option { None } - fn sol_get_invoke_depth(&self) -> u64 { + fn sol_get_stack_height(&self) -> u64 { 0 } } @@ -183,15 +183,15 @@ pub(crate) fn sol_log_data(data: &[&[u8]]) { SYSCALL_STUBS.read().unwrap().sol_log_data(data) } -pub(crate) fn sol_get_processed_sibling_instruction(index: usize) -> Option<(usize, Instruction)> { +pub(crate) fn sol_get_processed_sibling_instruction(index: usize) -> Option { SYSCALL_STUBS .read() .unwrap() .sol_get_processed_sibling_instruction(index) } -pub(crate) fn sol_get_invoke_depth() -> u64 { - SYSCALL_STUBS.read().unwrap().sol_get_invoke_depth() +pub(crate) fn sol_get_stack_height() -> u64 { + SYSCALL_STUBS.read().unwrap().sol_get_stack_height() } /// Check that two regions do not overlap. diff --git a/sdk/src/transaction_context.rs b/sdk/src/transaction_context.rs index db416869e34021..abac43d0789a3a 100644 --- a/sdk/src/transaction_context.rs +++ b/sdk/src/transaction_context.rs @@ -22,6 +22,10 @@ pub struct InstructionAccount { pub is_writable: bool, } +/// List of (stack height, instruction) for each top-level instruction +#[derive(Clone, Debug)] +pub type InstructionTrace = Vec> + /// Loaded transaction shared between runtime and programs. /// /// This context is valid for the entire duration of a transaction being processed. @@ -137,7 +141,8 @@ impl TransactionContext { self.instruction_context_capacity } - /// Gets the level of the next InstructionContext + /// Gets instruction stack height, top-level instructions are height + /// `solana_sdk::instruction::TRANSACTION_LEVEL_STACK_HEIGHT` pub fn get_instruction_context_stack_height(&self) -> usize { self.instruction_context_stack.len() } From 19f78686a0158338e54521091dd86e7f5a1ac11c Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 1 Feb 2022 15:07:58 -0800 Subject: [PATCH 3/4] combine instruction traces --- program-runtime/src/invoke_context.rs | 21 ++- programs/bpf/build.rs | 72 +++++------ .../rust/sibling_inner_instruction/src/lib.rs | 1 - programs/bpf_loader/src/syscalls.rs | 47 ++++--- runtime/src/bank.rs | 121 ++++++++++-------- sdk/program/src/instruction.rs | 8 ++ sdk/src/transaction_context.rs | 50 +++----- 7 files changed, 162 insertions(+), 158 deletions(-) diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index eb001cdeb73489..49a3b5fc0c7424 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -805,9 +805,9 @@ impl<'a> InvokeContext<'a> { .transaction_context .get_instruction_context_stack_height(); - let is_lowest_invocation_level = stack_height == 0; + let is_top_level_instruction = stack_height == 0; - if !is_lowest_invocation_level { + if !is_top_level_instruction { // Verify the calling program hasn't misbehaved let mut verify_caller_time = Measure::start("verify_caller_time"); let verify_caller_result = self.verify_and_update(instruction_accounts, true); @@ -821,7 +821,7 @@ impl<'a> InvokeContext<'a> { ); verify_caller_result?; - self.transaction_context.record_inner_instruction( + self.transaction_context.record_instruction( stack_height.saturating_add(1), InstructionContext::new(program_indices, instruction_accounts, instruction_data), ); @@ -843,7 +843,7 @@ impl<'a> InvokeContext<'a> { // Verify the called program has not misbehaved let mut verify_callee_time = Measure::start("verify_callee_time"); let result = execution_result.and_then(|_| { - if is_lowest_invocation_level { + if is_top_level_instruction { self.verify(instruction_accounts, program_indices) } else { self.verify_and_update(instruction_accounts, false) @@ -991,17 +991,12 @@ impl<'a> InvokeContext<'a> { &self.sysvar_cache } - /// Get trace of inner instructions - pub fn get_inner_instruction_trace(&self) -> &[Vec<(usize, InstructionContext)>] { - self.transaction_context.get_inner_instruction_trace() + /// Get instruction trace + pub fn get_instruction_trace(&self) -> &[Vec<(usize, InstructionContext)>] { + self.transaction_context.get_instruction_trace() } - /// Used by the runtime when a new CPI instruction begins - pub fn get_top_level_instruction_trace(&self) -> &[InstructionContext] { - self.transaction_context.get_top_level_instruction_trace() - } - - // TODO bounds checking required here + // Get pubkey of account at index pub fn get_key_of_account_at_index(&self, index_in_transaction: usize) -> &Pubkey { self.transaction_context .get_key_of_account_at_index(index_in_transaction) diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 49d00671a110bf..4d6be86a55e648 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -58,46 +58,46 @@ fn main() { "target/".to_string() + &env::var("PROFILE").unwrap() + &"/bpf".to_string(); let rust_programs = [ - // "128bit", - // "alloc", - // "call_depth", - // "caller_access", - // "custom_heap", - // "dep_crate", - // "deprecated_loader", - // "dup_accounts", - // "error_handling", - // "log_data", - // "external_spend", - // "finalize", - // "instruction_introspection", - // "invoke", - // "invoke_and_error", - // "invoke_and_ok", + "128bit", + "alloc", + "call_depth", + "caller_access", + "custom_heap", + "dep_crate", + "deprecated_loader", + "dup_accounts", + "error_handling", + "log_data", + "external_spend", + "finalize", + "instruction_introspection", + "invoke", + "invoke_and_error", + "invoke_and_ok", "invoke_and_return", - // "invoked", - // "iter", - // "many_args", - // "mem", - // "membuiltins", + "invoked", + "iter", + "many_args", + "mem", + "membuiltins", "noop", - // "panic", - // "param_passing", - // "rand", - // "realloc", - // "realloc_invoke", - // "ro_modify", - // "ro_account_modify", - // "sanity", - // "secp256k1_recover", - // "sha", + "panic", + "param_passing", + "rand", + "realloc", + "realloc_invoke", + "ro_modify", + "ro_account_modify", + "sanity", + "secp256k1_recover", + "sha", "sibling_inner_instruction", "sibling_instruction", - // "spoof1", - // "spoof1_system", - // "upgradeable", - // "upgraded", - // "zk_token_elgamal", + "spoof1", + "spoof1_system", + "upgradeable", + "upgraded", + "zk_token_elgamal", ]; for program in rust_programs.iter() { println!( diff --git a/programs/bpf/rust/sibling_inner_instruction/src/lib.rs b/programs/bpf/rust/sibling_inner_instruction/src/lib.rs index 066022393291cc..134d243a100cf8 100644 --- a/programs/bpf/rust/sibling_inner_instruction/src/lib.rs +++ b/programs/bpf/rust/sibling_inner_instruction/src/lib.rs @@ -11,7 +11,6 @@ use solana_program::{ TRANSACTION_LEVEL_STACK_HEIGHT, }, msg, - program::invoke, pubkey::Pubkey, }; diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 89488268702a79..650656991c874e 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -3034,35 +3034,32 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<' ); let stack_height = invoke_context.get_stack_height(); + let instruction_trace = invoke_context.get_instruction_trace(); let instruction_context = if stack_height == TRANSACTION_LEVEL_STACK_HEIGHT { - let trace = invoke_context.get_top_level_instruction_trace(); - trace + // pick one of the top-level instructions + instruction_trace .len() - .checked_sub((index as usize).saturating_add(1).saturating_add(1)) - .and_then(|index| trace.get(index)) + .checked_sub(2) + .and_then(|result| result.checked_sub(index as usize)) + .and_then(|index| instruction_trace.get(index)) + .and_then(|instruction_list| instruction_list.get(0)) } else { - invoke_context - .get_inner_instruction_trace() - .last() - .and_then(|inners| { - let mut current_index = 0; - inners - .iter() - .rev() - .skip(1) - .find(|(this_stack_height, _)| { - if stack_height == *this_stack_height { - if index == current_index { - return true; - } else { - current_index += 1; - } - } - false - }) - .map(|(_, instruction_context)| instruction_context) + // Walk the last list of inner instructions + instruction_trace.last().and_then(|inners| { + let mut current_index = 0; + inners.iter().rev().skip(1).find(|(this_stack_height, _)| { + if stack_height == *this_stack_height { + if index == current_index { + return true; + } else { + current_index += 1; + } + } + false }) - }; + }) + } + .map(|(_, instruction_context)| instruction_context); if let Some(instruction_context) = instruction_context { let ProcessedSiblingInstruction { diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index fff9f8c7e16bb9..550ab05d5abc6b 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -130,7 +130,7 @@ use { AddressLookupError, Result, SanitizedTransaction, Transaction, TransactionError, TransactionVerificationMode, VersionedTransaction, }, - transaction_context::{TransactionAccount, TransactionContext}, + transaction_context::{InstructionTrace, TransactionAccount, TransactionContext}, }, solana_stake_program::stake_state::{ self, InflationPointCalculationEvent, PointValue, StakeState, @@ -576,7 +576,7 @@ pub struct TransactionResults { pub struct TransactionExecutionDetails { pub status: Result<()>, pub log_messages: Option>, - pub inner_instructions: Option>>, + pub inner_instructions: Option, pub durable_nonce_fee: Option, } @@ -672,12 +672,42 @@ impl TransactionBalancesSet { } pub type TransactionBalances = Vec>; -/// An ordered list of instructions that were invoked during a transaction instruction +/// An ordered list of compiled instructions that were invoked during a +/// transaction instruction pub type InnerInstructions = Vec; -/// A list of instructions that were invoked during each instruction of a transaction +/// A list of compiled instructions that were invoked during each instruction of +/// a transaction pub type InnerInstructionsList = Vec; +/// Convert from an IntrustionTrace to InnerInstructionsList +pub fn inner_instructions_list_from_instruction_trace( + instruction_trace: &InstructionTrace, +) -> InnerInstructionsList { + instruction_trace + .iter() + .map(|inner_instructions_trace| { + inner_instructions_trace + .iter() + .skip(1) + .map(|(_, instruction_context)| { + CompiledInstruction::new_from_raw_parts( + instruction_context.get_program_id_index() as u8, + instruction_context.get_instruction_data().to_vec(), + instruction_context + .get_instruction_accounts() + .iter() + .map(|instruction_account| { + instruction_account.index_in_transaction as u8 + }) + .collect(), + ) + }) + .collect() + }) + .collect() +} + /// A list of log messages emitted during a transaction pub type TransactionLogMessages = Vec; @@ -3890,55 +3920,13 @@ impl Bank { let (accounts, instruction_trace) = transaction_context.deconstruct(); loaded_transaction.accounts = accounts; - // TODO verify - let enable_cpi_recording = true; - let inner_instructions: Option>> = if enable_cpi_recording { - Some( - instruction_trace - .iter() - .map(|inner_instructions| { - inner_instructions - .iter() - .map(|(_, instruction_context)| - // TODO this could be a helper - CompiledInstruction { - program_id_index: instruction_context.get_program_id_index() as u8, - data: instruction_context.get_instruction_data().to_vec(), - accounts: instruction_context - .get_instruction_accounts() - .iter() - .map(|instruction_account| { - instruction_account.index_in_transaction as u8 - }) - .collect(), - }) - .collect() - }) - .collect(), - ) + let inner_instructions = if enable_cpi_recording { + Some(inner_instructions_list_from_instruction_trace( + &instruction_trace, + )) } else { None }; - println!("trace start"); - for i in instruction_trace.iter() { - if i.is_empty() { - println!("trace: []"); - } - for x in i.iter() { - println!("trace {:?}", x); - } - } - println!("inner start"); - if let Some(ref inner) = inner_instructions { - for i in inner.iter() { - if i.is_empty() { - println!("inner: []"); - } - for x in i.iter() { - println!("inner {:?}", x); - } - } - } TransactionExecutionResult::Executed(TransactionExecutionDetails { status, @@ -6662,6 +6650,7 @@ pub(crate) mod tests { sysvar::rewards::Rewards, timing::duration_as_s, transaction::MAX_TX_ACCOUNT_LOCKS, + transaction_context::InstructionContext, }, solana_vote_program::{ vote_instruction, @@ -15875,4 +15864,36 @@ pub(crate) mod tests { } } } + + #[test] + fn test_inner_instructions_list_from_instruction_trace() { + let instruction_trace = vec![ + vec![ + (1, InstructionContext::new(&[], &[], &[1])), + (2, InstructionContext::new(&[], &[], &[2])), + ], + vec![], + vec![ + (1, InstructionContext::new(&[], &[], &[3])), + (2, InstructionContext::new(&[], &[], &[4])), + (3, InstructionContext::new(&[], &[], &[5])), + (2, InstructionContext::new(&[], &[], &[6])), + ], + ]; + + let inner_instructions = inner_instructions_list_from_instruction_trace(&instruction_trace); + + assert_eq!( + inner_instructions, + vec![ + vec![CompiledInstruction::new_from_raw_parts(0, vec![2], vec![])], + vec![], + vec![ + CompiledInstruction::new_from_raw_parts(0, vec![4], vec![]), + CompiledInstruction::new_from_raw_parts(0, vec![5], vec![]), + CompiledInstruction::new_from_raw_parts(0, vec![6], vec![]) + ] + ] + ); + } } diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index d5ac87cfa8a18d..a0077e891e3848 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -640,8 +640,16 @@ impl CompiledInstruction { let data = serialize(data).unwrap(); Self { program_id_index: program_ids_index, + accounts, data, + } + } + + pub fn new_from_raw_parts(program_id_index: u8, data: Vec, accounts: Vec) -> Self { + Self { + program_id_index, accounts, + data, } } diff --git a/sdk/src/transaction_context.rs b/sdk/src/transaction_context.rs index abac43d0789a3a..5f9ae46ad41d0f 100644 --- a/sdk/src/transaction_context.rs +++ b/sdk/src/transaction_context.rs @@ -2,7 +2,7 @@ use crate::{ account::{AccountSharedData, ReadableAccount, WritableAccount}, - instruction::InstructionError, + instruction::{InstructionError, TRANSACTION_LEVEL_STACK_HEIGHT}, lamports::LamportsError, pubkey::Pubkey, }; @@ -22,10 +22,6 @@ pub struct InstructionAccount { pub is_writable: bool, } -/// List of (stack height, instruction) for each top-level instruction -#[derive(Clone, Debug)] -pub type InstructionTrace = Vec> - /// Loaded transaction shared between runtime and programs. /// /// This context is valid for the entire duration of a transaction being processed. @@ -36,8 +32,7 @@ pub struct TransactionContext { instruction_context_capacity: usize, instruction_context_stack: Vec, number_of_instructions_at_transaction_level: usize, - top_level_instruction_trace: Vec, - inner_instruction_trace: Vec>, + instruction_trace: InstructionTrace, return_data: (Pubkey, Vec), } @@ -59,12 +54,7 @@ impl TransactionContext { instruction_context_capacity, instruction_context_stack: Vec::with_capacity(instruction_context_capacity), number_of_instructions_at_transaction_level, - top_level_instruction_trace: Vec::with_capacity( - number_of_instructions_at_transaction_level, - ), - inner_instruction_trace: Vec::with_capacity( - number_of_instructions_at_transaction_level, - ), + instruction_trace: Vec::with_capacity(number_of_instructions_at_transaction_level), return_data: (Pubkey::default(), Vec::new()), } } @@ -85,7 +75,7 @@ impl TransactionContext { .map(|account| account.into_inner()), ) .collect(), - self.inner_instruction_trace, + self.instruction_trace, ) } @@ -175,12 +165,12 @@ impl TransactionContext { }; if self.instruction_context_stack.is_empty() { debug_assert!( - self.inner_instruction_trace.len() - < self.number_of_instructions_at_transaction_level + self.instruction_trace.len() < self.number_of_instructions_at_transaction_level ); - self.inner_instruction_trace.push(Vec::new()); - self.top_level_instruction_trace - .push(instruction_context.clone()); + self.instruction_trace.push(vec![( + TRANSACTION_LEVEL_STACK_HEIGHT, + instruction_context.clone(), + )]); } self.instruction_context_stack.push(instruction_context); @@ -226,27 +216,21 @@ impl TransactionContext { } /// Used by the runtime when a new CPI instruction begins - pub fn record_inner_instruction( - &mut self, - stack_height: usize, - instruction: InstructionContext, - ) { - if let Some(records) = self.inner_instruction_trace.last_mut() { + pub fn record_instruction(&mut self, stack_height: usize, instruction: InstructionContext) { + if let Some(records) = self.instruction_trace.last_mut() { records.push((stack_height, instruction)); } } - /// Returns inner instruction trace - pub fn get_inner_instruction_trace(&self) -> &[Vec<(usize, InstructionContext)>] { - &self.inner_instruction_trace - } - - /// Returns the top-level instruction trace - pub fn get_top_level_instruction_trace(&self) -> &[InstructionContext] { - &self.top_level_instruction_trace + /// Returns instruction trace + pub fn get_instruction_trace(&self) -> &InstructionTrace { + &self.instruction_trace } } +/// List of (stack height, instruction) for each top-level instruction +pub type InstructionTrace = Vec>; + /// Loaded instruction shared between runtime and programs. /// /// This context is valid for the entire duration of a (possibly cross program) instruction being processed. From 6bdf7b0c533ce62285c2a2bf5523aef7d44b89f6 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 2 Feb 2022 13:46:42 -0800 Subject: [PATCH 4/4] feedback --- programs/bpf_loader/src/syscalls.rs | 10 +++---- runtime/src/bank.rs | 6 ++--- sdk/src/transaction_context.rs | 41 ++++++++++++++++++++++++++--- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 650656991c874e..318b5a1785cd5d 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -3104,13 +3104,13 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedSiblingInstruction<' instruction_context.get_program_id(invoke_context.transaction_context); data.clone_from_slice(instruction_context.get_instruction_data()); let account_metas = instruction_context - .get_instruction_accounts() + .get_instruction_accounts_metas() .iter() - .map(|instruction_account| AccountMeta { + .map(|meta| AccountMeta { pubkey: *invoke_context - .get_key_of_account_at_index(instruction_account.index_in_transaction), - is_signer: instruction_account.is_signer, - is_writable: instruction_account.is_writable, + .get_key_of_account_at_index(meta.index_in_transaction), + is_signer: meta.is_signer, + is_writable: meta.is_writable, }) .collect::>(); accounts.clone_from_slice(account_metas.as_slice()); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 550ab05d5abc6b..08e71ba2ea1858 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -695,11 +695,9 @@ pub fn inner_instructions_list_from_instruction_trace( instruction_context.get_program_id_index() as u8, instruction_context.get_instruction_data().to_vec(), instruction_context - .get_instruction_accounts() + .get_instruction_accounts_metas() .iter() - .map(|instruction_account| { - instruction_account.index_in_transaction as u8 - }) + .map(|meta| meta.index_in_transaction as u8) .collect(), ) }) diff --git a/sdk/src/transaction_context.rs b/sdk/src/transaction_context.rs index 5f9ae46ad41d0f..95c7fc075a2635 100644 --- a/sdk/src/transaction_context.rs +++ b/sdk/src/transaction_context.rs @@ -231,6 +231,13 @@ impl TransactionContext { /// List of (stack height, instruction) for each top-level instruction pub type InstructionTrace = Vec>; +#[derive(Clone, Debug)] +pub struct AccountMeta { + pub index_in_transaction: usize, + pub is_signer: bool, + pub is_writable: bool, +} + /// Loaded instruction shared between runtime and programs. /// /// This context is valid for the entire duration of a (possibly cross program) instruction being processed. @@ -275,9 +282,15 @@ impl InstructionContext { self.instruction_accounts.len() } - /// Get the instruction's accounts - pub fn get_instruction_accounts(&self) -> &[InstructionAccount] { - &self.instruction_accounts + pub fn get_instruction_accounts_metas(&self) -> Vec { + self.instruction_accounts + .iter() + .map(|instruction_account| AccountMeta { + index_in_transaction: instruction_account.index_in_transaction, + is_signer: instruction_account.is_signer, + is_writable: instruction_account.is_writable, + }) + .collect() } /// Number of accounts in this Instruction @@ -395,6 +408,28 @@ impl InstructionContext { } result } + + /// Returns whether an account is a signer + pub fn is_signer(&self, index_in_instruction: usize) -> bool { + if index_in_instruction < self.program_accounts.len() { + false + } else { + self.instruction_accounts + [index_in_instruction.saturating_sub(self.program_accounts.len())] + .is_signer + } + } + + /// Returns whether an account is writable + pub fn is_writable(&self, index_in_instruction: usize) -> bool { + if index_in_instruction < self.program_accounts.len() { + false + } else { + self.instruction_accounts + [index_in_instruction.saturating_sub(self.program_accounts.len())] + .is_writable + } + } } /// Shared account borrowed from the TransactionContext and an InstructionContext.