diff --git a/Cargo.lock b/Cargo.lock index 97f3184b877d9c..e15637e7d0cff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4604,9 +4604,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a556eca8a56761a16d712ed3e62a420da220f43749237befac3e4bf820f939c" +checksum = "185f68b54660652e2244bbdef792b369f12045da856a4af75b776e6e72757831" dependencies = [ "byteorder", "combine", diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 05cbdc40d4d904..760b1890101d9a 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1983,9 +1983,9 @@ dependencies = [ [[package]] name = "solana_rbpf" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a556eca8a56761a16d712ed3e62a420da220f43749237befac3e4bf820f939c" +checksum = "185f68b54660652e2244bbdef792b369f12045da856a4af75b776e6e72757831" dependencies = [ "byteorder 1.3.4", "combine", diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index b7b882f8dea314..45bef9ed94eb62 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -26,7 +26,7 @@ solana-bpf-loader-program = { path = "../bpf_loader", version = "1.4.0" } solana-logger = { path = "../../logger", version = "1.4.0" } solana-runtime = { path = "../../runtime", version = "1.4.0" } solana-sdk = { path = "../../sdk", version = "1.4.0" } -solana_rbpf = "=0.1.28" +solana_rbpf = "=0.1.30" [[bench]] name = "bpf_loader" diff --git a/programs/bpf/c/src/invoke/invoke.c b/programs/bpf/c/src/invoke/invoke.c index f99fa5424bfd61..4c67e36a6603e2 100644 --- a/programs/bpf/c/src/invoke/invoke.c +++ b/programs/bpf/c/src/invoke/invoke.c @@ -165,6 +165,33 @@ extern uint64_t entrypoint(const uint8_t *input) { SOL_ARRAY_SIZE(signers_seeds))); } + sol_log("Test multiple derived signers"); + { + SolAccountMeta arguments[] = { + {accounts[DERIVED_KEY1_INDEX].key, true, false}, + {accounts[DERIVED_KEY2_INDEX].key, true, true}, + {accounts[DERIVED_KEY3_INDEX].key, false, true}}; + uint8_t data[] = {TEST_VERIFY_NESTED_SIGNERS}; + const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, + arguments, SOL_ARRAY_SIZE(arguments), + data, SOL_ARRAY_SIZE(data)}; + uint8_t seed1[] = {'L', 'i', 'l', '\''}; + uint8_t seed2[] = {'B', 'i', 't', 's'}; + const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, + {seed2, SOL_ARRAY_SIZE(seed2)}, + {&nonce2, 1}}; + const SolSignerSeed seeds2[] = { + {(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY}, + {&nonce3, 1}}; + const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, + {seeds2, SOL_ARRAY_SIZE(seeds2)}}; + + sol_assert(SUCCESS == sol_invoke_signed(&instruction, accounts, + SOL_ARRAY_SIZE(accounts), + signers_seeds, + SOL_ARRAY_SIZE(signers_seeds))); + } + sol_log("Test readonly with writable account"); { SolAccountMeta arguments[] = { diff --git a/programs/bpf/c/src/invoked/invoked.c b/programs/bpf/c/src/invoked/invoked.c index db6473f826512b..2da8f8c5e71672 100644 --- a/programs/bpf/c/src/invoked/invoked.c +++ b/programs/bpf/c/src/invoked/invoked.c @@ -97,31 +97,6 @@ extern uint64_t entrypoint(const uint8_t *input) { sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer); sol_assert(!accounts[DERIVED_KEY2_INDEX].is_signer); - uint8_t nonce2 = params.data[1]; - uint8_t nonce3 = params.data[2]; - - SolAccountMeta arguments[] = { - {accounts[DERIVED_KEY1_INDEX].key, true, false}, - {accounts[DERIVED_KEY2_INDEX].key, true, true}, - {accounts[DERIVED_KEY3_INDEX].key, false, true}}; - uint8_t data[] = {TEST_VERIFY_NESTED_SIGNERS}; - const SolInstruction instruction = {accounts[INVOKED_PROGRAM_INDEX].key, - arguments, SOL_ARRAY_SIZE(arguments), - data, SOL_ARRAY_SIZE(data)}; - uint8_t seed1[] = {'L', 'i', 'l', '\''}; - uint8_t seed2[] = {'B', 'i', 't', 's'}; - const SolSignerSeed seeds1[] = {{seed1, SOL_ARRAY_SIZE(seed1)}, - {seed2, SOL_ARRAY_SIZE(seed2)}, - {&nonce2, 1}}; - const SolSignerSeed seeds2[] = { - {(uint8_t *)accounts[DERIVED_KEY2_INDEX].key, SIZE_PUBKEY}, - {&nonce3, 1}}; - const SolSignerSeeds signers_seeds[] = {{seeds1, SOL_ARRAY_SIZE(seeds1)}, - {seeds2, SOL_ARRAY_SIZE(seeds2)}}; - - sol_assert(SUCCESS == sol_invoke_signed( - &instruction, accounts, SOL_ARRAY_SIZE(accounts), - signers_seeds, SOL_ARRAY_SIZE(signers_seeds))); break; } diff --git a/programs/bpf/rust/invoke/src/lib.rs b/programs/bpf/rust/invoke/src/lib.rs index e8a22852128869..f402e90684d3c5 100644 --- a/programs/bpf/rust/invoke/src/lib.rs +++ b/programs/bpf/rust/invoke/src/lib.rs @@ -163,6 +163,24 @@ fn process_instruction( accounts, &[&[b"You pass butter", &[nonce1]]], )?; + + let invoked_instruction = create_instruction( + *accounts[INVOKED_PROGRAM_INDEX].key, + &[ + (accounts[DERIVED_KEY1_INDEX].key, true, false), + (accounts[DERIVED_KEY2_INDEX].key, true, true), + (accounts[DERIVED_KEY3_INDEX].key, false, true), + ], + vec![TEST_VERIFY_NESTED_SIGNERS], + ); + invoke_signed( + &invoked_instruction, + accounts, + &[ + &[b"Lil'", b"Bits", &[nonce2]], + &[accounts[DERIVED_KEY2_INDEX].key.as_ref(), &[nonce3]], + ], + )?; } info!("Test readonly with writable account"); @@ -188,8 +206,6 @@ fn process_instruction( &[ (accounts[ARGUMENT_INDEX].key, true, true), (accounts[INVOKED_ARGUMENT_INDEX].key, true, true), - (accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false), - (accounts[INVOKED_PROGRAM_DUP_INDEX].key, false, false), ], vec![TEST_NESTED_INVOKE], ); @@ -197,11 +213,9 @@ fn process_instruction( info!("2nd invoke from first program"); invoke(&instruction, accounts)?; - assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1 + 1 + 1); - assert_eq!( - accounts[INVOKED_ARGUMENT_INDEX].lamports(), - 10 + 5 - 1 - 1 - 1 - 1 - ); + info!(line!(), 0, 0, 0, accounts[ARGUMENT_INDEX].lamports()); + assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42 - 5 + 1 + 1); + assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10 + 5 - 1 - 1); } info!("Verify data values are retained and updated"); diff --git a/programs/bpf/rust/invoked/src/lib.rs b/programs/bpf/rust/invoked/src/lib.rs index b976697a88f9bd..ca88c135ba1e29 100644 --- a/programs/bpf/rust/invoked/src/lib.rs +++ b/programs/bpf/rust/invoked/src/lib.rs @@ -8,13 +8,8 @@ extern crate solana_sdk; use crate::instruction::*; use solana_sdk::{ - account_info::AccountInfo, - bpf_loader, entrypoint, - entrypoint::ProgramResult, - info, - program::{invoke, invoke_signed}, - program_error::ProgramError, - pubkey::Pubkey, + account_info::AccountInfo, bpf_loader, entrypoint, entrypoint::ProgramResult, info, + program::invoke, program_error::ProgramError, pubkey::Pubkey, }; entrypoint!(process_instruction); @@ -111,7 +106,6 @@ fn process_instruction( } TEST_DERIVED_SIGNERS => { info!("verify derived signers"); - const INVOKED_PROGRAM_INDEX: usize = 0; const DERIVED_KEY1_INDEX: usize = 1; const DERIVED_KEY2_INDEX: usize = 2; const DERIVED_KEY3_INDEX: usize = 3; @@ -119,26 +113,6 @@ fn process_instruction( assert!(accounts[DERIVED_KEY1_INDEX].is_signer); assert!(!accounts[DERIVED_KEY2_INDEX].is_signer); assert!(!accounts[DERIVED_KEY3_INDEX].is_signer); - - let nonce2 = instruction_data[1]; - let nonce3 = instruction_data[2]; - let invoked_instruction = create_instruction( - *accounts[INVOKED_PROGRAM_INDEX].key, - &[ - (accounts[DERIVED_KEY1_INDEX].key, true, false), - (accounts[DERIVED_KEY2_INDEX].key, true, true), - (accounts[DERIVED_KEY3_INDEX].key, false, true), - ], - vec![TEST_VERIFY_NESTED_SIGNERS], - ); - invoke_signed( - &invoked_instruction, - accounts, - &[ - &[b"Lil'", b"Bits", &[nonce2]], - &[accounts[DERIVED_KEY2_INDEX].key.as_ref(), &[nonce3]], - ], - )?; } TEST_VERIFY_NESTED_SIGNERS => { info!("verify nested derived signers"); diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 9c70875712ec26..eae89781e3584a 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -360,9 +360,9 @@ fn test_program_bpf_invoke() { let (derived_key1, nonce1) = Pubkey::find_program_address(&[b"You pass butter"], &invoke_program_id); let (derived_key2, nonce2) = - Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoked_program_id); + Pubkey::find_program_address(&[b"Lil'", b"Bits"], &invoke_program_id); let (derived_key3, nonce3) = - Pubkey::find_program_address(&[derived_key2.as_ref()], &invoked_program_id); + Pubkey::find_program_address(&[derived_key2.as_ref()], &invoke_program_id); let mint_pubkey = mint_keypair.pubkey(); let account_metas = vec![ diff --git a/programs/bpf_loader/Cargo.toml b/programs/bpf_loader/Cargo.toml index 7f6585a8998f6a..bf9732ec240d65 100644 --- a/programs/bpf_loader/Cargo.toml +++ b/programs/bpf_loader/Cargo.toml @@ -15,7 +15,7 @@ num-derive = { version = "0.3" } num-traits = { version = "0.2" } solana-runtime = { path = "../../runtime", version = "1.4.0" } solana-sdk = { path = "../../sdk", version = "1.4.0" } -solana_rbpf = "=0.1.28" +solana_rbpf = "=0.1.30" thiserror = "1.0" [dev-dependencies] diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index ca3cc95502f96f..d4367cec26d6bc 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -14,19 +14,20 @@ use num_derive::{FromPrimitive, ToPrimitive}; use solana_rbpf::{ ebpf::{EbpfError, UserDefinedError}, memory_region::MemoryRegion, - EbpfVm, + EbpfVm, InstructionMeter, }; use solana_sdk::{ account::{is_executable, next_keyed_account, KeyedAccount}, bpf_loader, bpf_loader_deprecated, decode_error::DecodeError, entrypoint::SUCCESS, - entrypoint_native::InvokeContext, + entrypoint_native::{ComputeMeter, InvokeContext}, instruction::InstructionError, loader_instruction::LoaderInstruction, program_utils::limited_deserialize, pubkey::Pubkey, }; +use std::{cell::RefCell, rc::Rc}; use thiserror::Error; solana_sdk::declare_builtin!( @@ -65,7 +66,6 @@ pub fn create_vm<'a>( ) -> Result<(EbpfVm<'a, BPFError>, MemoryRegion), EbpfError> { let mut vm = EbpfVm::new(None)?; vm.set_verifier(bpf_verifier::check)?; - vm.set_max_instruction_count(100_000)?; vm.set_elf(&prog)?; let heap_region = syscalls::register_syscalls(&mut vm, parameter_accounts, invoke_context)?; @@ -105,6 +105,20 @@ macro_rules! log{ }; } +struct ThisInstructionMeter { + compute_meter: Rc>, +} +impl InstructionMeter for ThisInstructionMeter { + fn consume(&mut self, amount: u64) { + // 1 to 1 instruction to compute unit mapping + // ignore error, Ebpf will bail if exceeded + let _ = self.compute_meter.borrow_mut().consume(amount); + } + fn get_remaining(&self) -> u64 { + self.compute_meter.borrow().get_remaining() + } +} + pub fn process_instruction( program_id: &Pubkey, keyed_accounts: &[KeyedAccount], @@ -132,6 +146,7 @@ pub fn process_instruction( &instruction_data, )?; { + let compute_meter = invoke_context.get_compute_meter(); let program_account = program.try_account_ref_mut()?; let (mut vm, heap_region) = match create_vm(&program_account.data, ¶meter_accounts, invoke_context) { @@ -143,7 +158,13 @@ pub fn process_instruction( }; log!(logger, "Call BPF program {}", program.unsigned_key()); - match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) { + let instruction_meter = ThisInstructionMeter { compute_meter }; + match vm.execute_program_metered( + parameter_bytes.as_slice(), + &[], + &[heap_region], + instruction_meter, + ) { Ok(status) => { if status != SUCCESS { let error: InstructionError = status.into(); @@ -228,17 +249,57 @@ mod tests { use rand::Rng; use solana_sdk::{ account::Account, - entrypoint_native::{Logger, ProcessInstruction}, + entrypoint_native::{ComputeMeter, Logger, ProcessInstruction}, instruction::CompiledInstruction, message::Message, rent::Rent, }; use std::{cell::RefCell, fs::File, io::Read, ops::Range, rc::Rc}; - #[derive(Debug, Default)] + #[derive(Debug, Default, Clone)] + pub struct MockComputeMeter { + pub remaining: u64, + } + impl ComputeMeter for MockComputeMeter { + fn consume(&mut self, amount: u64) -> Result<(), InstructionError> { + self.remaining = self.remaining.saturating_sub(amount); + if self.remaining == 0 { + return Err(InstructionError::ComputationalBudgetExceeded); + } + Ok(()) + } + fn get_remaining(&self) -> u64 { + self.remaining + } + } + #[derive(Debug, Default, Clone)] + pub struct MockLogger { + pub log: Rc>>, + } + impl Logger for MockLogger { + fn log_enabled(&self) -> bool { + true + } + fn log(&mut self, message: &str) { + self.log.borrow_mut().push(message.to_string()); + } + } + #[derive(Debug)] pub struct MockInvokeContext { - key: Pubkey, - mock_logger: MockLogger, + pub key: Pubkey, + pub logger: MockLogger, + pub compute_meter: MockComputeMeter, + } + impl Default for MockInvokeContext { + fn default() -> Self { + MockInvokeContext { + key: Pubkey::default(), + logger: MockLogger::default(), + compute_meter: MockComputeMeter { + remaining: std::u64::MAX, + }, + } + } } impl InvokeContext for MockInvokeContext { fn push(&mut self, _key: &Pubkey) -> Result<(), InstructionError> { @@ -260,22 +321,25 @@ mod tests { &[] } fn get_logger(&self) -> Rc> { - Rc::new(RefCell::new(self.mock_logger.clone())) + Rc::new(RefCell::new(self.logger.clone())) } fn is_cross_program_supported(&self) -> bool { true } + fn get_compute_meter(&self) -> Rc> { + Rc::new(RefCell::new(self.compute_meter.clone())) + } } - #[derive(Debug, Default, Clone)] - pub struct MockLogger { - pub log: Rc>>, + + struct TestInstructionMeter { + remaining: u64, } - impl Logger for MockLogger { - fn log_enabled(&self) -> bool { - true + impl InstructionMeter for TestInstructionMeter { + fn consume(&mut self, amount: u64) { + self.remaining = self.remaining.saturating_sub(amount); } - fn log(&mut self, message: &str) { - self.log.borrow_mut().push(message.to_string()); + fn get_remaining(&self) -> u64 { + self.remaining } } @@ -292,9 +356,10 @@ mod tests { let mut vm = EbpfVm::::new(None).unwrap(); vm.set_verifier(bpf_verifier::check).unwrap(); - vm.set_max_instruction_count(10).unwrap(); + let instruction_meter = TestInstructionMeter { remaining: 10 }; vm.set_program(program).unwrap(); - vm.execute_program(input, &[], &[]).unwrap(); + vm.execute_program_metered(input, &[], &[], instruction_meter) + .unwrap(); } #[test] diff --git a/programs/bpf_loader/src/syscalls.rs b/programs/bpf_loader/src/syscalls.rs index 9bf871ae915c8b..dc44732eaae91f 100644 --- a/programs/bpf_loader/src/syscalls.rs +++ b/programs/bpf_loader/src/syscalls.rs @@ -12,7 +12,7 @@ use solana_sdk::{ account_info::AccountInfo, bpf_loader, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, - entrypoint_native::{InvokeContext, Logger}, + entrypoint_native::{ComputeMeter, InvokeContext, Logger}, instruction::{AccountMeta, Instruction, InstructionError}, message::Message, program_error::ProgramError, @@ -59,6 +59,26 @@ impl From for EbpfError { } } +/// Sysval compute costs +/// Note: `abort`, `sol_panic_`, and `sol_alloc_free_` do not currently incur a cost +const COMPUTE_COST_LOG: u64 = 100; +const COMPUTE_COST_LOG_64: u64 = 100; +const COMPUTE_COST_CREATE_PROGRAM_ADDRESS: u64 = 1000; +const COMPUTE_COST_INVOKE: u64 = 1000; + +trait SyscallConsume { + fn consume(&mut self, amount: u64) -> Result<(), EbpfError>; +} +impl SyscallConsume for Rc> { + fn consume(&mut self, amount: u64) -> Result<(), EbpfError> { + self.try_borrow_mut() + .map_err(|_| SyscallError::InvokeContextBorrowFailed)? + .consume(amount) + .map_err(SyscallError::InstructionError)?; + Ok(()) + } +} + /// Program heap allocators are intended to allocate/free from a given /// chunk of memory. The specific allocator implementation is /// selectable at build-time. @@ -76,24 +96,31 @@ pub fn register_syscalls<'a>( callers_keyed_accounts: &'a [KeyedAccount<'a>], invoke_context: &'a mut dyn InvokeContext, ) -> Result> { - // Syscall function common across languages + // Syscall functions common across languages vm.register_syscall_ex("abort", syscall_abort)?; vm.register_syscall_ex("sol_panic_", syscall_sol_panic)?; vm.register_syscall_with_context_ex( "sol_log_", Box::new(SyscallLog { + compute_meter: invoke_context.get_compute_meter(), logger: invoke_context.get_logger(), }), )?; vm.register_syscall_with_context_ex( "sol_log_64_", Box::new(SyscallLogU64 { + compute_meter: invoke_context.get_compute_meter(), logger: invoke_context.get_logger(), }), )?; if invoke_context.is_cross_program_supported() { - vm.register_syscall_ex("sol_create_program_address", syscall_create_program_address)?; + vm.register_syscall_with_context_ex( + "sol_create_program_address", + Box::new(SyscallCreateProgramAddress { + compute_meter: invoke_context.get_compute_meter(), + }), + )?; // Cross-program invocation syscalls @@ -253,6 +280,7 @@ pub fn syscall_sol_panic( /// Log a user's info message pub struct SyscallLog { + compute_meter: Rc>, logger: Rc>, } impl SyscallObject for SyscallLog { @@ -266,6 +294,7 @@ impl SyscallObject for SyscallLog { ro_regions: &[MemoryRegion], _rw_regions: &[MemoryRegion], ) -> Result> { + self.compute_meter.consume(COMPUTE_COST_LOG)?; let mut logger = self .logger .try_borrow_mut() @@ -282,6 +311,7 @@ impl SyscallObject for SyscallLog { /// Log 5 64-bit values pub struct SyscallLogU64 { + compute_meter: Rc>, logger: Rc>, } impl SyscallObject for SyscallLogU64 { @@ -295,6 +325,7 @@ impl SyscallObject for SyscallLogU64 { _ro_regions: &[MemoryRegion], _rw_regions: &[MemoryRegion], ) -> Result> { + self.compute_meter.consume(COMPUTE_COST_LOG_64)?; let mut logger = self .logger .try_borrow_mut() @@ -346,38 +377,47 @@ impl SyscallObject for SyscallSolAllocFree { } /// Create a program address -pub fn syscall_create_program_address( - seeds_addr: u64, - seeds_len: u64, - program_id_addr: u64, - address_addr: u64, - _arg5: u64, - ro_regions: &[MemoryRegion], - rw_regions: &[MemoryRegion], -) -> Result> { - let untranslated_seeds = translate_slice!(&[&u8], seeds_addr, seeds_len, ro_regions)?; +pub struct SyscallCreateProgramAddress { + compute_meter: Rc>, +} +impl SyscallObject for SyscallCreateProgramAddress { + fn call( + &mut self, + seeds_addr: u64, + seeds_len: u64, + program_id_addr: u64, + address_addr: u64, + _arg5: u64, + ro_regions: &[MemoryRegion], + rw_regions: &[MemoryRegion], + ) -> Result> { + self.compute_meter + .consume(COMPUTE_COST_CREATE_PROGRAM_ADDRESS)?; - let seeds = untranslated_seeds - .iter() - .map(|untranslated_seed| { - translate_slice!( - u8, - untranslated_seed.as_ptr(), - untranslated_seed.len(), - ro_regions - ) - }) - .collect::, EbpfError>>()?; - let program_id = translate_type!(Pubkey, program_id_addr, ro_regions)?; + let untranslated_seeds = translate_slice!(&[&u8], seeds_addr, seeds_len, ro_regions)?; + let seeds = untranslated_seeds + .iter() + .map(|untranslated_seed| { + translate_slice!( + u8, + untranslated_seed.as_ptr(), + untranslated_seed.len(), + ro_regions + ) + }) + .collect::, EbpfError>>()?; + let program_id = translate_type!(Pubkey, program_id_addr, ro_regions)?; - let new_address = - match Pubkey::create_program_address(&seeds, program_id).map_err(SyscallError::BadSeeds) { + let new_address = match Pubkey::create_program_address(&seeds, program_id) + .map_err(SyscallError::BadSeeds) + { Ok(address) => address, Err(_) => return Ok(1), }; - let address = translate_slice_mut!(u8, address_addr, 32, rw_regions)?; - address.copy_from_slice(new_address.as_ref()); - Ok(0) + let address = translate_slice_mut!(u8, address_addr, 32, rw_regions)?; + address.copy_from_slice(new_address.as_ref()); + Ok(0) + } } // Cross-program invocation syscalls @@ -844,6 +884,9 @@ fn call<'a>( rw_regions: &[MemoryRegion], ) -> Result> { let mut invoke_context = syscall.get_context_mut()?; + invoke_context + .get_compute_meter() + .consume(COMPUTE_COST_INVOKE)?; // Translate data passed from the VM @@ -929,7 +972,7 @@ fn call<'a>( #[cfg(test)] mod tests { use super::*; - use crate::tests::MockLogger; + use crate::tests::{MockComputeMeter, MockLogger}; #[test] fn test_translate() { @@ -1080,6 +1123,18 @@ mod tests { fn test_syscall_sol_log() { let string = "Gaggablaghblagh!"; let addr = string.as_ptr() as *const _ as u64; + + let compute_meter: Rc> = + Rc::new(RefCell::new(MockComputeMeter { + remaining: std::u64::MAX, // TODO also test error + })); + let log = Rc::new(RefCell::new(vec![])); + let logger: Rc> = + Rc::new(RefCell::new(MockLogger { log: log.clone() })); + let mut syscall_sol_log = SyscallLog { + compute_meter, + logger, + }; let ro_regions = &[MemoryRegion { addr_host: addr, addr_vm: 100, @@ -1087,11 +1142,6 @@ mod tests { }]; let rw_regions = &[MemoryRegion::default()]; - let log = Rc::new(RefCell::new(vec![])); - let mock_logger = MockLogger { log: log.clone() }; - let logger: Rc> = Rc::new(RefCell::new(mock_logger)); - let mut syscall_sol_log = SyscallLog { logger }; - syscall_sol_log .call(100, string.len() as u64, 0, 0, 0, ro_regions, rw_regions) .unwrap(); @@ -1114,10 +1164,16 @@ mod tests { #[test] fn test_syscall_sol_log_u64() { + let compute_meter: Rc> = + Rc::new(RefCell::new(MockComputeMeter { + remaining: std::u64::MAX, // TODO also test error + })); let log = Rc::new(RefCell::new(vec![])); - let mock_logger = MockLogger { log: log.clone() }; + let logger: Rc> = + Rc::new(RefCell::new(MockLogger { log: log.clone() })); let mut syscall_sol_log_u64 = SyscallLogU64 { - logger: Rc::new(RefCell::new(mock_logger)), + compute_meter, + logger, }; let ro_regions = &[MemoryRegion::default()]; let rw_regions = &[MemoryRegion::default()]; diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 9d5d195d2b06b6..428738f50c7b46 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize}; use solana_sdk::{ account::{create_keyed_readonly_accounts, Account, KeyedAccount}, clock::Epoch, - entrypoint_native::{InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext}, + entrypoint_native::{ + ComputeMeter, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext, + }, instruction::{CompiledInstruction, InstructionError}, message::Message, native_loader, @@ -17,6 +19,10 @@ use solana_sdk::{ }; use std::{cell::RefCell, rc::Rc}; +// Number of compute units that an instruction is allowed. Compute units +// are consumed by program execution, resources they use, etc... +const COMPUTE_BUDGET: u64 = 100_000; + // The relevant state of an account before an Instruction executes, used // to verify account integrity after the Instruction completes #[derive(Clone, Debug, Default)] @@ -155,6 +161,21 @@ impl PreAccount { } } +pub struct ThisComputeMeter { + remaining: u64, +} +impl ComputeMeter for ThisComputeMeter { + fn consume(&mut self, amount: u64) -> Result<(), InstructionError> { + self.remaining = self.remaining.saturating_sub(amount); + if self.remaining == 0 { + return Err(InstructionError::ComputationalBudgetExceeded); + } + Ok(()) + } + fn get_remaining(&self) -> u64 { + self.remaining + } +} pub struct ThisInvokeContext { program_ids: Vec, rent: Rent, @@ -162,9 +183,10 @@ pub struct ThisInvokeContext { programs: Vec<(Pubkey, ProcessInstruction)>, logger: Rc>, is_cross_program_supported: bool, + compute_meter: Rc>, } impl ThisInvokeContext { - const MAX_INVOCATION_DEPTH: usize = 5; + const MAX_INVOCATION_DEPTH: usize = 2; pub fn new( program_id: &Pubkey, rent: Rent, @@ -172,6 +194,7 @@ impl ThisInvokeContext { programs: Vec<(Pubkey, ProcessInstruction)>, log_collector: Option>, is_cross_program_supported: bool, + compute_budget: u64, ) -> Self { let mut program_ids = Vec::with_capacity(Self::MAX_INVOCATION_DEPTH); program_ids.push(*program_id); @@ -182,6 +205,9 @@ impl ThisInvokeContext { programs, logger: Rc::new(RefCell::new(ThisLogger { log_collector })), is_cross_program_supported, + compute_meter: Rc::new(RefCell::new(ThisComputeMeter { + remaining: compute_budget, + })), } } } @@ -232,6 +258,9 @@ impl InvokeContext for ThisInvokeContext { fn is_cross_program_supported(&self) -> bool { self.is_cross_program_supported } + fn get_compute_meter(&self) -> Rc> { + self.compute_meter.clone() + } } pub struct ThisLogger { log_collector: Option>, @@ -560,6 +589,7 @@ impl MessageProcessor { self.programs.clone(), // get rid of clone log_collector, self.is_cross_program_supported, + COMPUTE_BUDGET, ); let keyed_accounts = Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; @@ -639,6 +669,7 @@ mod tests { vec![], None, true, + u64::MAX, ); // Check call depth increases and has a limit @@ -1409,6 +1440,7 @@ mod tests { vec![], None, true, + u64::MAX, ); let metas = vec![ AccountMeta::new(owned_key, false), diff --git a/sdk/src/entrypoint_native.rs b/sdk/src/entrypoint_native.rs index 6b17a277e1a40d..d629ff3ad1eb45 100644 --- a/sdk/src/entrypoint_native.rs +++ b/sdk/src/entrypoint_native.rs @@ -200,6 +200,16 @@ pub trait InvokeContext { fn get_logger(&self) -> Rc>; /// Are cross program invocations supported fn is_cross_program_supported(&self) -> bool; + /// Get this invocation's compute meter + fn get_compute_meter(&self) -> Rc>; +} + +/// Compute meter +pub trait ComputeMeter { + /// Consume compute units + fn consume(&mut self, amount: u64) -> Result<(), InstructionError>; + /// Get the number of remaining compute units + fn get_remaining(&self) -> u64; } /// Log messages diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index d4d7a301fbf15e..ccd35d60e57334 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -160,9 +160,13 @@ pub enum InstructionError { #[error("Provided seeds do not result in a valid address")] InvalidSeeds, - // Failed to reallocate account data of this length + /// Failed to reallocate account data of this length #[error("Failed to reallocate account data")] InvalidRealloc, + + /// Computational budget exceeded + #[error("Computational budget exceeded")] + ComputationalBudgetExceeded, } #[derive(Debug, PartialEq, Clone)]