Refactor: move compute budget runtime logic into solana-program-runtime (#22543)
This commit is contained in:
parent
5e5cdf397c
commit
cc76a73c49
|
@ -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<usize>,
|
||||||
|
/// 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<FeatureSet>,
|
||||||
|
) -> 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()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
accounts_data_meter::AccountsDataMeter,
|
accounts_data_meter::AccountsDataMeter,
|
||||||
|
compute_budget::ComputeBudget,
|
||||||
ic_logger_msg, ic_msg,
|
ic_logger_msg, ic_msg,
|
||||||
instruction_recorder::InstructionRecorder,
|
instruction_recorder::InstructionRecorder,
|
||||||
log_collector::LogCollector,
|
log_collector::LogCollector,
|
||||||
|
@ -13,7 +14,6 @@ use {
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::{AccountSharedData, ReadableAccount},
|
account::{AccountSharedData, ReadableAccount},
|
||||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||||
compute_budget::ComputeBudget,
|
|
||||||
feature_set::{
|
feature_set::{
|
||||||
cap_accounts_data_len, do_support_realloc, neon_evm_compute_budget,
|
cap_accounts_data_len, do_support_realloc, neon_evm_compute_budget,
|
||||||
reject_empty_instruction_without_program, remove_native_loader, requestable_heap_size,
|
reject_empty_instruction_without_program, remove_native_loader, requestable_heap_size,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
#![cfg_attr(RUSTC_WITH_SPECIALIZATION, feature(min_specialization))]
|
||||||
|
|
||||||
pub mod accounts_data_meter;
|
pub mod accounts_data_meter;
|
||||||
|
pub mod compute_budget;
|
||||||
pub mod instruction_recorder;
|
pub mod instruction_recorder;
|
||||||
pub mod invoke_context;
|
pub mod invoke_context;
|
||||||
pub mod log_collector;
|
pub mod log_collector;
|
||||||
|
|
|
@ -10,7 +10,8 @@ use {
|
||||||
solana_banks_client::start_client,
|
solana_banks_client::start_client,
|
||||||
solana_banks_server::banks_server::start_local_server,
|
solana_banks_server::banks_server::start_local_server,
|
||||||
solana_program_runtime::{
|
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::{
|
solana_runtime::{
|
||||||
bank::Bank,
|
bank::Bank,
|
||||||
|
@ -23,7 +24,6 @@ use {
|
||||||
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
|
account::{Account, AccountSharedData, ReadableAccount, WritableAccount},
|
||||||
account_info::AccountInfo,
|
account_info::AccountInfo,
|
||||||
clock::Slot,
|
clock::Slot,
|
||||||
compute_budget::ComputeBudget,
|
|
||||||
entrypoint::{ProgramResult, SUCCESS},
|
entrypoint::{ProgramResult, SUCCESS},
|
||||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||||
genesis_config::{ClusterType, GenesisConfig},
|
genesis_config::{ClusterType, GenesisConfig},
|
||||||
|
|
|
@ -17,7 +17,10 @@ use solana_bpf_loader_program::{
|
||||||
use solana_bpf_rust_invoke::instructions::*;
|
use solana_bpf_rust_invoke::instructions::*;
|
||||||
use solana_bpf_rust_realloc::instructions::*;
|
use solana_bpf_rust_realloc::instructions::*;
|
||||||
use solana_bpf_rust_realloc_invoke::instructions::*;
|
use solana_bpf_rust_realloc_invoke::instructions::*;
|
||||||
use solana_program_runtime::{invoke_context::with_mock_invoke_context, timings::ExecuteTimings};
|
use solana_program_runtime::{
|
||||||
|
compute_budget::ComputeBudget, invoke_context::with_mock_invoke_context,
|
||||||
|
timings::ExecuteTimings,
|
||||||
|
};
|
||||||
use solana_rbpf::{
|
use solana_rbpf::{
|
||||||
elf::Executable,
|
elf::Executable,
|
||||||
static_analysis::Analysis,
|
static_analysis::Analysis,
|
||||||
|
@ -41,7 +44,7 @@ use solana_sdk::{
|
||||||
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
||||||
client::SyncClient,
|
client::SyncClient,
|
||||||
clock::MAX_PROCESSING_AGE,
|
clock::MAX_PROCESSING_AGE,
|
||||||
compute_budget::{ComputeBudget, ComputeBudgetInstruction},
|
compute_budget::ComputeBudgetInstruction,
|
||||||
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
||||||
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
||||||
loader_instruction,
|
loader_instruction,
|
||||||
|
@ -226,13 +229,16 @@ fn run_program(name: &str) -> u64 {
|
||||||
let mut instruction_count = 0;
|
let mut instruction_count = 0;
|
||||||
let mut tracer = None;
|
let mut tracer = None;
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
invoke_context.transaction_context.set_return_data(
|
invoke_context
|
||||||
*invoke_context
|
.transaction_context
|
||||||
.transaction_context
|
.set_return_data(
|
||||||
.get_program_key()
|
*invoke_context
|
||||||
.unwrap(),
|
.transaction_context
|
||||||
Vec::new(),
|
.get_program_key()
|
||||||
).unwrap();
|
.unwrap(),
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let mut parameter_bytes = parameter_bytes.clone();
|
let mut parameter_bytes = parameter_bytes.clone();
|
||||||
{
|
{
|
||||||
let mut vm = create_vm(
|
let mut vm = create_vm(
|
||||||
|
|
|
@ -74,6 +74,7 @@ use {
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
solana_metrics::{inc_new_counter_debug, inc_new_counter_info},
|
solana_metrics::{inc_new_counter_debug, inc_new_counter_info},
|
||||||
solana_program_runtime::{
|
solana_program_runtime::{
|
||||||
|
compute_budget::ComputeBudget,
|
||||||
instruction_recorder::InstructionRecorder,
|
instruction_recorder::InstructionRecorder,
|
||||||
invoke_context::{
|
invoke_context::{
|
||||||
BuiltinProgram, Executor, Executors, ProcessInstructionWithContext, TransactionExecutor,
|
BuiltinProgram, Executor, Executors, ProcessInstructionWithContext, TransactionExecutor,
|
||||||
|
@ -93,7 +94,6 @@ use {
|
||||||
INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES,
|
INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES,
|
||||||
MAX_TRANSACTION_FORWARDING_DELAY, SECONDS_PER_DAY,
|
MAX_TRANSACTION_FORWARDING_DELAY, SECONDS_PER_DAY,
|
||||||
},
|
},
|
||||||
compute_budget::ComputeBudget,
|
|
||||||
ed25519_program,
|
ed25519_program,
|
||||||
epoch_info::EpochInfo,
|
epoch_info::EpochInfo,
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use {
|
||||||
serde::{Deserialize, Serialize},
|
serde::{Deserialize, Serialize},
|
||||||
solana_measure::measure::Measure,
|
solana_measure::measure::Measure,
|
||||||
solana_program_runtime::{
|
solana_program_runtime::{
|
||||||
|
compute_budget::ComputeBudget,
|
||||||
instruction_recorder::InstructionRecorder,
|
instruction_recorder::InstructionRecorder,
|
||||||
invoke_context::{BuiltinProgram, Executors, InvokeContext},
|
invoke_context::{BuiltinProgram, Executors, InvokeContext},
|
||||||
log_collector::LogCollector,
|
log_collector::LogCollector,
|
||||||
|
@ -10,7 +11,6 @@ use {
|
||||||
},
|
},
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
account::WritableAccount,
|
account::WritableAccount,
|
||||||
compute_budget::ComputeBudget,
|
|
||||||
feature_set::{prevent_calling_precompiles_as_programs, FeatureSet},
|
feature_set::{prevent_calling_precompiles_as_programs, FeatureSet},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
message::SanitizedMessage,
|
message::SanitizedMessage,
|
||||||
|
|
|
@ -1,22 +1,12 @@
|
||||||
#![cfg(feature = "full")]
|
#![cfg(feature = "full")]
|
||||||
|
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::instruction::Instruction,
|
||||||
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},
|
|
||||||
},
|
|
||||||
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
|
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
|
||||||
std::sync::Arc,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::declare_id!("ComputeBudget111111111111111111111111111111");
|
crate::declare_id!("ComputeBudget111111111111111111111111111111");
|
||||||
|
|
||||||
const MAX_UNITS: u32 = 1_000_000;
|
|
||||||
const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
|
|
||||||
|
|
||||||
/// Compute Budget Instructions
|
/// Compute Budget Instructions
|
||||||
#[derive(
|
#[derive(
|
||||||
Serialize,
|
Serialize,
|
||||||
|
@ -39,11 +29,13 @@ pub enum ComputeBudgetInstruction {
|
||||||
/// applies to each program executed, including all calls to CPIs.
|
/// applies to each program executed, including all calls to CPIs.
|
||||||
RequestHeapFrame(u32),
|
RequestHeapFrame(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputeBudgetInstruction {
|
impl ComputeBudgetInstruction {
|
||||||
/// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction`
|
/// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction`
|
||||||
pub fn request_units(units: u32) -> Instruction {
|
pub fn request_units(units: u32) -> Instruction {
|
||||||
Instruction::new_with_borsh(id(), &ComputeBudgetInstruction::RequestUnits(units), vec![])
|
Instruction::new_with_borsh(id(), &ComputeBudgetInstruction::RequestUnits(units), vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `ComputeBudgetInstruction::RequestHeapFrame` `Instruction`
|
/// Create a `ComputeBudgetInstruction::RequestHeapFrame` `Instruction`
|
||||||
pub fn request_heap_frame(bytes: u32) -> Instruction {
|
pub fn request_heap_frame(bytes: u32) -> Instruction {
|
||||||
Instruction::new_with_borsh(
|
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<usize>,
|
|
||||||
/// 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<FeatureSet>,
|
|
||||||
) -> 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()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue