diff --git a/Cargo.lock b/Cargo.lock index 712b3ee31f34c1..3456626bda4e5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6763,6 +6763,7 @@ dependencies = [ "rand 0.8.5", "solana-builtins-default-costs", "solana-compute-budget", + "solana-compute-budget-program", "solana-cost-model", "solana-feature-set", "solana-frozen-abi", diff --git a/builtins-default-costs/benches/builtin_instruction_costs.rs b/builtins-default-costs/benches/builtin_instruction_costs.rs index 04443655c01300..4aeee4b51c8f78 100644 --- a/builtins-default-costs/benches/builtin_instruction_costs.rs +++ b/builtins-default-costs/benches/builtin_instruction_costs.rs @@ -2,22 +2,23 @@ extern crate test; use { rand::Rng, - solana_builtins_default_costs::BUILTIN_INSTRUCTION_COSTS, + solana_builtins_default_costs::get_builtin_instruction_cost, solana_sdk::{ address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, - compute_budget, ed25519_program, loader_v4, pubkey::Pubkey, secp256k1_program, + compute_budget, ed25519_program, feature_set::FeatureSet, loader_v4, pubkey::Pubkey, + secp256k1_program, }, test::Bencher, }; struct BenchSetup { pubkeys: [Pubkey; 12], + feature_set: FeatureSet, } const NUM_TRANSACTIONS_PER_ITER: usize = 1024; -const DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT: u32 = 200_000; -fn setup() -> BenchSetup { +fn setup(all_features_enabled: bool) -> BenchSetup { let pubkeys: [Pubkey; 12] = [ solana_stake_program::id(), solana_config_program::id(), @@ -33,23 +34,39 @@ fn setup() -> BenchSetup { ed25519_program::id(), ]; - BenchSetup { pubkeys } + let feature_set = if all_features_enabled { + FeatureSet::all_enabled() + } else { + FeatureSet::default() + }; + + BenchSetup { + pubkeys, + feature_set, + } +} + +fn do_hash_find(setup: &BenchSetup) { + for _t in 0..NUM_TRANSACTIONS_PER_ITER { + let idx = rand::thread_rng().gen_range(0..setup.pubkeys.len()); + get_builtin_instruction_cost(&setup.pubkeys[idx], &setup.feature_set); + } +} + +#[bench] +fn bench_hash_find_builtins_not_migrated(bencher: &mut Bencher) { + let bench_setup = setup(false); + + bencher.iter(|| { + do_hash_find(&bench_setup); + }); } #[bench] -fn bench_hash_find(bencher: &mut Bencher) { - let BenchSetup { pubkeys } = setup(); +fn bench_hash_find_builtins_migrated(bencher: &mut Bencher) { + let bench_setup = setup(true); bencher.iter(|| { - for _t in 0..NUM_TRANSACTIONS_PER_ITER { - let idx = rand::thread_rng().gen_range(0..pubkeys.len()); - let ix_execution_cost = - if let Some(builtin_cost) = BUILTIN_INSTRUCTION_COSTS.get(&pubkeys[idx]) { - *builtin_cost - } else { - u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT) - }; - assert!(ix_execution_cost != DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT as u64); - } + do_hash_find(&bench_setup); }); } diff --git a/builtins-default-costs/src/lib.rs b/builtins-default-costs/src/lib.rs index 915064b4b79e35..48e2d76936e83d 100644 --- a/builtins-default-costs/src/lib.rs +++ b/builtins-default-costs/src/lib.rs @@ -5,11 +5,25 @@ use { lazy_static::lazy_static, solana_sdk::{ address_lookup_table, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, - compute_budget, ed25519_program, loader_v4, pubkey::Pubkey, secp256k1_program, + compute_budget, ed25519_program, + feature_set::{self, FeatureSet}, + loader_v4, + pubkey::Pubkey, + secp256k1_program, }, }; -// Number of compute units for each built-in programs +/// 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, +} + lazy_static! { /// Number of compute units for each built-in programs /// @@ -20,21 +34,95 @@ lazy_static! { /// 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. - pub static ref BUILTIN_INSTRUCTION_COSTS: AHashMap = [ - (solana_stake_program::id(), solana_stake_program::stake_instruction::DEFAULT_COMPUTE_UNITS), - (solana_config_program::id(), solana_config_program::config_processor::DEFAULT_COMPUTE_UNITS), - (solana_vote_program::id(), solana_vote_program::vote_processor::DEFAULT_COMPUTE_UNITS), - (solana_system_program::id(), solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS), - (compute_budget::id(), solana_compute_budget_program::DEFAULT_COMPUTE_UNITS), - (address_lookup_table::program::id(), solana_address_lookup_table_program::processor::DEFAULT_COMPUTE_UNITS), - (bpf_loader_upgradeable::id(), solana_bpf_loader_program::UPGRADEABLE_LOADER_COMPUTE_UNITS), - (bpf_loader_deprecated::id(), solana_bpf_loader_program::DEPRECATED_LOADER_COMPUTE_UNITS), - (bpf_loader::id(), solana_bpf_loader_program::DEFAULT_LOADER_COMPUTE_UNITS), - (loader_v4::id(), solana_loader_v4_program::DEFAULT_COMPUTE_UNITS), - // Note: These are precompile, run directly in bank during sanitizing; - (secp256k1_program::id(), 0), - (ed25519_program::id(), 0), - // DO NOT ADD MORE ENTRIES TO THIS MAP + static ref BUILTIN_INSTRUCTION_COSTS: AHashMap = [ + ( + 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() @@ -54,3 +142,58 @@ lazy_static! { temp_table }; } + +pub fn get_builtin_instruction_cost<'a>( + program_id: &'a Pubkey, + feature_set: &'a FeatureSet, +) -> Option { + 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) +} + +#[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() + ); + } +} diff --git a/core/src/banking_stage/packet_filter.rs b/core/src/banking_stage/packet_filter.rs index 4c38d70762e35e..057f0f90c45df9 100644 --- a/core/src/banking_stage/packet_filter.rs +++ b/core/src/banking_stage/packet_filter.rs @@ -1,10 +1,19 @@ use { super::immutable_deserialized_packet::ImmutableDeserializedPacket, - solana_builtins_default_costs::BUILTIN_INSTRUCTION_COSTS, - solana_sdk::{ed25519_program, saturating_add_assign, secp256k1_program}, + lazy_static::lazy_static, + solana_builtins_default_costs::get_builtin_instruction_cost, + solana_sdk::{ + ed25519_program, feature_set::FeatureSet, saturating_add_assign, secp256k1_program, + }, thiserror::Error, }; +lazy_static! { + // To calculate the static_builtin_cost_sum conservatively, an all-enabled dummy feature_set + // is used. It lowers required minimal compute_unit_limit, aligns with future versions. + static ref FEATURE_SET: FeatureSet = FeatureSet::all_enabled(); +} + #[derive(Debug, Error, PartialEq)] pub enum PacketFilterFailure { #[error("Insufficient compute unit limit")] @@ -22,8 +31,8 @@ impl ImmutableDeserializedPacket { pub fn check_insufficent_compute_unit_limit(&self) -> Result<(), PacketFilterFailure> { let mut static_builtin_cost_sum: u64 = 0; for (program_id, _) in self.transaction().get_message().program_instructions_iter() { - if let Some(ix_cost) = BUILTIN_INSTRUCTION_COSTS.get(program_id) { - saturating_add_assign!(static_builtin_cost_sum, *ix_cost); + if let Some(ix_cost) = get_builtin_instruction_cost(program_id, &FEATURE_SET) { + saturating_add_assign!(static_builtin_cost_sum, ix_cost); } } diff --git a/cost-model/Cargo.toml b/cost-model/Cargo.toml index 09bed5dcdd755f..d6e9e5b6e2bd46 100644 --- a/cost-model/Cargo.toml +++ b/cost-model/Cargo.toml @@ -36,6 +36,7 @@ name = "solana_cost_model" itertools = { workspace = true } rand = "0.8.5" # See order-crates-for-publishing.py for using this unusual `path = "."` +solana-compute-budget-program = { workspace = true } solana-cost-model = { path = ".", features = ["dev-context-only-utils"] } solana-logger = { workspace = true } solana-runtime-transaction = { workspace = true, features = [ diff --git a/cost-model/src/cost_model.rs b/cost-model/src/cost_model.rs index 918921e59b96cb..ee23ed893b44de 100644 --- a/cost-model/src/cost_model.rs +++ b/cost-model/src/cost_model.rs @@ -7,7 +7,7 @@ use { crate::{block_cost_limits::*, transaction_cost::*}, - solana_builtins_default_costs::BUILTIN_INSTRUCTION_COSTS, + solana_builtins_default_costs::get_builtin_instruction_cost, solana_compute_budget::compute_budget_limits::{ DEFAULT_HEAP_COST, DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT, MAX_COMPUTE_UNIT_LIMIT, }, @@ -163,8 +163,8 @@ impl CostModel { for (program_id, instruction) in transaction.program_instructions_iter() { let ix_execution_cost = - if let Some(builtin_cost) = BUILTIN_INSTRUCTION_COSTS.get(program_id) { - *builtin_cost + if let Some(builtin_cost) = get_builtin_instruction_cost(program_id, feature_set) { + builtin_cost } else { has_user_space_instructions = true; u64::from(DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT) @@ -518,14 +518,13 @@ mod tests { ); // expected cost for one system transfer instructions - let expected_execution_cost = BUILTIN_INSTRUCTION_COSTS - .get(&system_program::id()) - .unwrap(); + let expected_execution_cost = + solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS; let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = CostModel::get_transaction_cost(&simple_transaction, &FeatureSet::all_enabled()); - assert_eq!(*expected_execution_cost, program_execution_cost); + assert_eq!(expected_execution_cost, program_execution_cost); assert_eq!(3, data_bytes_cost); } @@ -672,9 +671,7 @@ mod tests { debug!("many transfer transaction {:?}", tx); // expected cost for two system transfer instructions - let program_cost = BUILTIN_INSTRUCTION_COSTS - .get(&system_program::id()) - .unwrap(); + let program_cost = solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS; let expected_cost = program_cost * 2; let (program_execution_cost, _loaded_accounts_data_size_cost, data_bytes_cost) = @@ -757,9 +754,8 @@ mod tests { )); let expected_account_cost = WRITE_LOCK_UNITS * 2; - let expected_execution_cost = BUILTIN_INSTRUCTION_COSTS - .get(&system_program::id()) - .unwrap(); + let expected_execution_cost = + solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS; const DEFAULT_PAGE_COST: u64 = 8; let expected_loaded_accounts_data_size_cost = solana_compute_budget::compute_budget_limits::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES.get() @@ -769,7 +765,7 @@ mod tests { let tx_cost = CostModel::calculate_cost(&tx, &FeatureSet::all_enabled()); assert_eq!(expected_account_cost, tx_cost.write_lock_cost()); - assert_eq!(*expected_execution_cost, tx_cost.programs_execution_cost()); + assert_eq!(expected_execution_cost, tx_cost.programs_execution_cost()); assert_eq!(2, tx_cost.writable_accounts().count()); assert_eq!( expected_loaded_accounts_data_size_cost, @@ -795,12 +791,8 @@ mod tests { let feature_set = FeatureSet::all_enabled(); let expected_account_cost = WRITE_LOCK_UNITS * 2; - let expected_execution_cost = BUILTIN_INSTRUCTION_COSTS - .get(&system_program::id()) - .unwrap() - + BUILTIN_INSTRUCTION_COSTS - .get(&compute_budget::id()) - .unwrap(); + let expected_execution_cost = solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS + + solana_compute_budget_program::DEFAULT_COMPUTE_UNITS; let expected_loaded_accounts_data_size_cost = (data_limit as u64) / (32 * 1024) * 8; let tx_cost = CostModel::calculate_cost(&tx, &feature_set); @@ -828,9 +820,7 @@ mod tests { start_hash, )); // transaction has one builtin instruction, and one bpf instruction, no ComputeBudget::compute_unit_limit - let expected_builtin_cost = *BUILTIN_INSTRUCTION_COSTS - .get(&solana_system_program::id()) - .unwrap(); + let expected_builtin_cost = solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS; let expected_bpf_cost = DEFAULT_INSTRUCTION_COMPUTE_UNIT_LIMIT; let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) = @@ -857,12 +847,8 @@ mod tests { start_hash, )); // transaction has one builtin instruction, and one ComputeBudget::compute_unit_limit - let expected_cost = *BUILTIN_INSTRUCTION_COSTS - .get(&solana_system_program::id()) - .unwrap() - + BUILTIN_INSTRUCTION_COSTS - .get(&compute_budget::id()) - .unwrap(); + let expected_cost = solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS + + solana_compute_budget_program::DEFAULT_COMPUTE_UNITS; let (program_execution_cost, _loaded_accounts_data_size_cost, _data_bytes_cost) = CostModel::get_transaction_cost(&transaction, &FeatureSet::all_enabled()); diff --git a/runtime/src/bank/builtins/mod.rs b/runtime/src/bank/builtins/mod.rs index 46a11849758f78..8ea62495ff87b0 100644 --- a/runtime/src/bank/builtins/mod.rs +++ b/runtime/src/bank/builtins/mod.rs @@ -31,6 +31,11 @@ macro_rules! testable_prototype { }; } +/// DEVELOPER: when a builtin is migrated to sbpf, please add its corresponding +/// migration feature ID to solana-builtin-default-costs::BUILTIN_INSTRUCTION_COSTS, +/// so the builtin's default cost can be determined properly based on feature status. +/// When migration completed, and the feature gate is enabled everywhere, please +/// remove that builtin entry from solana-builtin-default-costs::BUILTIN_INSTRUCTION_COSTS. pub static BUILTINS: &[BuiltinPrototype] = &[ testable_prototype!(BuiltinPrototype { core_bpf_migration_config: None,