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 {
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -17,7 +17,10 @@ use solana_bpf_loader_program::{
|
|||
use solana_bpf_rust_invoke::instructions::*;
|
||||
use solana_bpf_rust_realloc::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::{
|
||||
elf::Executable,
|
||||
static_analysis::Analysis,
|
||||
|
@ -41,7 +44,7 @@ use solana_sdk::{
|
|||
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
||||
client::SyncClient,
|
||||
clock::MAX_PROCESSING_AGE,
|
||||
compute_budget::{ComputeBudget, ComputeBudgetInstruction},
|
||||
compute_budget::ComputeBudgetInstruction,
|
||||
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
||||
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
|
||||
loader_instruction,
|
||||
|
@ -226,13 +229,16 @@ fn run_program(name: &str) -> u64 {
|
|||
let mut instruction_count = 0;
|
||||
let mut tracer = None;
|
||||
for i in 0..2 {
|
||||
invoke_context.transaction_context.set_return_data(
|
||||
*invoke_context
|
||||
.transaction_context
|
||||
.get_program_key()
|
||||
.unwrap(),
|
||||
Vec::new(),
|
||||
).unwrap();
|
||||
invoke_context
|
||||
.transaction_context
|
||||
.set_return_data(
|
||||
*invoke_context
|
||||
.transaction_context
|
||||
.get_program_key()
|
||||
.unwrap(),
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap();
|
||||
let mut parameter_bytes = parameter_bytes.clone();
|
||||
{
|
||||
let mut vm = create_vm(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<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