2022-02-11 16:23:16 -08:00
|
|
|
use solana_sdk::{
|
|
|
|
borsh::try_from_slice_unchecked,
|
|
|
|
compute_budget::{self, ComputeBudgetInstruction},
|
|
|
|
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
|
|
|
|
instruction::InstructionError,
|
|
|
|
message::SanitizedMessage,
|
|
|
|
transaction::TransactionError,
|
2022-01-17 04:48:00 -08:00
|
|
|
};
|
|
|
|
|
2022-03-16 08:34:15 -07:00
|
|
|
pub const DEFAULT_UNITS: u32 = 200_000;
|
|
|
|
pub const MAX_UNITS: u32 = 1_400_000;
|
2022-01-17 04:48:00 -08:00
|
|
|
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,
|
2022-02-20 13:12:15 -08:00
|
|
|
/// Maximum number of slices hashed per syscall
|
|
|
|
pub sha256_max_slices: u64,
|
2022-01-17 04:48:00 -08:00
|
|
|
/// 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 {
|
2022-03-16 08:34:15 -07:00
|
|
|
Self::new(MAX_UNITS)
|
2022-01-17 04:48:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ComputeBudget {
|
2022-03-16 08:34:15 -07:00
|
|
|
pub fn new(max_units: u32) -> Self {
|
2022-01-17 04:48:00 -08:00
|
|
|
ComputeBudget {
|
2022-03-16 08:34:15 -07:00
|
|
|
max_units: max_units as u64,
|
2022-01-17 04:48:00 -08:00
|
|
|
log_64_units: 100,
|
|
|
|
create_program_address_units: 1500,
|
|
|
|
invoke_units: 1000,
|
|
|
|
max_invoke_depth: 4,
|
|
|
|
sha256_base_cost: 85,
|
|
|
|
sha256_byte_cost: 1,
|
2022-02-20 13:12:15 -08:00
|
|
|
sha256_max_slices: 20_000,
|
2022-01-17 04:48:00 -08:00
|
|
|
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,
|
2022-01-26 18:01:57 -08:00
|
|
|
mem_op_base_cost: 10,
|
2022-01-17 04:48:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-11 16:23:16 -08:00
|
|
|
pub fn process_message(
|
2022-01-17 04:48:00 -08:00
|
|
|
&mut self,
|
2022-02-11 16:23:16 -08:00
|
|
|
message: &SanitizedMessage,
|
|
|
|
requestable_heap_size: bool,
|
|
|
|
) -> Result<u64, TransactionError> {
|
|
|
|
let mut requested_additional_fee = 0;
|
2022-01-17 04:48:00 -08:00
|
|
|
let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData);
|
|
|
|
// Compute budget instruction must be in the 1st 3 instructions (avoid
|
|
|
|
// nonce marker), otherwise ignored
|
2022-02-11 16:23:16 -08:00
|
|
|
for (program_id, instruction) in message.program_instructions_iter().take(3) {
|
2022-01-17 04:48:00 -08:00
|
|
|
if compute_budget::check_id(program_id) {
|
|
|
|
match try_from_slice_unchecked(&instruction.data) {
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(ComputeBudgetInstruction::RequestUnits {
|
|
|
|
units,
|
|
|
|
additional_fee,
|
|
|
|
}) => {
|
|
|
|
self.max_units = units.min(MAX_UNITS) as u64;
|
|
|
|
requested_additional_fee = additional_fee as u64;
|
2022-01-17 04:48:00 -08:00
|
|
|
}
|
|
|
|
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
|
2022-02-11 16:23:16 -08:00
|
|
|
if !requestable_heap_size
|
2022-01-17 04:48:00 -08:00
|
|
|
|| 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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(requested_additional_fee)
|
2022-01-17 04:48:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use {
|
|
|
|
super::*,
|
|
|
|
solana_sdk::{
|
2022-02-11 16:23:16 -08:00
|
|
|
hash::Hash,
|
|
|
|
instruction::Instruction,
|
|
|
|
message::Message,
|
|
|
|
pubkey::Pubkey,
|
|
|
|
signature::Keypair,
|
|
|
|
signer::Signer,
|
|
|
|
transaction::{SanitizedTransaction, Transaction},
|
2022-01-17 04:48:00 -08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
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 mut compute_budget = ComputeBudget::default();
|
2022-02-11 16:23:16 -08:00
|
|
|
let result = compute_budget.process_message(&tx.message(), true);
|
|
|
|
assert_eq!($expected_error, result);
|
2022-01-17 04:48:00 -08:00
|
|
|
assert_eq!(compute_budget, $expected_budget);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2022-02-11 16:23:16 -08:00
|
|
|
fn test_process_mesage() {
|
2022-01-17 04:48:00 -08:00
|
|
|
// Units
|
2022-02-11 16:23:16 -08:00
|
|
|
test!(&[], Ok(0), ComputeBudget::default());
|
2022-01-17 04:48:00 -08:00
|
|
|
test!(
|
|
|
|
&[
|
2022-02-11 16:23:16 -08:00
|
|
|
ComputeBudgetInstruction::request_units(1, 0),
|
2022-01-17 04:48:00 -08:00
|
|
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
ComputeBudget {
|
|
|
|
max_units: 1,
|
|
|
|
..ComputeBudget::default()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
&[
|
2022-02-11 16:23:16 -08:00
|
|
|
ComputeBudgetInstruction::request_units(MAX_UNITS + 1, 0),
|
2022-01-17 04:48:00 -08:00
|
|
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
ComputeBudget::default()
|
|
|
|
);
|
|
|
|
test!(
|
|
|
|
&[
|
|
|
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
2022-02-11 16:23:16 -08:00
|
|
|
ComputeBudgetInstruction::request_units(MAX_UNITS, 0),
|
2022-01-17 04:48:00 -08:00
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
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![]),
|
2022-02-11 16:23:16 -08:00
|
|
|
ComputeBudgetInstruction::request_units(1, 0),
|
2022-01-17 04:48:00 -08:00
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
ComputeBudget::default()
|
|
|
|
);
|
|
|
|
|
|
|
|
// HeapFrame
|
2022-02-11 16:23:16 -08:00
|
|
|
test!(&[], Ok(0), ComputeBudget::default());
|
2022-01-17 04:48:00 -08:00
|
|
|
test!(
|
|
|
|
&[
|
|
|
|
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
|
|
|
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
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),
|
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
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
|
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
ComputeBudget::default()
|
|
|
|
);
|
|
|
|
|
|
|
|
// Combined
|
|
|
|
test!(
|
|
|
|
&[
|
|
|
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
|
|
|
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
2022-02-11 16:23:16 -08:00
|
|
|
ComputeBudgetInstruction::request_units(MAX_UNITS, 0),
|
2022-01-17 04:48:00 -08:00
|
|
|
],
|
2022-02-11 16:23:16 -08:00
|
|
|
Ok(0),
|
2022-01-17 04:48:00 -08:00
|
|
|
ComputeBudget {
|
|
|
|
max_units: MAX_UNITS as u64,
|
|
|
|
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
|
|
|
|
..ComputeBudget::default()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|