Skip to content

Commit

Permalink
Fix reserve minimal compute units for builtins (#3799)
Browse files Browse the repository at this point in the history
- 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
tao-stones authored and mergify[bot] committed Dec 5, 2024
1 parent 2c653b0 commit 4099de5
Show file tree
Hide file tree
Showing 29 changed files with 3,944 additions and 25 deletions.
204 changes: 204 additions & 0 deletions builtins-default-costs/src/lib.rs
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()
);
}
}
106 changes: 106 additions & 0 deletions compute-budget/src/compute_budget_limits.rs
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);
}
}
Loading

0 comments on commit 4099de5

Please sign in to comment.