forked from solana-labs/solana
-
Notifications
You must be signed in to change notification settings - Fork 265
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix reserve minimal compute units for builtins (#3799)
- Add feature gate, issue #2562; - Implement SIMD-170; --------- Co-authored-by: Justin Starry <[email protected]> (cherry picked from commit 3e9af14) # Conflicts: # builtins-default-costs/src/lib.rs # compute-budget/src/compute_budget_limits.rs # compute-budget/src/compute_budget_processor.rs # core/src/banking_stage/consumer.rs # core/src/banking_stage/immutable_deserialized_packet.rs # core/src/banking_stage/transaction_scheduler/receive_and_buffer.rs # cost-model/src/cost_model.rs # cost-model/src/transaction_cost.rs # programs/compute-budget-bench/benches/compute_budget.rs # programs/sbf/tests/programs.rs # runtime-transaction/benches/process_compute_budget_instructions.rs # runtime-transaction/src/compute_budget_instruction_details.rs # runtime-transaction/src/compute_budget_program_id_filter.rs # runtime-transaction/src/lib.rs # runtime-transaction/src/runtime_transaction.rs # runtime-transaction/src/runtime_transaction/sdk_transactions.rs # runtime/src/bank.rs # runtime/src/bank/tests.rs # runtime/src/prioritization_fee_cache.rs # sdk/src/feature_set.rs # svm-transaction/src/svm_message.rs # svm-transaction/src/svm_message/sanitized_message.rs # svm-transaction/src/svm_message/sanitized_transaction.rs # svm/src/transaction_processor.rs # transaction-view/src/resolved_transaction_view.rs # transaction-view/src/transaction_view.rs
- Loading branch information
1 parent
2c653b0
commit 4099de5
Showing
29 changed files
with
3,944 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))] | ||
#![allow(clippy::arithmetic_side_effects)] | ||
use { | ||
ahash::AHashMap, | ||
lazy_static::lazy_static, | ||
solana_sdk::{ | ||
address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, | ||
compute_budget, ed25519_program, | ||
feature_set::{self, FeatureSet}, | ||
loader_v4, | ||
pubkey::Pubkey, | ||
secp256k1_program, | ||
}, | ||
}; | ||
|
||
/// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding | ||
/// migration feature ID to BUILTIN_INSTRUCTION_COSTS, so the builtin's default | ||
/// cost can be determined properly based on feature status. | ||
/// When migration completed, eg the feature gate is enabled everywhere, please | ||
/// remove that builtin entry from BUILTIN_INSTRUCTION_COSTS. | ||
#[derive(Clone)] | ||
struct BuiltinCost { | ||
native_cost: u64, | ||
core_bpf_migration_feature: Option<Pubkey>, | ||
} | ||
|
||
lazy_static! { | ||
/// Number of compute units for each built-in programs | ||
/// | ||
/// DEVELOPER WARNING: This map CANNOT be modified without causing a | ||
/// consensus failure because this map is used to calculate the compute | ||
/// limit for transactions that don't specify a compute limit themselves as | ||
/// of https://github.com/anza-xyz/agave/issues/2212. It's also used to | ||
/// calculate the cost of a transaction which is used in replay to enforce | ||
/// block cost limits as of | ||
/// https://github.com/solana-labs/solana/issues/29595. | ||
static ref BUILTIN_INSTRUCTION_COSTS: AHashMap<Pubkey, BuiltinCost> = [ | ||
( | ||
solana_stake_program::id(), | ||
BuiltinCost { | ||
native_cost: solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: Some(feature_set::migrate_stake_program_to_core_bpf::id()), | ||
}, | ||
), | ||
( | ||
solana_config_program::id(), | ||
BuiltinCost { | ||
native_cost: solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: Some(feature_set::migrate_config_program_to_core_bpf::id()), | ||
}, | ||
), | ||
( | ||
solana_vote_program::id(), | ||
BuiltinCost { | ||
native_cost: solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
solana_system_program::id(), | ||
BuiltinCost { | ||
native_cost: solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
compute_budget::id(), | ||
BuiltinCost { | ||
native_cost: solana_compute_budget_program::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
address_lookup_table::program::id(), | ||
BuiltinCost { | ||
native_cost: solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: Some( | ||
feature_set::migrate_address_lookup_table_program_to_core_bpf::id(), | ||
), | ||
}, | ||
), | ||
( | ||
bpf_loader_upgradeable::id(), | ||
BuiltinCost { | ||
native_cost: solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
bpf_loader_deprecated::id(), | ||
BuiltinCost { | ||
native_cost: solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
bpf_loader::id(), | ||
BuiltinCost { | ||
native_cost: solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
loader_v4::id(), | ||
BuiltinCost { | ||
native_cost: solana_loader_v4_program::DEFAULT_COMPUTE_UNITS, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
// Note: These are precompile, run directly in bank during sanitizing; | ||
( | ||
secp256k1_program::id(), | ||
BuiltinCost { | ||
native_cost: 0, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
( | ||
ed25519_program::id(), | ||
BuiltinCost { | ||
native_cost: 0, | ||
core_bpf_migration_feature: None, | ||
}, | ||
), | ||
// DO NOT ADD MORE ENTRIES TO THIS MAP | ||
] | ||
.iter() | ||
.cloned() | ||
.collect(); | ||
} | ||
|
||
lazy_static! { | ||
/// A table of 256 booleans indicates whether the first `u8` of a Pubkey exists in | ||
/// BUILTIN_INSTRUCTION_COSTS. If the value is true, the Pubkey might be a builtin key; | ||
/// if false, it cannot be a builtin key. This table allows for quick filtering of | ||
/// builtin program IDs without the need for hashing. | ||
pub static ref MAYBE_BUILTIN_KEY: [bool; 256] = { | ||
let mut temp_table: [bool; 256] = [false; 256]; | ||
BUILTIN_INSTRUCTION_COSTS | ||
.keys() | ||
.for_each(|key| temp_table[key.as_ref()[0] as usize] = true); | ||
temp_table | ||
}; | ||
} | ||
|
||
pub fn get_builtin_instruction_cost<'a>( | ||
program_id: &'a Pubkey, | ||
feature_set: &'a FeatureSet, | ||
) -> Option<u64> { | ||
BUILTIN_INSTRUCTION_COSTS | ||
.get(program_id) | ||
.filter( | ||
// Returns true if builtin program id has no core_bpf_migration_feature or feature is not activated; | ||
// otherwise returns false because it's not considered as builtin | ||
|builtin_cost| -> bool { | ||
builtin_cost | ||
.core_bpf_migration_feature | ||
.map(|feature_id| !feature_set.is_active(&feature_id)) | ||
.unwrap_or(true) | ||
}, | ||
) | ||
.map(|builtin_cost| builtin_cost.native_cost) | ||
} | ||
|
||
#[inline] | ||
pub fn is_builtin_program(program_id: &Pubkey) -> bool { | ||
BUILTIN_INSTRUCTION_COSTS.contains_key(program_id) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_get_builtin_instruction_cost() { | ||
// use native cost if no migration planned | ||
assert_eq!( | ||
Some(solana_compute_budget_program::DEFAULT_COMPUTE_UNITS), | ||
get_builtin_instruction_cost(&compute_budget::id(), &FeatureSet::all_enabled()) | ||
); | ||
|
||
// use native cost if migration is planned but not activated | ||
assert_eq!( | ||
Some(solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS), | ||
get_builtin_instruction_cost(&solana_stake_program::id(), &FeatureSet::default()) | ||
); | ||
|
||
// None if migration is planned and activated, in which case, it's no longer builtin | ||
assert!(get_builtin_instruction_cost( | ||
&solana_stake_program::id(), | ||
&FeatureSet::all_enabled() | ||
) | ||
.is_none()); | ||
|
||
// None if not builtin | ||
assert!( | ||
get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::default()).is_none() | ||
); | ||
assert!( | ||
get_builtin_instruction_cost(&Pubkey::new_unique(), &FeatureSet::all_enabled()) | ||
.is_none() | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use { | ||
solana_fee_structure::FeeBudgetLimits, solana_program_entrypoint::HEAP_LENGTH, | ||
std::num::NonZeroU32, | ||
}; | ||
|
||
/// Roughly 0.5us/page, where page is 32K; given roughly 15CU/us, the | ||
/// default heap page cost = 0.5 * 15 ~= 8CU/page | ||
pub const DEFAULT_HEAP_COST: u64 = 8; | ||
pub const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; | ||
// SIMD-170 defines max CUs to be allocated for any builtin program instructions, that | ||
// have not been migrated to sBPF programs. | ||
pub const MAX_BUILTIN_ALLOCATION_COMPUTE_UNIT_LIMIT: u32 = 3_000; | ||
pub const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; | ||
pub const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024; | ||
pub const MIN_HEAP_FRAME_BYTES: u32 = HEAP_LENGTH as u32; | ||
|
||
type MicroLamports = u128; | ||
|
||
/// There are 10^6 micro-lamports in one lamport | ||
const MICRO_LAMPORTS_PER_LAMPORT: u64 = 1_000_000; | ||
|
||
/// The total accounts data a transaction can load is limited to 64MiB to not break | ||
/// anyone in Mainnet-beta today. It can be set by set_loaded_accounts_data_size_limit instruction | ||
pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: NonZeroU32 = | ||
unsafe { NonZeroU32::new_unchecked(64 * 1024 * 1024) }; | ||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
pub struct ComputeBudgetLimits { | ||
pub updated_heap_bytes: u32, | ||
pub compute_unit_limit: u32, | ||
pub compute_unit_price: u64, | ||
pub loaded_accounts_bytes: NonZeroU32, | ||
} | ||
|
||
impl Default for ComputeBudgetLimits { | ||
fn default() -> Self { | ||
ComputeBudgetLimits { | ||
updated_heap_bytes: MIN_HEAP_FRAME_BYTES, | ||
compute_unit_limit: MAX_COMPUTE_UNIT_LIMIT, | ||
compute_unit_price: 0, | ||
loaded_accounts_bytes: MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES, | ||
} | ||
} | ||
} | ||
|
||
fn get_prioritization_fee(compute_unit_price: u64, compute_unit_limit: u64) -> u64 { | ||
let micro_lamport_fee: MicroLamports = | ||
(compute_unit_price as u128).saturating_mul(compute_unit_limit as u128); | ||
micro_lamport_fee | ||
.saturating_add(MICRO_LAMPORTS_PER_LAMPORT.saturating_sub(1) as u128) | ||
.checked_div(MICRO_LAMPORTS_PER_LAMPORT as u128) | ||
.and_then(|fee| u64::try_from(fee).ok()) | ||
.unwrap_or(u64::MAX) | ||
} | ||
|
||
impl From<ComputeBudgetLimits> for FeeBudgetLimits { | ||
fn from(val: ComputeBudgetLimits) -> Self { | ||
let prioritization_fee = | ||
get_prioritization_fee(val.compute_unit_price, u64::from(val.compute_unit_limit)); | ||
|
||
FeeBudgetLimits { | ||
loaded_accounts_data_size_limit: val.loaded_accounts_bytes, | ||
heap_cost: DEFAULT_HEAP_COST, | ||
compute_unit_limit: u64::from(val.compute_unit_limit), | ||
prioritization_fee, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_new_with_no_fee() { | ||
for compute_units in [0, 1, MICRO_LAMPORTS_PER_LAMPORT, u64::MAX] { | ||
assert_eq!(get_prioritization_fee(0, compute_units), 0); | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_new_with_compute_unit_price() { | ||
assert_eq!( | ||
get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT - 1, 1), | ||
1, | ||
"should round up (<1.0) lamport fee to 1 lamport" | ||
); | ||
|
||
assert_eq!(get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT, 1), 1); | ||
|
||
assert_eq!( | ||
get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT + 1, 1), | ||
2, | ||
"should round up (>1.0) lamport fee to 2 lamports" | ||
); | ||
|
||
assert_eq!(get_prioritization_fee(200, 100_000), 20); | ||
|
||
assert_eq!( | ||
get_prioritization_fee(MICRO_LAMPORTS_PER_LAMPORT, u64::MAX), | ||
u64::MAX | ||
); | ||
|
||
assert_eq!(get_prioritization_fee(u64::MAX, u64::MAX), u64::MAX); | ||
} | ||
} |
Oops, something went wrong.