Add program heap bump instruction (backport #20607) (#20815)

* Add program heap bump instruction (#20607)

(cherry picked from commit 58164517e4)

* nudge

Co-authored-by: Jack May <jack@solana.com>
This commit is contained in:
mergify[bot]
2021-10-20 23:05:57 +00:00
committed by GitHub
parent d5fc81e12a
commit 440ccd189e
7 changed files with 251 additions and 74 deletions

View File

@ -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 =
<dyn Executable<BpfError, ThisInstructionMeter>>::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] #[bench]
fn bench_instruction_count_tuner(_bencher: &mut Bencher) { fn bench_instruction_count_tuner(_bencher: &mut Bencher) {
const BUDGET: u64 = 200_000; const BUDGET: u64 = 200_000;

View File

@ -34,7 +34,7 @@ use solana_sdk::{
entrypoint::{HEAP_LENGTH, SUCCESS}, entrypoint::{HEAP_LENGTH, SUCCESS},
feature_set::{ feature_set::{
add_missing_program_error_mappings, close_upgradeable_program_accounts, fix_write_privs, 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, ic_logger_msg, ic_msg,
instruction::{AccountMeta, InstructionError}, instruction::{AccountMeta, InstructionError},
@ -150,6 +150,12 @@ pub fn create_vm<'a>(
invoke_context: &'a mut dyn InvokeContext, invoke_context: &'a mut dyn InvokeContext,
) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> { ) -> Result<EbpfVm<'a, BpfError, ThisInstructionMeter>, EbpfError<BpfError>> {
let bpf_compute_budget = invoke_context.get_bpf_compute_budget(); 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( let heap = AlignedMemory::new_with_size(
bpf_compute_budget.heap_size.unwrap_or(HEAP_LENGTH), bpf_compute_budget.heap_size.unwrap_or(HEAP_LENGTH),
HOST_ALIGN, HOST_ALIGN,

View File

@ -3677,7 +3677,11 @@ impl Bank {
.unwrap_or_else(BpfComputeBudget::new); .unwrap_or_else(BpfComputeBudget::new);
let mut process_result = if feature_set.is_active(&tx_wide_compute_cap::id()) { 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 { } else {
Ok(()) Ok(())
}; };
@ -14253,6 +14257,7 @@ pub(crate) mod tests {
*compute_budget, *compute_budget,
BpfComputeBudget { BpfComputeBudget {
max_units: 1, max_units: 1,
heap_size: Some(48 * 1024),
..BpfComputeBudget::default() ..BpfComputeBudget::default()
} }
); );
@ -14264,6 +14269,7 @@ pub(crate) mod tests {
let message = Message::new( let message = Message::new(
&[ &[
compute_budget::request_units(1), compute_budget::request_units(1),
compute_budget::request_heap_frame(48 * 1024),
Instruction::new_with_bincode(program_id, &0, vec![]), Instruction::new_with_bincode(program_id, &0, vec![]),
], ],
Some(&mint_keypair.pubkey()), Some(&mint_keypair.pubkey()),

View File

@ -11,8 +11,8 @@ use solana_sdk::{
bpf_loader_upgradeable::{self, UpgradeableLoaderState}, bpf_loader_upgradeable::{self, UpgradeableLoaderState},
feature_set::{ feature_set::{
demote_program_write_locks, fix_write_privs, instructions_sysvar_enabled, demote_program_write_locks, fix_write_privs, instructions_sysvar_enabled,
neon_evm_compute_budget, remove_native_loader, tx_wide_compute_cap, updated_verify_policy, neon_evm_compute_budget, remove_native_loader, requestable_heap_size, tx_wide_compute_cap,
FeatureSet, updated_verify_policy, FeatureSet,
}, },
ic_logger_msg, ic_msg, ic_logger_msg, ic_msg,
instruction::{CompiledInstruction, Instruction, InstructionError}, instruction::{CompiledInstruction, Instruction, InstructionError},
@ -1227,11 +1227,17 @@ impl MessageProcessor {
let program_id = instruction.program_id(&message.account_keys); let program_id = instruction.program_id(&message.account_keys);
let mut bpf_compute_budget = bpf_compute_budget; 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() && *program_id == crate::neon_evm_program::id()
{ {
// Bump the compute budget for neon_evm // Bump the compute budget for neon_evm
bpf_compute_budget.max_units = bpf_compute_budget.max_units.max(500_000); 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); bpf_compute_budget.heap_size = Some(256 * 1024);
} }

View File

@ -1,6 +1,8 @@
#![cfg(feature = "full")] #![cfg(feature = "full")]
use crate::{ use crate::{
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
feature_set::{requestable_heap_size, FeatureSet},
process_instruction::BpfComputeBudget, process_instruction::BpfComputeBudget,
transaction::{Transaction, TransactionError}, transaction::{Transaction, TransactionError},
}; };
@ -9,10 +11,12 @@ use solana_sdk::{
borsh::try_from_slice_unchecked, borsh::try_from_slice_unchecked,
instruction::{Instruction, InstructionError}, instruction::{Instruction, InstructionError},
}; };
use std::sync::Arc;
crate::declare_id!("ComputeBudget111111111111111111111111111111"); crate::declare_id!("ComputeBudget111111111111111111111111111111");
const MAX_UNITS: u32 = 1_000_000; const MAX_UNITS: u32 = 1_000_000;
const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
/// Compute Budget Instructions /// Compute Budget Instructions
#[derive( #[derive(
@ -31,6 +35,10 @@ pub enum ComputeBudgetInstruction {
/// Request a specific maximum number of compute units the transaction is /// Request a specific maximum number of compute units the transaction is
/// allowed to consume. /// allowed to consume.
RequestUnits(u32), 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` /// 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![]) 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( pub fn process_request(
compute_budget: &mut BpfComputeBudget, compute_budget: &mut BpfComputeBudget,
tx: &Transaction, tx: &Transaction,
feature_set: Arc<FeatureSet>,
) -> Result<(), TransactionError> { ) -> Result<(), TransactionError> {
let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData); let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData);
// Compute budget instruction must be in 1st or 2nd instruction (avoid nonce marker) // Compute budget instruction must be in the 1st 3 instructions (avoid
for instruction in tx.message().instructions.iter().take(2) { // nonce marker), otherwise ignored
for instruction in tx.message().instructions.iter().take(3) {
if check_id(instruction.program_id(&tx.message().account_keys)) { if check_id(instruction.program_id(&tx.message().account_keys)) {
let ComputeBudgetInstruction::RequestUnits(units) = match try_from_slice_unchecked(&instruction.data) {
try_from_slice_unchecked::<ComputeBudgetInstruction>(&instruction.data) Ok(ComputeBudgetInstruction::RequestUnits(units)) => {
.map_err(|_| error.clone())?; if units > MAX_UNITS {
if units > MAX_UNITS { return Err(error);
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(()) Ok(())
@ -61,82 +92,153 @@ pub fn process_request(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{hash::Hash, message::Message, pubkey::Pubkey, signature::Keypair, signer::Signer};
compute_budget, 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] #[test]
fn test_process_request() { fn test_process_request() {
let payer_keypair = Keypair::new(); // Units
let mut compute_budget = BpfComputeBudget::default(); test!(&[], Ok(()), BpfComputeBudget::default());
test!(
let tx = Transaction::new( &[
&[&payer_keypair], request_units(1),
Message::new(&[], Some(&payer_keypair.pubkey())), Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Hash::default(), ],
); Ok(()),
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,
BpfComputeBudget { BpfComputeBudget {
max_units: 1, max_units: 1,
..BpfComputeBudget::default() ..BpfComputeBudget::default()
} }
); );
test!(
let tx = Transaction::new( &[
&[&payer_keypair], request_units(MAX_UNITS + 1),
Message::new( Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
&[ ],
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,
Err(TransactionError::InstructionError( Err(TransactionError::InstructionError(
0, 0,
InstructionError::InvalidInstructionData InstructionError::InvalidInstructionData,
)) )),
BpfComputeBudget::default()
); );
test!(
let tx = Transaction::new( &[
&[&payer_keypair], Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
Message::new( request_units(MAX_UNITS),
&[ ],
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), Ok(()),
compute_budget::request_units(MAX_UNITS),
],
Some(&payer_keypair.pubkey()),
),
Hash::default(),
);
process_request(&mut compute_budget, &tx).unwrap();
assert_eq!(
compute_budget,
BpfComputeBudget { BpfComputeBudget {
max_units: MAX_UNITS as u64, max_units: MAX_UNITS as u64,
..BpfComputeBudget::default() ..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()
}
);
} }
} }

View File

@ -243,6 +243,10 @@ pub mod ed25519_program_enabled {
solana_sdk::declare_id!("E1TvTNipX8TKNHrhRC8SMuAwQmGY58TZ4drdztP3Gxwc"); solana_sdk::declare_id!("E1TvTNipX8TKNHrhRC8SMuAwQmGY58TZ4drdztP3Gxwc");
} }
pub mod requestable_heap_size {
solana_sdk::declare_id!("CCu4boMmfLuqcmfTLPHQiUo22ZdUsXjgzPAURYaWt1Bw");
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -304,6 +308,7 @@ lazy_static! {
(return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall"), (return_data_syscall_enabled::id(), "enable sol_{set,get}_return_data syscall"),
(sol_log_data_syscall_enabled::id(), "enable sol_log_data syscall"), (sol_log_data_syscall_enabled::id(), "enable sol_log_data syscall"),
(ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"), (ed25519_program_enabled::id(), "enable builtin ed25519 signature verify program"),
(requestable_heap_size::id(), "Requestable heap frame size"),
/*************** ADD NEW FEATURES HERE ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()

View File

@ -190,6 +190,9 @@ pub struct BpfComputeBudget {
pub syscall_base_cost: u64, pub syscall_base_cost: u64,
/// Optional program heap region size, if `None` then loader default /// Optional program heap region size, if `None` then loader default
pub heap_size: Option<usize>, 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,
} }
impl Default for BpfComputeBudget { impl Default for BpfComputeBudget {
@ -217,6 +220,7 @@ impl BpfComputeBudget {
syscall_base_cost: 100, syscall_base_cost: 100,
secp256k1_recover_cost: 25_000, secp256k1_recover_cost: 25_000,
heap_size: None, heap_size: None,
heap_cost: 8,
} }
} }
} }