diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs new file mode 100644 index 000000000..45e46d732 --- /dev/null +++ b/program-runtime/src/compute_budget.rs @@ -0,0 +1,292 @@ +use { + solana_sdk::{ + borsh::try_from_slice_unchecked, + compute_budget::{self, ComputeBudgetInstruction}, + entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, + feature_set::{requestable_heap_size, FeatureSet}, + instruction::InstructionError, + transaction::{SanitizedTransaction, TransactionError}, + }, + std::sync::Arc, +}; + +const MAX_UNITS: u32 = 1_000_000; +const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; + +#[cfg(RUSTC_WITH_SPECIALIZATION)] +impl ::solana_frozen_abi::abi_example::AbiExample for ComputeBudget { + fn example() -> Self { + // ComputeBudget is not Serialize so just rely on Default. + ComputeBudget::default() + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct ComputeBudget { + /// Number of compute units that an instruction is allowed. Compute units + /// are consumed by program execution, resources they use, etc... + pub max_units: u64, + /// Number of compute units consumed by a log_u64 call + pub log_64_units: u64, + /// Number of compute units consumed by a create_program_address call + pub create_program_address_units: u64, + /// Number of compute units consumed by an invoke call (not including the cost incurred by + /// the called program) + pub invoke_units: u64, + /// Maximum cross-program invocation depth allowed + pub max_invoke_depth: usize, + /// Base number of compute units consumed to call SHA256 + pub sha256_base_cost: u64, + /// Incremental number of units consumed by SHA256 (based on bytes) + pub sha256_byte_cost: u64, + /// Maximum BPF to BPF call depth + pub max_call_depth: usize, + /// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend + pub stack_frame_size: usize, + /// Number of compute units consumed by logging a `Pubkey` + pub log_pubkey_units: u64, + /// Maximum cross-program invocation instruction size + pub max_cpi_instruction_size: usize, + /// Number of account data bytes per conpute unit charged during a cross-program invocation + pub cpi_bytes_per_unit: u64, + /// Base number of compute units consumed to get a sysvar + pub sysvar_base_cost: u64, + /// Number of compute units consumed to call secp256k1_recover + pub secp256k1_recover_cost: u64, + /// Number of compute units consumed to do a syscall without any work + pub syscall_base_cost: u64, + /// Number of compute units consumed to call zktoken_crypto_op + pub zk_token_elgamal_op_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, + /// Memory operation syscall base cost + pub mem_op_base_cost: u64, +} + +impl Default for ComputeBudget { + fn default() -> Self { + Self::new() + } +} + +impl ComputeBudget { + pub fn new() -> Self { + ComputeBudget { + max_units: 200_000, + log_64_units: 100, + create_program_address_units: 1500, + invoke_units: 1000, + max_invoke_depth: 4, + sha256_base_cost: 85, + sha256_byte_cost: 1, + max_call_depth: 64, + stack_frame_size: 4_096, + log_pubkey_units: 100, + max_cpi_instruction_size: 1280, // IPv6 Min MTU size + cpi_bytes_per_unit: 250, // ~50MB at 200,000 units + sysvar_base_cost: 100, + secp256k1_recover_cost: 25_000, + syscall_base_cost: 100, + zk_token_elgamal_op_cost: 25_000, + heap_size: None, + heap_cost: 8, + mem_op_base_cost: 15, + } + } + + pub fn process_transaction( + &mut self, + tx: &SanitizedTransaction, + feature_set: Arc, + ) -> Result<(), TransactionError> { + let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData); + // Compute budget instruction must be in the 1st 3 instructions (avoid + // nonce marker), otherwise ignored + for (program_id, instruction) in tx.message().program_instructions_iter().take(3) { + if compute_budget::check_id(program_id) { + match try_from_slice_unchecked(&instruction.data) { + Ok(ComputeBudgetInstruction::RequestUnits(units)) => { + if units > MAX_UNITS { + return Err(error); + } + self.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); + } + self.heap_size = Some(bytes as usize); + } + _ => return Err(error), + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_sdk::{ + hash::Hash, instruction::Instruction, message::Message, pubkey::Pubkey, + signature::Keypair, signer::Signer, transaction::Transaction, + }, + }; + + macro_rules! test { + ( $instructions: expr, $expected_error: expr, $expected_budget: expr ) => { + let payer_keypair = Keypair::new(); + let tx = SanitizedTransaction::from_transaction_for_tests(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 = ComputeBudget::default(); + let result = compute_budget.process_transaction(&tx, feature_set); + assert_eq!($expected_error as Result<(), TransactionError>, result); + assert_eq!(compute_budget, $expected_budget); + }; + } + + #[test] + fn test_process_transaction() { + // Units + test!(&[], Ok(()), ComputeBudget::default()); + test!( + &[ + ComputeBudgetInstruction::request_units(1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Ok(()), + ComputeBudget { + max_units: 1, + ..ComputeBudget::default() + } + ); + test!( + &[ + ComputeBudgetInstruction::request_units(MAX_UNITS + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ComputeBudgetInstruction::request_units(MAX_UNITS), + ], + Ok(()), + ComputeBudget { + max_units: MAX_UNITS as u64, + ..ComputeBudget::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![]), + ComputeBudgetInstruction::request_units(1), + ], + Ok(()), + ComputeBudget::default() + ); + + // HeapFrame + test!(&[], Ok(()), ComputeBudget::default()); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(40 * 1024), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Ok(()), + ComputeBudget { + heap_size: Some(40 * 1024), + ..ComputeBudget::default() + } + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(31 * 1024), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1), + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ], + Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidInstructionData, + )), + ComputeBudget::default() + ); + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), + ], + Ok(()), + ComputeBudget { + heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), + ..ComputeBudget::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![]), + ComputeBudgetInstruction::request_heap_frame(1), // ignored + ], + Ok(()), + ComputeBudget::default() + ); + + // Combined + test!( + &[ + Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), + ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), + ComputeBudgetInstruction::request_units(MAX_UNITS), + ], + Ok(()), + ComputeBudget { + max_units: MAX_UNITS as u64, + heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), + ..ComputeBudget::default() + } + ); + } +} diff --git a/program-runtime/src/invoke_context.rs b/program-runtime/src/invoke_context.rs index 5bb2cfaf2..6c9c0a1a2 100644 --- a/program-runtime/src/invoke_context.rs +++ b/program-runtime/src/invoke_context.rs @@ -1,6 +1,7 @@ use { crate::{ accounts_data_meter::AccountsDataMeter, + compute_budget::ComputeBudget, ic_logger_msg, ic_msg, instruction_recorder::InstructionRecorder, log_collector::LogCollector, @@ -13,7 +14,6 @@ use { solana_sdk::{ account::{AccountSharedData, ReadableAccount}, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - compute_budget::ComputeBudget, feature_set::{ cap_accounts_data_len, do_support_realloc, neon_evm_compute_budget, reject_empty_instruction_without_program, remove_native_loader, requestable_heap_size, diff --git a/program-runtime/src/lib.rs b/program-runtime/src/lib.rs index e313be69c..db911ab81 100644 --- a/program-runtime/src/lib.rs +++ b/program-runtime/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))] pub mod accounts_data_meter; +pub mod compute_budget; pub mod instruction_recorder; pub mod invoke_context; pub mod log_collector; diff --git a/program-test/src/lib.rs b/program-test/src/lib.rs index aa7beb75a..b1922c778 100644 --- a/program-test/src/lib.rs +++ b/program-test/src/lib.rs @@ -10,7 +10,8 @@ use { solana_banks_client::start_client, solana_banks_server::banks_server::start_local_server, solana_program_runtime::{ - ic_msg, invoke_context::ProcessInstructionWithContext, stable_log, timings::ExecuteTimings, + compute_budget::ComputeBudget, ic_msg, invoke_context::ProcessInstructionWithContext, + stable_log, timings::ExecuteTimings, }, solana_runtime::{ bank::Bank, @@ -23,7 +24,6 @@ use { account::{Account, AccountSharedData, ReadableAccount, WritableAccount}, account_info::AccountInfo, clock::Slot, - compute_budget::ComputeBudget, entrypoint::{ProgramResult, SUCCESS}, fee_calculator::{FeeCalculator, FeeRateGovernor}, genesis_config::{ClusterType, GenesisConfig}, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 908b28a0e..253777887 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -74,6 +74,7 @@ use { solana_measure::measure::Measure, solana_metrics::{inc_new_counter_debug, inc_new_counter_info}, solana_program_runtime::{ + compute_budget::ComputeBudget, instruction_recorder::InstructionRecorder, invoke_context::{ BuiltinProgram, Executor, Executors, ProcessInstructionWithContext, TransactionExecutor, @@ -93,7 +94,6 @@ use { INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, MAX_TRANSACTION_FORWARDING_DELAY, SECONDS_PER_DAY, }, - compute_budget::ComputeBudget, ed25519_program, epoch_info::EpochInfo, epoch_schedule::EpochSchedule, diff --git a/runtime/src/message_processor.rs b/runtime/src/message_processor.rs index c49bbb74c..cb7aee2f5 100644 --- a/runtime/src/message_processor.rs +++ b/runtime/src/message_processor.rs @@ -2,6 +2,7 @@ use { serde::{Deserialize, Serialize}, solana_measure::measure::Measure, solana_program_runtime::{ + compute_budget::ComputeBudget, instruction_recorder::InstructionRecorder, invoke_context::{BuiltinProgram, Executors, InvokeContext}, log_collector::LogCollector, @@ -10,7 +11,6 @@ use { }, solana_sdk::{ account::WritableAccount, - compute_budget::ComputeBudget, feature_set::{prevent_calling_precompiles_as_programs, FeatureSet}, hash::Hash, message::SanitizedMessage, diff --git a/sdk/src/compute_budget.rs b/sdk/src/compute_budget.rs index 595939d9a..0662c83c3 100644 --- a/sdk/src/compute_budget.rs +++ b/sdk/src/compute_budget.rs @@ -1,22 +1,12 @@ #![cfg(feature = "full")] use { - crate::{ - borsh::try_from_slice_unchecked, - entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, - feature_set::{requestable_heap_size, FeatureSet}, - instruction::{Instruction, InstructionError}, - transaction::{SanitizedTransaction, TransactionError}, - }, + crate::instruction::Instruction, borsh::{BorshDeserialize, BorshSchema, BorshSerialize}, - 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( Serialize, @@ -39,11 +29,13 @@ pub enum ComputeBudgetInstruction { /// applies to each program executed, including all calls to CPIs. RequestHeapFrame(u32), } + impl ComputeBudgetInstruction { /// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction` 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( @@ -53,270 +45,3 @@ impl ComputeBudgetInstruction { ) } } - -#[derive(Clone, Copy, Debug, AbiExample, PartialEq)] -pub struct ComputeBudget { - /// Number of compute units that an instruction is allowed. Compute units - /// are consumed by program execution, resources they use, etc... - pub max_units: u64, - /// Number of compute units consumed by a log_u64 call - pub log_64_units: u64, - /// Number of compute units consumed by a create_program_address call - pub create_program_address_units: u64, - /// Number of compute units consumed by an invoke call (not including the cost incurred by - /// the called program) - pub invoke_units: u64, - /// Maximum cross-program invocation depth allowed - pub max_invoke_depth: usize, - /// Base number of compute units consumed to call SHA256 - pub sha256_base_cost: u64, - /// Incremental number of units consumed by SHA256 (based on bytes) - pub sha256_byte_cost: u64, - /// Maximum BPF to BPF call depth - pub max_call_depth: usize, - /// Size of a stack frame in bytes, must match the size specified in the LLVM BPF backend - pub stack_frame_size: usize, - /// Number of compute units consumed by logging a `Pubkey` - pub log_pubkey_units: u64, - /// Maximum cross-program invocation instruction size - pub max_cpi_instruction_size: usize, - /// Number of account data bytes per conpute unit charged during a cross-program invocation - pub cpi_bytes_per_unit: u64, - /// Base number of compute units consumed to get a sysvar - pub sysvar_base_cost: u64, - /// Number of compute units consumed to call secp256k1_recover - pub secp256k1_recover_cost: u64, - /// Number of compute units consumed to do a syscall without any work - pub syscall_base_cost: u64, - /// Number of compute units consumed to call zktoken_crypto_op - pub zk_token_elgamal_op_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, - /// Memory operation syscall base cost - pub mem_op_base_cost: u64, -} -impl Default for ComputeBudget { - fn default() -> Self { - Self::new() - } -} -impl ComputeBudget { - pub fn new() -> Self { - ComputeBudget { - max_units: 200_000, - log_64_units: 100, - create_program_address_units: 1500, - invoke_units: 1000, - max_invoke_depth: 4, - sha256_base_cost: 85, - sha256_byte_cost: 1, - max_call_depth: 64, - stack_frame_size: 4_096, - log_pubkey_units: 100, - max_cpi_instruction_size: 1280, // IPv6 Min MTU size - cpi_bytes_per_unit: 250, // ~50MB at 200,000 units - sysvar_base_cost: 100, - secp256k1_recover_cost: 25_000, - syscall_base_cost: 100, - zk_token_elgamal_op_cost: 25_000, - heap_size: None, - heap_cost: 8, - mem_op_base_cost: 15, - } - } - pub fn process_transaction( - &mut self, - tx: &SanitizedTransaction, - feature_set: Arc, - ) -> Result<(), TransactionError> { - let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData); - // Compute budget instruction must be in the 1st 3 instructions (avoid - // nonce marker), otherwise ignored - for (program_id, instruction) in tx.message().program_instructions_iter().take(3) { - if check_id(program_id) { - match try_from_slice_unchecked(&instruction.data) { - Ok(ComputeBudgetInstruction::RequestUnits(units)) => { - if units > MAX_UNITS { - return Err(error); - } - self.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); - } - self.heap_size = Some(bytes as usize); - } - _ => return Err(error), - } - } - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use { - super::*, - crate::{ - hash::Hash, message::Message, pubkey::Pubkey, signature::Keypair, signer::Signer, - transaction::Transaction, - }, - }; - - macro_rules! test { - ( $instructions: expr, $expected_error: expr, $expected_budget: expr ) => { - let payer_keypair = Keypair::new(); - let tx = SanitizedTransaction::from_transaction_for_tests(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 = ComputeBudget::default(); - let result = compute_budget.process_transaction(&tx, feature_set); - assert_eq!($expected_error as Result<(), TransactionError>, result); - assert_eq!(compute_budget, $expected_budget); - }; - } - - #[test] - fn test_process_transaction() { - // Units - test!(&[], Ok(()), ComputeBudget::default()); - test!( - &[ - ComputeBudgetInstruction::request_units(1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Ok(()), - ComputeBudget { - max_units: 1, - ..ComputeBudget::default() - } - ); - test!( - &[ - ComputeBudgetInstruction::request_units(MAX_UNITS + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )), - ComputeBudget::default() - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ComputeBudgetInstruction::request_units(MAX_UNITS), - ], - Ok(()), - ComputeBudget { - max_units: MAX_UNITS as u64, - ..ComputeBudget::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![]), - ComputeBudgetInstruction::request_units(1), - ], - Ok(()), - ComputeBudget::default() - ); - - // HeapFrame - test!(&[], Ok(()), ComputeBudget::default()); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(40 * 1024), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Ok(()), - ComputeBudget { - heap_size: Some(40 * 1024), - ..ComputeBudget::default() - } - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(40 * 1024 + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )), - ComputeBudget::default() - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(31 * 1024), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )), - ComputeBudget::default() - ); - test!( - &[ - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES + 1), - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ], - Err(TransactionError::InstructionError( - 0, - InstructionError::InvalidInstructionData, - )), - ComputeBudget::default() - ); - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ], - Ok(()), - ComputeBudget { - heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), - ..ComputeBudget::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![]), - ComputeBudgetInstruction::request_heap_frame(1), // ignored - ], - Ok(()), - ComputeBudget::default() - ); - - // Combined - test!( - &[ - Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ComputeBudgetInstruction::request_units(MAX_UNITS), - ], - Ok(()), - ComputeBudget { - max_units: MAX_UNITS as u64, - heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), - ..ComputeBudget::default() - } - ); - } -}