diff --git a/programs/bpf/benches/bpf_loader.rs b/programs/bpf/benches/bpf_loader.rs index 5a79157add..0ca7dea17e 100644 --- a/programs/bpf/benches/bpf_loader.rs +++ b/programs/bpf/benches/bpf_loader.rs @@ -193,6 +193,54 @@ fn bench_program_execute_noop(bencher: &mut Bencher) { }); } +#[bench] +fn bench_create_vm(bencher: &mut Bencher) { + const BUDGET: u64 = 200_000; + let loader_id = bpf_loader::id(); + + let accounts = [RefCell::new(AccountSharedData::new( + 1, + 10000001, + &solana_sdk::pubkey::new_rand(), + ))]; + let keys = [solana_sdk::pubkey::new_rand()]; + let keyed_accounts: Vec<_> = keys + .iter() + .zip(&accounts) + .map(|(key, account)| solana_sdk::keyed_account::KeyedAccount::new(&key, false, &account)) + .collect(); + let instruction_data = vec![0u8]; + + let mut invoke_context = MockInvokeContext::new(keyed_accounts); + invoke_context.compute_meter.remaining = BUDGET; + + // Serialize account data + let keyed_accounts = invoke_context.get_keyed_accounts().unwrap(); + let mut serialized = serialize_parameters( + &bpf_loader::id(), + &solana_sdk::pubkey::new_rand(), + keyed_accounts, + &instruction_data, + ) + .unwrap(); + + let elf = load_elf("noop").unwrap(); + let mut executable = + >::from_elf(&elf, None, Config::default()) + .unwrap(); + executable.set_syscall_registry(register_syscalls(&mut invoke_context).unwrap()); + + bencher.iter(|| { + let _ = create_vm( + &loader_id, + executable.as_ref(), + serialized.as_slice_mut(), + &mut invoke_context, + ) + .unwrap(); + }); +} + #[bench] fn bench_instruction_count_tuner(_bencher: &mut Bencher) { const BUDGET: u64 = 200_000; diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 537eb83b21..3352c9d0f9 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -34,7 +34,7 @@ use solana_sdk::{ entrypoint::{HEAP_LENGTH, SUCCESS}, feature_set::{ add_missing_program_error_mappings, close_upgradeable_program_accounts, fix_write_privs, - reduce_required_deploy_balance, upgradeable_close_instruction, + reduce_required_deploy_balance, requestable_heap_size, upgradeable_close_instruction, }, ic_logger_msg, ic_msg, instruction::{AccountMeta, InstructionError}, @@ -150,6 +150,12 @@ pub fn create_vm<'a>( invoke_context: &'a mut dyn InvokeContext, ) -> Result, EbpfError> { let bpf_compute_budget = invoke_context.get_bpf_compute_budget(); + let heap_size = bpf_compute_budget.heap_size.unwrap_or(HEAP_LENGTH); + if invoke_context.is_feature_active(&requestable_heap_size::id()) { + let _ = invoke_context.get_compute_meter().borrow_mut().consume( + (heap_size as u64 / (32 * 1024)).saturating_sub(1) * bpf_compute_budget.heap_cost, + ); + } let heap = AlignedMemory::new_with_size( bpf_compute_budget.heap_size.unwrap_or(HEAP_LENGTH), HOST_ALIGN, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 59e6343b67..6b91d78595 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3677,7 +3677,11 @@ impl Bank { .unwrap_or_else(BpfComputeBudget::new); let mut process_result = if feature_set.is_active(&tx_wide_compute_cap::id()) { - compute_budget::process_request(&mut bpf_compute_budget, tx) + compute_budget::process_request( + &mut bpf_compute_budget, + tx, + feature_set.clone(), + ) } else { Ok(()) }; @@ -14253,6 +14257,7 @@ pub(crate) mod tests { *compute_budget, BpfComputeBudget { max_units: 1, + heap_size: Some(48 * 1024), ..BpfComputeBudget::default() } ); @@ -14264,6 +14269,7 @@ pub(crate) mod tests { let message = Message::new( &[ compute_budget::request_units(1), + compute_budget::request_heap_frame(48 * 1024), Instruction::new_with_bincode(program_id, &0, vec![]), ], Some(&mint_keypair.pubkey()), diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index 9316f8ace7..9984e8912f 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -11,8 +11,8 @@ use solana_sdk::{ bpf_loader_upgradeable::{self, UpgradeableLoaderState}, feature_set::{ demote_program_write_locks, fix_write_privs, instructions_sysvar_enabled, - neon_evm_compute_budget, remove_native_loader, tx_wide_compute_cap, updated_verify_policy, - FeatureSet, + neon_evm_compute_budget, remove_native_loader, requestable_heap_size, tx_wide_compute_cap, + updated_verify_policy, FeatureSet, }, ic_logger_msg, ic_msg, instruction::{CompiledInstruction, Instruction, InstructionError}, @@ -1227,11 +1227,17 @@ impl MessageProcessor { let program_id = instruction.program_id(&message.account_keys); let mut bpf_compute_budget = bpf_compute_budget; - if feature_set.is_active(&neon_evm_compute_budget::id()) + if !feature_set.is_active(&tx_wide_compute_cap::id()) + && feature_set.is_active(&neon_evm_compute_budget::id()) && *program_id == crate::neon_evm_program::id() { // Bump the compute budget for neon_evm bpf_compute_budget.max_units = bpf_compute_budget.max_units.max(500_000); + } + if !feature_set.is_active(&requestable_heap_size::id()) + && feature_set.is_active(&neon_evm_compute_budget::id()) + && *program_id == crate::neon_evm_program::id() + { bpf_compute_budget.heap_size = Some(256 * 1024); } diff --git a/sdk/src/compute_budget.rs b/sdk/src/compute_budget.rs index e90ae3d7f8..f1cdd84bc4 100644 --- a/sdk/src/compute_budget.rs +++ b/sdk/src/compute_budget.rs @@ -1,6 +1,8 @@ #![cfg(feature = "full")] use crate::{ + entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, + feature_set::{requestable_heap_size, FeatureSet}, process_instruction::BpfComputeBudget, transaction::{Transaction, TransactionError}, }; @@ -9,10 +11,12 @@ use solana_sdk::{ borsh::try_from_slice_unchecked, instruction::{Instruction, InstructionError}, }; +use std::sync::Arc; crate::declare_id!("ComputeBudget111111111111111111111111111111"); const MAX_UNITS: u32 = 1_000_000; +const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; /// Compute Budget Instructions #[derive( @@ -31,6 +35,10 @@ pub enum ComputeBudgetInstruction { /// Request a specific maximum number of compute units the transaction is /// allowed to consume. RequestUnits(u32), + /// Request a specific transaction-wide program heap frame size in bytes. + /// The value requested must be a multiple of 1024. This new heap frame size + /// applies to each program executed, including all calls to CPIs. + RequestHeapFrame(u32), } /// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction` @@ -38,21 +46,44 @@ pub fn request_units(units: u32) -> Instruction { Instruction::new_with_borsh(id(), &ComputeBudgetInstruction::RequestUnits(units), vec![]) } +/// Create a `ComputeBudgetInstruction::RequestHeapFrame` `Instruction` +pub fn request_heap_frame(bytes: u32) -> Instruction { + Instruction::new_with_borsh( + id(), + &ComputeBudgetInstruction::RequestHeapFrame(bytes), + vec![], + ) +} + pub fn process_request( compute_budget: &mut BpfComputeBudget, tx: &Transaction, + feature_set: Arc, ) -> Result<(), TransactionError> { let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData); - // Compute budget instruction must be in 1st or 2nd instruction (avoid nonce marker) - for instruction in tx.message().instructions.iter().take(2) { + // Compute budget instruction must be in the 1st 3 instructions (avoid + // nonce marker), otherwise ignored + for instruction in tx.message().instructions.iter().take(3) { if check_id(instruction.program_id(&tx.message().account_keys)) { - let ComputeBudgetInstruction::RequestUnits(units) = - try_from_slice_unchecked::(&instruction.data) - .map_err(|_| error.clone())?; - if units > MAX_UNITS { - return Err(error); + match try_from_slice_unchecked(&instruction.data) { + Ok(ComputeBudgetInstruction::RequestUnits(units)) => { + if units > MAX_UNITS { + return Err(error); + } + compute_budget.max_units = units as u64; + } + Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => { + if !feature_set.is_active(&requestable_heap_size::id()) + || bytes > MAX_HEAP_FRAME_BYTES + || bytes < MIN_HEAP_FRAME_BYTES as u32 + || bytes % 1024 != 0 + { + return Err(error); + } + compute_budget.heap_size = Some(bytes as usize); + } + _ => return Err(error), } - compute_budget.max_units = units as u64; } } Ok(()) @@ -61,82 +92,153 @@ pub fn process_request( #[cfg(test)] mod tests { use super::*; - use crate::{ - compute_budget, hash::Hash, message::Message, pubkey::Pubkey, signature::Keypair, - signer::Signer, - }; + use crate::{hash::Hash, message::Message, pubkey::Pubkey, signature::Keypair, signer::Signer}; + + macro_rules! test { + ( $instructions: expr, $expected_error: expr, $expected_budget: expr ) => { + let payer_keypair = Keypair::new(); + let tx = Transaction::new( + &[&payer_keypair], + Message::new($instructions, Some(&payer_keypair.pubkey())), + Hash::default(), + ); + let feature_set = Arc::new(FeatureSet::all_enabled()); + let mut compute_budget = BpfComputeBudget::default(); + let result = process_request(&mut compute_budget, &tx, feature_set); + assert_eq!($expected_error as Result<(), TransactionError>, result); + assert_eq!(compute_budget, $expected_budget); + }; + } #[test] fn test_process_request() { - let payer_keypair = Keypair::new(); - let mut compute_budget = BpfComputeBudget::default(); - - let tx = Transaction::new( - &[&payer_keypair], - Message::new(&[], Some(&payer_keypair.pubkey())), - Hash::default(), - ); - process_request(&mut compute_budget, &tx).unwrap(); - assert_eq!(compute_budget, BpfComputeBudget::default()); - - let tx = Transaction::new( - &[&payer_keypair], - Message::new( - &[ - compute_budget::request_units(1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Some(&payer_keypair.pubkey()), - ), - Hash::default(), - ); - process_request(&mut compute_budget, &tx).unwrap(); - assert_eq!( - compute_budget, + // Units + test!(&[], Ok(()), BpfComputeBudget::default()); + test!( + &[ + request_units(1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Ok(()), BpfComputeBudget { max_units: 1, ..BpfComputeBudget::default() } ); - - let tx = Transaction::new( - &[&payer_keypair], - Message::new( - &[ - compute_budget::request_units(MAX_UNITS + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Some(&payer_keypair.pubkey()), - ), - Hash::default(), - ); - let result = process_request(&mut compute_budget, &tx); - assert_eq!( - result, + test!( + &[ + request_units(MAX_UNITS + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], Err(TransactionError::InstructionError( 0, - InstructionError::InvalidInstructionData - )) + InstructionError::InvalidInstructionData, + )), + BpfComputeBudget::default() ); - - let tx = Transaction::new( - &[&payer_keypair], - Message::new( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - compute_budget::request_units(MAX_UNITS), - ], - Some(&payer_keypair.pubkey()), - ), - Hash::default(), - ); - process_request(&mut compute_budget, &tx).unwrap(); - assert_eq!( - compute_budget, + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + request_units(MAX_UNITS), + ], + Ok(()), BpfComputeBudget { max_units: MAX_UNITS as u64, ..BpfComputeBudget::default() } ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + request_units(1), + ], + Ok(()), + BpfComputeBudget::default() + ); + + // HeapFrame + test!(&[], Ok(()), BpfComputeBudget::default()); + test!( + &[ + request_heap_frame(40 * 1024), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Ok(()), + BpfComputeBudget { + heap_size: Some(40 * 1024), + ..BpfComputeBudget::default() + } + ); + test!( + &[ + request_heap_frame(40 * 1024 + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + BpfComputeBudget::default() + ); + test!( + &[ + request_heap_frame(31 * 1024), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + BpfComputeBudget::default() + ); + test!( + &[ + request_heap_frame(MAX_HEAP_FRAME_BYTES + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + BpfComputeBudget::default() + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + request_heap_frame(MAX_HEAP_FRAME_BYTES), + ], + Ok(()), + BpfComputeBudget { + heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), + ..BpfComputeBudget::default() + } + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + request_heap_frame(1), // ignored + ], + Ok(()), + BpfComputeBudget::default() + ); + + // Combined + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + request_heap_frame(MAX_HEAP_FRAME_BYTES), + request_units(MAX_UNITS), + ], + Ok(()), + BpfComputeBudget { + max_units: MAX_UNITS as u64, + heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), + ..BpfComputeBudget::default() + } + ); } } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 8379331492..a0fba52c9e 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -243,6 +243,10 @@ pub mod ed25519_program_enabled { solana_sdk::declare_id!("E1TvTNipX8TKNHrhRC8SMuAwQmGY58TZ4drdztP3Gxwc"); } +pub mod requestable_heap_size { + solana_sdk::declare_id!("CCu4boMmfLuqcmfTLPHQiUo22ZdUsXjgzPAURYaWt1Bw"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -304,6 +308,7 @@ lazy_static! { (return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall"), (sol_log_data_syscall_enabled::id(), "enable sol_log_data syscall"), (ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"), + (requestable_heap_size::id(), "Requestable heap frame size"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter() diff --git a/sdk/src/process_instruction.rs b/sdk/src/process_instruction.rs index 75a467f620..98859439aa 100644 --- a/sdk/src/process_instruction.rs +++ b/sdk/src/process_instruction.rs @@ -190,6 +190,9 @@ pub struct BpfComputeBudget { pub syscall_base_cost: u64, /// Optional program heap region size, if `None` then loader default pub heap_size: Option, + /// Number of compute units per additional 32k heap above the default (~.5 + /// us per 32k at 15 units/us rounded up) + pub heap_cost: u64, } impl Default for BpfComputeBudget { @@ -217,6 +220,7 @@ impl BpfComputeBudget { syscall_base_cost: 100, secp256k1_recover_cost: 25_000, heap_size: None, + heap_cost: 8, } } }