From 2386aaddade542f37115f2535c8bde578ca991e2 Mon Sep 17 00:00:00 2001 From: Jack May Date: Thu, 13 Jan 2022 13:34:36 -0800 Subject: [PATCH 1/3] Add get_processed_inner_instruction syscall --- program-runtime/src/invoke_context.rs | 23 ++++ programs/bpf/Cargo.lock | 7 ++ programs/bpf/Cargo.toml | 1 + programs/bpf/build.rs | 1 + .../bpf/rust/inner_instruction/Cargo.toml | 23 ++++ .../bpf/rust/inner_instruction/src/lib.rs | 61 +++++++++ programs/bpf/tests/programs.rs | 50 ++++++++ programs/bpf_loader/src/syscalls.rs | 117 ++++++++++++++++-- sdk/program/src/instruction.rs | 113 ++++++++++++++++- sdk/program/src/program_stubs.rs | 10 ++ sdk/src/feature_set.rs | 5 + 11 files changed, 403 insertions(+), 8 deletions(-) create mode 100644 programs/bpf/rust/inner_instruction/Cargo.toml create mode 100644 programs/bpf/rust/inner_instruction/src/lib.rs diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 8d1e6d99511aee..58a4971672cec4 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -195,6 +195,7 @@ pub struct InvokeContext<'a> { pub timings: ExecuteDetailsTimings, pub blockhash: Hash, pub lamports_per_signature: u64, + pub processed_inner_instructions: Vec<(usize, Instruction)>, } impl<'a> InvokeContext<'a> { @@ -229,6 +230,7 @@ impl<'a> InvokeContext<'a> { timings: ExecuteDetailsTimings::default(), blockhash, lamports_per_signature, + processed_inner_instructions: Vec::new(), } } @@ -805,6 +807,7 @@ impl<'a> InvokeContext<'a> { .transaction_context .get_instruction_context_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"); @@ -833,6 +836,8 @@ impl<'a> InvokeContext<'a> { }; self.transaction_context .record_compiled_instruction(compiled_instruction); + } else { + self.processed_inner_instructions.clear(); } let result = self @@ -998,6 +1003,24 @@ impl<'a> InvokeContext<'a> { pub fn get_sysvar_cache(&self) -> &SysvarCache { &self.sysvar_cache } + + // Push a processed inner instruction + pub fn add_processed_inner_instruction(&mut self, instruction: Instruction) { + self.processed_inner_instructions + .push((self.invoke_depth(), instruction)); + } + + /// Get a processed inner instruction, reverse ordered list, where last added is index 0 + pub fn get_processed_inner_instruction(&self, index: usize) -> Option<(usize, &Instruction)> { + let index = self + .processed_inner_instructions + .len() + .checked_sub(1)? + .checked_sub(index)?; + self.processed_inner_instructions + .get(index) + .map(|(usize, instruction)| (*usize, instruction)) + } } pub struct MockInvokeContextPreparation { diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 5e883be8e14933..39c626f462392c 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -2836,6 +2836,13 @@ dependencies = [ "solana-program 1.10.0", ] +[[package]] +name = "solana-bpf-rust-inner-instructions" +version = "1.10.0" +dependencies = [ + "solana-program 1.10.0", +] + [[package]] name = "solana-bpf-rust-instruction-introspection" version = "1.10.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index 24187b1ac03f98..b255daeb783255 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -58,6 +58,7 @@ members = [ "rust/log_data", "rust/external_spend", "rust/finalize", + "rust/inner_instruction", "rust/instruction_introspection", "rust/invoke", "rust/invoke_and_error", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 3285481538346f..96c6c9ae781470 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -70,6 +70,7 @@ fn main() { "log_data", "external_spend", "finalize", + "inner_instruction", "instruction_introspection", "invoke", "invoke_and_error", diff --git a/programs/bpf/rust/inner_instruction/Cargo.toml b/programs/bpf/rust/inner_instruction/Cargo.toml new file mode 100644 index 00000000000000..cbe3c8477be7a0 --- /dev/null +++ b/programs/bpf/rust/inner_instruction/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "solana-bpf-rust-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/inner_instruction/src/lib.rs b/programs/bpf/rust/inner_instruction/src/lib.rs new file mode 100644 index 00000000000000..0acbbe7efe7c72 --- /dev/null +++ b/programs/bpf/rust/inner_instruction/src/lib.rs @@ -0,0 +1,61 @@ +//! Example Rust-based BPF program that uses sol_get_processed_inner_instruction syscall + +#![cfg(feature = "program")] + +use solana_program::{ + account_info::AccountInfo, + entrypoint, + entrypoint::ProgramResult, + instruction::{get_processed_inner_instruction, AccountMeta, Instruction}, + msg, + program::invoke, + pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + msg!("inner"); + + // account 0 is mint + // account 1 is noop + // account 2 is invoke_and_return + + let instruction3 = Instruction::new_with_bytes(*accounts[1].key, instruction_data, vec![]); + let instruction2 = Instruction::new_with_bytes( + *accounts[2].key, + instruction_data, + vec![AccountMeta::new_readonly(*accounts[1].key, false)], + ); + let instruction1 = Instruction::new_with_bytes( + *accounts[1].key, + instruction_data, + vec![ + AccountMeta::new_readonly(*accounts[0].key, true), + AccountMeta::new_readonly(*accounts[1].key, false), + ], + ); + let instruction0 = Instruction::new_with_bytes( + *accounts[1].key, + instruction_data, + vec![ + AccountMeta::new_readonly(*accounts[1].key, false), + AccountMeta::new_readonly(*accounts[0].key, true), + ], + ); + + invoke(&instruction2, accounts)?; + invoke(&instruction1, accounts)?; + invoke(&instruction0, accounts)?; + + assert_eq!(Some((1, instruction0)), get_processed_inner_instruction(0)); + assert_eq!(Some((1, instruction1)), get_processed_inner_instruction(1)); + assert_eq!(Some((1, instruction2)), get_processed_inner_instruction(2)); + assert_eq!(Some((2, instruction3)), get_processed_inner_instruction(3)); + assert!(get_processed_inner_instruction(4).is_none()); + + Ok(()) +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index bc0acdceb6d758..003d20716fd325 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -3331,3 +3331,53 @@ 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 program_id = load_bpf_program( + &bank_client, + &bpf_loader::id(), + &mint_keypair, + "solana_bpf_rust_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 instruction = Instruction::new_with_bytes( + 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), + ], + ); + let message = Message::new(&[instruction], 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..79c81a903ad4bf 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_inner_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, InnerMeta, Instruction, InstructionError}, keccak, native_loader, precompiles::is_precompile, program::MAX_RETURN_DATA, @@ -222,6 +222,16 @@ 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_inner_instruction_syscall::id()) + { + syscall_registry.register_syscall_by_name( + b"sol_get_processed_inner_instruction", + SyscallGetProcessedInnerInstruction::call, + )?; + } + Ok(syscall_registry) } @@ -262,6 +272,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_inner_instruction_syscall = invoke_context + .feature_set + .is_active(&add_get_processed_inner_instruction_syscall::id()); let loader_id = invoke_context .transaction_context @@ -444,6 +457,15 @@ pub fn bind_syscall_context_objects<'a, 'b>( }), ); + // processed inner instructions + bind_feature_gated_syscall_context_object!( + vm, + add_get_processed_inner_instruction_syscall, + Box::new(SyscallGetProcessedInnerInstruction { + invoke_context: invoke_context.clone(), + }), + ); + // Cross-program invocation syscalls vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedC { @@ -2610,7 +2632,6 @@ fn check_authorized_program( } Ok(()) } - /// Call process instruction, common to both Rust and C fn call<'a, 'b: 'a>( syscall: &mut dyn SyscallInvokeSigned<'a, 'b>, @@ -2677,6 +2698,8 @@ fn call<'a, 'b: 'a>( ) .map_err(SyscallError::InstructionError)?; + invoke_context.add_processed_inner_instruction(instruction); + // Copy results back to caller for (callee_account_index, caller_account) in accounts.iter_mut() { if let Some(caller_account) = caller_account { @@ -2955,6 +2978,86 @@ impl<'a, 'b> SyscallObject for SyscallLogData<'a, 'b> { } } +pub struct SyscallGetProcessedInnerInstruction<'a, 'b> { + invoke_context: Rc>>, +} +impl<'a, 'b> SyscallObject for SyscallGetProcessedInnerInstruction<'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 + ); + + if let Some((stack_depth, instruction)) = invoke_context.get_processed_inner_instruction(index as usize) { + let InnerMeta { + data_len, + accounts_len, + depth, + } = question_mark!( + translate_type_mut::(memory_mapping, meta_addr, &loader_id), + result + ); + if *data_len >= instruction.data.len() + && *accounts_len >= instruction.accounts.len() + { + 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.program_id; + data.clone_from_slice(instruction.data.as_slice()); + accounts.clone_from_slice(instruction.accounts.as_slice()); + } + *data_len = instruction.data.len(); + *accounts_len = instruction.accounts.len(); + *depth = stack_depth; + *result = Ok(true as u64); + } else { + *result = Ok(false as u64); + } + } +} + #[cfg(test)] mod tests { #[allow(deprecated)] diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index e1fbb1825a845a..69d34d8a71bf46 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -524,7 +524,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, @@ -644,3 +645,113 @@ impl CompiledInstruction { &program_ids[self.program_id_index as usize] } } + +/// Use to query and convey information about the inner instruction components +/// when calling the `sol_get_processed_inner_instruction` syscall. +#[repr(C)] +#[derive(Default, Debug, Clone, Copy)] +pub struct InnerMeta { + pub data_len: usize, + pub accounts_len: usize, + pub depth: usize, +} + +/// Returns a inner instruction from the processed inner instruction list. +/// +/// The processed inner instruction list is a reverse-ordered list of +/// successfully processed inner instructions. For example, given the call flow: +/// +/// A +/// B -> C -> D +/// B -> E +/// B -> F +/// +/// Then B's processed inner instruction list is: [(1, F), (1, E), (1, C), (2, D)] +pub fn get_processed_inner_instruction(index: usize) -> Option<(usize, Instruction)> { + #[cfg(target_arch = "bpf")] + { + extern "C" { + fn sol_get_processed_inner_instruction( + index: u64, + meta: *mut InnerMeta, + program_id: *mut Pubkey, + data: *mut u8, + accounts: *mut AccountMeta, + ) -> u64; + } + + let mut meta = InnerMeta::default(); + let mut program_id = Pubkey::default(); + + if 1 == unsafe { + sol_get_processed_inner_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_inner_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::get_processed_inner_instruction(index) +} + +#[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 b9f62575149735..288efe4f2886c2 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -91,6 +91,9 @@ pub trait SyscallStubs: Sync + Send { fn sol_log_data(&self, fields: &[&[u8]]) { println!("data: {}", fields.iter().map(base64::encode).join(" ")); } + fn get_processed_inner_instruction(&self, _index: usize) -> Option<(usize, Instruction)> { + None + } } struct DefaultSyscallStubs {} @@ -177,6 +180,13 @@ pub(crate) fn sol_log_data(data: &[&[u8]]) { SYSCALL_STUBS.read().unwrap().sol_log_data(data) } +pub(crate) fn get_processed_inner_instruction(index: usize) -> Option<(usize, Instruction)> { + SYSCALL_STUBS + .read() + .unwrap() + .get_processed_inner_instruction(index) +} + /// 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 f5f7466e04fac2..942e8f961c4fe8 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -311,6 +311,10 @@ pub mod vote_withdraw_authority_may_change_authorized_voter { solana_sdk::declare_id!("AVZS3ZsN4gi6Rkx2QUibYuSJG3S6QHib7xCYhG6vGJxU"); } +pub mod add_get_processed_inner_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! { (filter_votes_outside_slot_hashes::id(), "filter vote slots older than the slot hashes history"), (update_syscall_base_costs::id(), "Update syscall base costs"), (vote_withdraw_authority_may_change_authorized_voter::id(), "vote account withdraw authority may change the authorized voter #22521"), + (add_get_processed_inner_instruction_syscall::id(), "add add_get_processed_inner_instruction_syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() From 1355eaf38afc0e85b1e25566d170f586a83dc667 Mon Sep 17 00:00:00 2001 From: Jack May Date: Mon, 17 Jan 2022 23:02:42 -0800 Subject: [PATCH 2/3] add get_invoke_depth syscall --- .../bpf/rust/inner_instruction/src/lib.rs | 3 +- programs/bpf_loader/src/syscalls.rs | 65 +++++++++++++++++-- sdk/program/src/instruction.rs | 25 ++++++- sdk/program/src/program_stubs.rs | 13 +++- 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/programs/bpf/rust/inner_instruction/src/lib.rs b/programs/bpf/rust/inner_instruction/src/lib.rs index 0acbbe7efe7c72..5018a7e9554ba0 100644 --- a/programs/bpf/rust/inner_instruction/src/lib.rs +++ b/programs/bpf/rust/inner_instruction/src/lib.rs @@ -6,7 +6,7 @@ use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, - instruction::{get_processed_inner_instruction, AccountMeta, Instruction}, + instruction::{get_invoke_depth, get_processed_inner_instruction, AccountMeta, Instruction}, msg, program::invoke, pubkey::Pubkey, @@ -51,6 +51,7 @@ fn process_instruction( invoke(&instruction1, accounts)?; invoke(&instruction0, accounts)?; + assert_eq!(1, get_invoke_depth()); assert_eq!(Some((1, instruction0)), get_processed_inner_instruction(0)); assert_eq!(Some((1, instruction1)), get_processed_inner_instruction(1)); assert_eq!(Some((1, instruction2)), get_processed_inner_instruction(2)); diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 79c81a903ad4bf..d7cf4eca73958f 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -232,6 +232,14 @@ pub fn register_syscalls( )?; } + if invoke_context + .feature_set + .is_active(&add_get_processed_inner_instruction_syscall::id()) + { + syscall_registry + .register_syscall_by_name(b"sol_get_invoke_depth", SyscallGetInvokeDepth::call)?; + } + Ok(syscall_registry) } @@ -466,6 +474,15 @@ pub fn bind_syscall_context_objects<'a, 'b>( }), ); + // processed inner instructions + bind_feature_gated_syscall_context_object!( + vm, + add_get_processed_inner_instruction_syscall, + Box::new(SyscallGetInvokeDepth { + invoke_context: invoke_context.clone(), + }), + ); + // Cross-program invocation syscalls vm.bind_syscall_context_object( Box::new(SyscallInvokeSignedC { @@ -3014,7 +3031,9 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedInnerInstruction<'a, result ); - if let Some((stack_depth, instruction)) = invoke_context.get_processed_inner_instruction(index as usize) { + if let Some((stack_depth, instruction)) = + invoke_context.get_processed_inner_instruction(index as usize) + { let InnerMeta { data_len, accounts_len, @@ -3023,15 +3042,18 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedInnerInstruction<'a, translate_type_mut::(memory_mapping, meta_addr, &loader_id), result ); - if *data_len >= instruction.data.len() - && *accounts_len >= instruction.accounts.len() - { + if *data_len >= instruction.data.len() && *accounts_len >= instruction.accounts.len() { 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,), + translate_slice_mut::( + memory_mapping, + data_addr, + *data_len as u64, + &loader_id, + ), result ); let accounts = question_mark!( @@ -3058,6 +3080,39 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedInnerInstruction<'a, } } +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.invoke_depth() as u64); + } +} + #[cfg(test)] mod tests { #[allow(deprecated)] diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 69d34d8a71bf46..b271d3c2d6ae0e 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -707,14 +707,35 @@ pub fn get_processed_inner_instruction(index: usize) -> Option<(usize, Instructi ) }; - Some((meta.depth, Instruction::new_with_bytes(program_id, &data, accounts))) + Some(( + meta.depth, + Instruction::new_with_bytes(program_id, &data, accounts), + )) } else { None } } #[cfg(not(target_arch = "bpf"))] - crate::program_stubs::get_processed_inner_instruction(index) + crate::program_stubs::sol_get_processed_inner_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] diff --git a/sdk/program/src/program_stubs.rs b/sdk/program/src/program_stubs.rs index 288efe4f2886c2..e9154b3b29a55a 100644 --- a/sdk/program/src/program_stubs.rs +++ b/sdk/program/src/program_stubs.rs @@ -91,9 +91,12 @@ pub trait SyscallStubs: Sync + Send { fn sol_log_data(&self, fields: &[&[u8]]) { println!("data: {}", fields.iter().map(base64::encode).join(" ")); } - fn get_processed_inner_instruction(&self, _index: usize) -> Option<(usize, Instruction)> { + fn sol_get_processed_inner_instruction(&self, _index: usize) -> Option<(usize, Instruction)> { None } + fn sol_get_invoke_depth(&self) -> u64 { + 0 + } } struct DefaultSyscallStubs {} @@ -180,11 +183,15 @@ pub(crate) fn sol_log_data(data: &[&[u8]]) { SYSCALL_STUBS.read().unwrap().sol_log_data(data) } -pub(crate) fn get_processed_inner_instruction(index: usize) -> Option<(usize, Instruction)> { +pub(crate) fn sol_get_processed_inner_instruction(index: usize) -> Option<(usize, Instruction)> { SYSCALL_STUBS .read() .unwrap() - .get_processed_inner_instruction(index) + .sol_get_processed_inner_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. From b1aab0dd71e7d582926a6bbeab7947d305fae5e1 Mon Sep 17 00:00:00 2001 From: Jack May Date: Wed, 19 Jan 2022 08:49:14 -0800 Subject: [PATCH 3/3] rename --- programs/bpf_loader/src/syscalls.rs | 10 +++++++--- sdk/program/src/instruction.rs | 6 +++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index d7cf4eca73958f..a583af0363e10a 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -29,7 +29,7 @@ use { sol_log_data_syscall_enabled, update_syscall_base_costs, }, hash::{Hasher, HASH_BYTES}, - instruction::{AccountMeta, InnerMeta, Instruction, InstructionError}, + instruction::{AccountMeta, Instruction, InstructionError, ProcessedInnerInstruction}, keccak, native_loader, precompiles::is_precompile, program::MAX_RETURN_DATA, @@ -3034,12 +3034,16 @@ impl<'a, 'b> SyscallObject for SyscallGetProcessedInnerInstruction<'a, if let Some((stack_depth, instruction)) = invoke_context.get_processed_inner_instruction(index as usize) { - let InnerMeta { + let ProcessedInnerInstruction { data_len, accounts_len, depth, } = question_mark!( - translate_type_mut::(memory_mapping, meta_addr, &loader_id), + translate_type_mut::( + memory_mapping, + meta_addr, + &loader_id + ), result ); if *data_len >= instruction.data.len() && *accounts_len >= instruction.accounts.len() { diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index b271d3c2d6ae0e..da150b767603d3 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -650,7 +650,7 @@ impl CompiledInstruction { /// when calling the `sol_get_processed_inner_instruction` syscall. #[repr(C)] #[derive(Default, Debug, Clone, Copy)] -pub struct InnerMeta { +pub struct ProcessedInnerInstruction { pub data_len: usize, pub accounts_len: usize, pub depth: usize, @@ -673,14 +673,14 @@ pub fn get_processed_inner_instruction(index: usize) -> Option<(usize, Instructi extern "C" { fn sol_get_processed_inner_instruction( index: u64, - meta: *mut InnerMeta, + meta: *mut ProcessedInnerInstruction, program_id: *mut Pubkey, data: *mut u8, accounts: *mut AccountMeta, ) -> u64; } - let mut meta = InnerMeta::default(); + let mut meta = ProcessedInnerInstruction::default(); let mut program_id = Pubkey::default(); if 1 == unsafe {