From b1b3702e6d774e4d46a538460c4a08350cccbe03 Mon Sep 17 00:00:00 2001 From: Tao Zhu <82401714+taozhu-chicago@users.noreply.github.com> Date: Sun, 15 May 2022 23:06:33 -0500 Subject: [PATCH] Prioritize transactions in banking stage by their compute unit price (#25178) * - get prioritization fee from compute_budget instruction; - update compute_budget::process_instruction function to take instruction iter to support sanitized versioned message; - updated runtime.md * update transaction fee calculation for prioritization fee rate as lamports per 10K CUs * review changes * fix test * fix a bpf test * fix bpf test * patch feedback * fix clippy * fix bpf test * feedback * rename prioritization fee rate to compute unit price * feedback Co-authored-by: Justin Starry --- cli/src/cli.rs | 6 +- cli/src/cluster_query.rs | 23 +- core/benches/unprocessed_packet_batches.rs | 2 +- core/src/banking_stage.rs | 12 +- core/src/unprocessed_packet_batches.rs | 76 +++---- .../developing/programming-model/runtime.md | 38 ++-- program-runtime/src/compute_budget.rs | 140 +++++++----- program-runtime/src/lib.rs | 1 + program-runtime/src/prioritization_fee.rs | 204 ++++++++++++++++++ programs/bpf/tests/programs.rs | 13 +- runtime/src/accounts.rs | 4 +- runtime/src/bank.rs | 66 +++--- sdk/src/compute_budget.rs | 12 +- sdk/src/feature_set.rs | 9 +- 14 files changed, 435 insertions(+), 171 deletions(-) create mode 100644 program-runtime/src/prioritization_fee.rs diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 9ff3909e54..1f0f2ce1a7 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -89,7 +89,7 @@ pub enum CliCommand { timeout: Duration, blockhash: Option, print_timestamp: bool, - prioritization_fee: Option, + compute_unit_price: Option, }, Rent { data_length: usize, @@ -877,7 +877,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { timeout, blockhash, print_timestamp, - prioritization_fee, + compute_unit_price, } => process_ping( &rpc_client, config, @@ -886,7 +886,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { timeout, blockhash, *print_timestamp, - prioritization_fee, + compute_unit_price, ), CliCommand::Rent { data_length, diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 9507a2e534..bf6a131a8d 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -271,12 +271,11 @@ impl ClusterQuerySubCommands for App<'_, '_> { .help("Wait up to timeout seconds for transaction confirmation"), ) .arg( - Arg::with_name("prioritization-fee") - .long("prioritization-fee") - .alias("additional-fee") - .value_name("NUMBER") + Arg::with_name("compute_unit_price") + .long("compute-unit-price") + .value_name("MICRO-LAMPORTS") .takes_value(true) - .help("Set prioritization-fee for transaction"), + .help("Set the price in micro-lamports of each transaction compute unit"), ) .arg(blockhash_arg()), ) @@ -523,7 +522,7 @@ pub fn parse_cluster_ping( let timeout = Duration::from_secs(value_t_or_exit!(matches, "timeout", u64)); let blockhash = value_of(matches, BLOCKHASH_ARG.name); let print_timestamp = matches.is_present("print_timestamp"); - let prioritization_fee = value_of(matches, "prioritization_fee"); + let compute_unit_price = value_of(matches, "compute_unit_price"); Ok(CliCommandInfo { command: CliCommand::Ping { interval, @@ -531,7 +530,7 @@ pub fn parse_cluster_ping( timeout, blockhash, print_timestamp, - prioritization_fee, + compute_unit_price, }, signers: vec![default_signer.signer_from_path(matches, wallet_manager)?], }) @@ -1364,7 +1363,7 @@ pub fn process_ping( timeout: &Duration, fixed_blockhash: &Option, print_timestamp: bool, - prioritization_fee: &Option, + compute_unit_price: &Option, ) -> ProcessResult { let (signal_sender, signal_receiver) = unbounded(); ctrlc::set_handler(move || { @@ -1409,9 +1408,9 @@ pub fn process_ping( &to, lamports, )]; - if let Some(prioritization_fee) = prioritization_fee { - ixs.push(ComputeBudgetInstruction::set_prioritization_fee( - *prioritization_fee, + if let Some(compute_unit_price) = compute_unit_price { + ixs.push(ComputeBudgetInstruction::set_compute_unit_price( + *compute_unit_price, )); } Message::new(&ixs, Some(&config.signers[0].pubkey())) @@ -2338,7 +2337,7 @@ mod tests { Hash::from_str("4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX").unwrap() ), print_timestamp: true, - prioritization_fee: None, + compute_unit_price: None, }, signers: vec![default_keypair.into()], } diff --git a/core/benches/unprocessed_packet_batches.rs b/core/benches/unprocessed_packet_batches.rs index e490db9ce5..8cbac27b59 100644 --- a/core/benches/unprocessed_packet_batches.rs +++ b/core/benches/unprocessed_packet_batches.rs @@ -74,7 +74,7 @@ fn insert_packet_batches( } else { build_packet_batch(packet_per_batch_count) }; - let deserialized_packets = deserialize_packets(&packet_batch, &packet_indexes, None); + let deserialized_packets = deserialize_packets(&packet_batch, &packet_indexes); unprocessed_packet_batches.insert_batch(deserialized_packets); }); timer.stop(); diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index b8deaa0c45..68aa89f9d7 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -2110,7 +2110,7 @@ impl BankingStage { let number_of_dropped_packets = unprocessed_packet_batches.insert_batch( // Passing `None` for bank for now will make all packet weights 0 - unprocessed_packet_batches::deserialize_packets(packet_batch, packet_indexes, None), + unprocessed_packet_batches::deserialize_packets(packet_batch, packet_indexes), ); saturating_add_assign!(*dropped_packets_count, number_of_dropped_packets); @@ -3236,7 +3236,7 @@ mod tests { let transaction = system_transaction::transfer(&keypair, &pubkey, 1, blockhash); let mut p = Packet::from_data(None, &transaction).unwrap(); p.meta.port = packets_id; - DeserializedPacket::new(p, None).unwrap() + DeserializedPacket::new(p).unwrap() }) .collect_vec(); @@ -4022,7 +4022,7 @@ mod tests { Hash::new_unique(), ); let packet = Packet::from_data(None, &tx).unwrap(); - let deserialized_packet = DeserializedPacket::new(packet, None).unwrap(); + let deserialized_packet = DeserializedPacket::new(packet).unwrap(); let genesis_config_info = create_slow_genesis_config(10_000); let GenesisConfigInfo { @@ -4101,14 +4101,14 @@ mod tests { let transaction = system_transaction::transfer(&keypair, &pubkey, 1, fwd_block_hash); let mut packet = Packet::from_data(None, &transaction).unwrap(); packet.meta.flags |= PacketFlags::FORWARDED; - DeserializedPacket::new(packet, None).unwrap() + DeserializedPacket::new(packet).unwrap() }; let normal_block_hash = Hash::new_unique(); let normal_packet = { let transaction = system_transaction::transfer(&keypair, &pubkey, 1, normal_block_hash); let packet = Packet::from_data(None, &transaction).unwrap(); - DeserializedPacket::new(packet, None).unwrap() + DeserializedPacket::new(packet).unwrap() }; let mut unprocessed_packet_batches: UnprocessedPacketBatches = @@ -4229,7 +4229,7 @@ mod tests { packet_vector .into_iter() - .map(|p| DeserializedPacket::new(p, None).unwrap()) + .map(|p| DeserializedPacket::new(p).unwrap()) .collect() } diff --git a/core/src/unprocessed_packet_batches.rs b/core/src/unprocessed_packet_batches.rs index 63bbbbaad6..8f5949363a 100644 --- a/core/src/unprocessed_packet_batches.rs +++ b/core/src/unprocessed_packet_batches.rs @@ -1,7 +1,7 @@ use { min_max_heap::MinMaxHeap, solana_perf::packet::{limited_deserialize, Packet, PacketBatch}, - solana_runtime::bank::Bank, + solana_program_runtime::compute_budget::ComputeBudget, solana_sdk::{ hash::Hash, message::{Message, SanitizedVersionedMessage}, @@ -15,7 +15,6 @@ use { collections::{hash_map::Entry, HashMap}, mem::size_of, rc::Rc, - sync::Arc, }, thiserror::Error, }; @@ -31,6 +30,8 @@ pub enum DeserializedPacketError { SignatureOverflowed(usize), #[error("packet failed sanitization {0}")] SanitizeError(#[from] SanitizeError), + #[error("transaction failed prioritization")] + PrioritizationFailure, } #[derive(Debug, PartialEq, Eq)] @@ -39,7 +40,7 @@ pub struct ImmutableDeserializedPacket { transaction: SanitizedVersionedTransaction, message_hash: Hash, is_simple_vote: bool, - fee_per_cu: u64, + priority: u64, } impl ImmutableDeserializedPacket { @@ -63,8 +64,8 @@ impl ImmutableDeserializedPacket { self.is_simple_vote } - pub fn fee_per_cu(&self) -> u64 { - self.fee_per_cu + pub fn priority(&self) -> u64 { + self.priority } } @@ -77,22 +78,18 @@ pub struct DeserializedPacket { } impl DeserializedPacket { - pub fn new(packet: Packet, bank: Option<&Arc>) -> Result { - Self::new_internal(packet, bank, None) + pub fn new(packet: Packet) -> Result { + Self::new_internal(packet, None) } #[cfg(test)] - fn new_with_fee_per_cu( - packet: Packet, - fee_per_cu: u64, - ) -> Result { - Self::new_internal(packet, None, Some(fee_per_cu)) + fn new_with_priority(packet: Packet, priority: u64) -> Result { + Self::new_internal(packet, Some(priority)) } pub fn new_internal( packet: Packet, - bank: Option<&Arc>, - fee_per_cu: Option, + priority: Option, ) -> Result { let versioned_transaction: VersionedTransaction = limited_deserialize(&packet.data[0..packet.meta.size])?; @@ -101,18 +98,18 @@ impl DeserializedPacket { let message_hash = Message::hash_raw_message(message_bytes); let is_simple_vote = packet.meta.is_simple_vote_tx(); - let fee_per_cu = fee_per_cu.unwrap_or_else(|| { - bank.as_ref() - .map(|bank| compute_fee_per_cu(sanitized_transaction.get_message(), bank)) - .unwrap_or(0) - }); + // drop transaction if prioritization fails. + let priority = priority + .or_else(|| get_priority(sanitized_transaction.get_message())) + .ok_or(DeserializedPacketError::PrioritizationFailure)?; + Ok(Self { immutable_section: Rc::new(ImmutableDeserializedPacket { original_packet: packet, transaction: sanitized_transaction, message_hash, is_simple_vote, - fee_per_cu, + priority, }), forwarded: false, }) @@ -133,8 +130,8 @@ impl Ord for DeserializedPacket { fn cmp(&self, other: &Self) -> Ordering { match self .immutable_section() - .fee_per_cu() - .cmp(&other.immutable_section().fee_per_cu()) + .priority() + .cmp(&other.immutable_section().priority()) { Ordering::Equal => self .immutable_section() @@ -153,7 +150,7 @@ impl PartialOrd for ImmutableDeserializedPacket { impl Ord for ImmutableDeserializedPacket { fn cmp(&self, other: &Self) -> Ordering { - match self.fee_per_cu().cmp(&other.fee_per_cu()) { + match self.priority().cmp(&other.priority()) { Ordering::Equal => self.sender_stake().cmp(&other.sender_stake()), ordering => ordering, } @@ -193,8 +190,8 @@ impl UnprocessedPacketBatches { self.message_hash_to_transaction.clear(); } - /// Insert new `deserizlized_packet_batch` into inner `MinMaxHeap`, - /// weighted first by the fee-per-cu, then the stake of the sender. + /// Insert new `deserialized_packet_batch` into inner `MinMaxHeap`, + /// weighted first by the tx priority, then the stake of the sender. /// If buffer is at the max limit, the lowest weighted packet is dropped /// /// Returns tuple of number of packets dropped @@ -351,10 +348,9 @@ impl UnprocessedPacketBatches { pub fn deserialize_packets<'a>( packet_batch: &'a PacketBatch, packet_indexes: &'a [usize], - bank: Option<&'a Arc>, ) -> impl Iterator + 'a { packet_indexes.iter().filter_map(move |packet_index| { - DeserializedPacket::new(packet_batch.packets[*packet_index].clone(), bank).ok() + DeserializedPacket::new(packet_batch.packets[*packet_index].clone()).ok() }) } @@ -372,9 +368,17 @@ pub fn packet_message(packet: &Packet) -> Result<&[u8], DeserializedPacketError> .ok_or(DeserializedPacketError::SignatureOverflowed(sig_size)) } -/// Computes `(addition_fee + base_fee / requested_cu)` for `deserialized_packet` -fn compute_fee_per_cu(_message: &SanitizedVersionedMessage, _bank: &Bank) -> u64 { - 1 +fn get_priority(message: &SanitizedVersionedMessage) -> Option { + let mut compute_budget = ComputeBudget::default(); + let prioritization_fee_details = compute_budget + .process_instructions( + message.program_instructions_iter(), + false, // not request heap size + true, // use default units per instruction + true, // use changed prioritization fee + ) + .ok()?; + Some(prioritization_fee_details.get_priority()) } pub fn transactions_to_deserialized_packets( @@ -384,7 +388,7 @@ pub fn transactions_to_deserialized_packets( .iter() .map(|transaction| { let packet = Packet::from_data(None, transaction)?; - DeserializedPacket::new(packet, None) + DeserializedPacket::new(packet) }) .collect() } @@ -409,10 +413,10 @@ mod tests { if let Some(ip) = ip { packet.meta.addr = ip; } - DeserializedPacket::new(packet, None).unwrap() + DeserializedPacket::new(packet).unwrap() } - fn packet_with_fee_per_cu(fee_per_cu: u64) -> DeserializedPacket { + fn packet_with_priority(priority: u64) -> DeserializedPacket { let tx = system_transaction::transfer( &Keypair::new(), &solana_sdk::pubkey::new_rand(), @@ -420,7 +424,7 @@ mod tests { Hash::new_unique(), ); let packet = Packet::from_data(None, &tx).unwrap(); - DeserializedPacket::new_with_fee_per_cu(packet, fee_per_cu).unwrap() + DeserializedPacket::new_with_priority(packet, priority).unwrap() } #[test] @@ -441,10 +445,10 @@ mod tests { #[test] fn test_unprocessed_packet_batches_insert_minimum_packet_over_capacity() { let heavier_packet_weight = 2; - let heavier_packet = packet_with_fee_per_cu(heavier_packet_weight); + let heavier_packet = packet_with_priority(heavier_packet_weight); let lesser_packet_weight = heavier_packet_weight - 1; - let lesser_packet = packet_with_fee_per_cu(lesser_packet_weight); + let lesser_packet = packet_with_priority(lesser_packet_weight); // Test that the heavier packet is actually heavier let mut unprocessed_packet_batches = UnprocessedPacketBatches::with_capacity(2); diff --git a/docs/src/developing/programming-model/runtime.md b/docs/src/developing/programming-model/runtime.md index 929d5b789c..a6783a9fed 100644 --- a/docs/src/developing/programming-model/runtime.md +++ b/docs/src/developing/programming-model/runtime.md @@ -101,11 +101,15 @@ At runtime a program may log how much of the compute budget remains. See [debugging](developing/on-chain-programs/debugging.md#monitoring-compute-budget-consumption) for more information. -A transaction may request a specific level of `max_units` it is allowed to -consume by including a -[``ComputeBudgetInstruction`](https://github.com/solana-labs/solana/blob/db32549c00a1b5370fcaf128981ad3323bbd9570/sdk/src/compute_budget.rs#L39). -Transaction prioritization depends on the fee/compute-unit ratio so transaction -should request the minimum amount of compute units required for them to process. +A transaction may set the maximum number of compute units it is allowed to +consume by including a "request units" +[`ComputeBudgetInstruction`](https://github.com/solana-labs/solana/blob/db32549c00a1b5370fcaf128981ad3323bbd9570/sdk/src/compute_budget.rs#L39). +Note that a transaction's prioritization fee is calculated from multiplying the +number of compute units requested by the compute unit price (measured in +micro-lamports) set by the transaction. So transactions should request the +minimum amount of compute units required for execution to minimize fees. Also +note that fees are not adjusted when the number of requested compute units +exceeds the number of compute units consumed by an executed transaction. Compute Budget instructions don't require any accounts and don't consume any compute units to process. Transactions can only contain one of each type of @@ -131,14 +135,22 @@ Budget](#compute-budget). The transaction-wide compute budget applies the `max_units` cap to the entire transaction rather than to each instruction within the transaction. The default transaction-wide `max_units` will be calculated as the product of the number of -instructions in the transaction by the default per-instruction units, which is -currently 200k. During processing, the sum of the compute units used by each -instruction in the transaction must not exceed that value. This default value -attempts to retain existing behavior to avoid breaking clients. Transactions can -request a specific number of `max_units` via [Compute Budget](#compute-budget) -instructions. Clients should request only what they need; requesting the -minimum amount of units required to process the transaction will improve their -fee/compute-unit ratio, which transaction prioritization is based on. +instructions in the transaction (excluding [Compute Budget](#compute-budget) +instructions) by the default per-instruction units, which is currently 200k. +During processing, the sum of the compute units used by each instruction in the +transaction must not exceed that value. This default value attempts to retain +existing behavior to avoid breaking clients. Transactions can request a specific +number of `max_units` via [Compute Budget](#compute-budget) instructions. +Clients should request only what they need; requesting the minimum amount of +units required to process the transaction will reduce overall transaction cost, +which may include a prioritization-fee charged for every compute unit. + +Transaction prioritization is determined by the transactions prioritization fee +which itself is the product of the transaction's compute unit budget and its +compute unit price (measured in micro-lamports). The compute unit budget and +compute unit fee can be set by adding instructions created by the +`ComputeBudgetInstruction::request_compute_units` and +`ComputeBudgetInstruction::set_compute_unit_price` function, respectively. ## New Features diff --git a/program-runtime/src/compute_budget.rs b/program-runtime/src/compute_budget.rs index f181b71e7a..d8b655997f 100644 --- a/program-runtime/src/compute_budget.rs +++ b/program-runtime/src/compute_budget.rs @@ -1,10 +1,13 @@ -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, +use { + crate::prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, + solana_sdk::{ + borsh::try_from_slice_unchecked, + compute_budget::{self, ComputeBudgetInstruction}, + entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES, + instruction::{CompiledInstruction, InstructionError}, + pubkey::Pubkey, + transaction::TransactionError, + }, }; pub const DEFAULT_UNITS: u32 = 200_000; @@ -122,24 +125,21 @@ impl ComputeBudget { } } - pub fn process_message( + pub fn process_instructions<'a>( &mut self, - message: &SanitizedMessage, + instructions: impl Iterator, requestable_heap_size: bool, default_units_per_instruction: bool, - prioritization_fee_type_change: bool, - ) -> Result { - let mut num_instructions = message.instructions().len(); + support_set_compute_unit_price_ix: bool, + ) -> Result { + let mut num_non_compute_budget_instructions: usize = 0; let mut requested_units = None; let mut requested_heap_size = None; let mut prioritization_fee = None; - for (i, (program_id, instruction)) in message.program_instructions_iter().enumerate() { + for (i, (program_id, instruction)) in instructions.enumerate() { if compute_budget::check_id(program_id) { - // don't include request instructions in default max calc - num_instructions = num_instructions.saturating_sub(1); - - if prioritization_fee_type_change { + if support_set_compute_unit_price_ix { let invalid_instruction_data_error = TransactionError::InstructionError( i as u8, InstructionError::InvalidInstructionData, @@ -159,7 +159,8 @@ impl ComputeBudget { return Err(duplicate_instruction_error); } requested_units = Some(units as u64); - prioritization_fee = Some(additional_fee as u64); + prioritization_fee = + Some(PrioritizationFeeType::Deprecated(additional_fee as u64)); } Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => { if requested_heap_size.is_some() { @@ -173,11 +174,12 @@ impl ComputeBudget { } requested_units = Some(units as u64); } - Ok(ComputeBudgetInstruction::SetPrioritizationFee(fee)) => { + Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => { if prioritization_fee.is_some() { return Err(duplicate_instruction_error); } - prioritization_fee = Some(fee); + prioritization_fee = + Some(PrioritizationFeeType::ComputeUnitPrice(micro_lamports)); } _ => return Err(invalid_instruction_data_error), } @@ -188,7 +190,8 @@ impl ComputeBudget { additional_fee, }) => { requested_units = Some(units as u64); - prioritization_fee = Some(additional_fee as u64); + prioritization_fee = + Some(PrioritizationFeeType::Deprecated(additional_fee as u64)); } Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => { requested_heap_size = Some((bytes, 0)); @@ -201,6 +204,10 @@ impl ComputeBudget { } } } + } else { + // only include non-request instructions in default max calc + num_non_compute_budget_instructions = + num_non_compute_budget_instructions.saturating_add(1); } } @@ -219,15 +226,21 @@ impl ComputeBudget { } self.max_units = if default_units_per_instruction { - requested_units - .or_else(|| Some(num_instructions.saturating_mul(DEFAULT_UNITS as usize) as u64)) + requested_units.or_else(|| { + Some( + num_non_compute_budget_instructions.saturating_mul(DEFAULT_UNITS as usize) + as u64, + ) + }) } else { requested_units } .unwrap_or(MAX_UNITS as u64) .min(MAX_UNITS as u64); - Ok(prioritization_fee.unwrap_or(0)) + Ok(prioritization_fee + .map(|fee_type| PrioritizationFeeDetails::new(fee_type, self.max_units)) + .unwrap_or_default()) } } @@ -258,7 +271,7 @@ mod tests { } macro_rules! test { - ( $instructions: expr, $expected_error: expr, $expected_budget: expr, $type_change: expr ) => { + ( $instructions: expr, $expected_result: expr, $expected_budget: expr, $type_change: expr ) => { let payer_keypair = Keypair::new(); let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new( &[&payer_keypair], @@ -266,21 +279,26 @@ mod tests { Hash::default(), )); let mut compute_budget = ComputeBudget::default(); - let result = compute_budget.process_message(&tx.message(), true, true, $type_change); - assert_eq!($expected_error, result); + let result = compute_budget.process_instructions( + tx.message().program_instructions_iter(), + true, + true, + $type_change, + ); + assert_eq!($expected_result, result); assert_eq!(compute_budget, $expected_budget); }; - ( $instructions: expr, $expected_error: expr, $expected_budget: expr) => { - test!($instructions, $expected_error, $expected_budget, true); + ( $instructions: expr, $expected_result: expr, $expected_budget: expr) => { + test!($instructions, $expected_result, $expected_budget, true); }; } #[test] - fn test_process_mesage() { + fn test_process_instructions() { // Units test!( &[], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: 0, ..ComputeBudget::default() @@ -291,7 +309,7 @@ mod tests { ComputeBudgetInstruction::request_units(1), Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: 1, ..ComputeBudget::default() @@ -302,7 +320,7 @@ mod tests { ComputeBudgetInstruction::request_units(MAX_UNITS + 1), Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: MAX_UNITS as u64, ..ComputeBudget::default() @@ -313,7 +331,7 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_units(MAX_UNITS), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: MAX_UNITS as u64, ..ComputeBudget::default() @@ -326,7 +344,7 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_units(1), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: 1, ..ComputeBudget::default() @@ -340,7 +358,7 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_units(1), // ignored ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: DEFAULT_UNITS as u64 * 3, ..ComputeBudget::default() @@ -351,7 +369,10 @@ mod tests { // Prioritization fee test!( &[request_units_deprecated(1, 42)], - Ok(42), + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::Deprecated(42), + 1, + )), ComputeBudget { max_units: 1, ..ComputeBudget::default() @@ -362,9 +383,12 @@ mod tests { test!( &[ ComputeBudgetInstruction::request_units(1), - ComputeBudgetInstruction::set_prioritization_fee(42) + ComputeBudgetInstruction::set_compute_unit_price(42) ], - Ok(42), + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(42), + 1 + )), ComputeBudget { max_units: 1, ..ComputeBudget::default() @@ -373,7 +397,10 @@ mod tests { test!( &[request_units_deprecated(1, u32::MAX)], - Ok(u32::MAX as u64), + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::Deprecated(u32::MAX as u64), + 1 + )), ComputeBudget { max_units: 1, ..ComputeBudget::default() @@ -384,7 +411,7 @@ mod tests { // HeapFrame test!( &[], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: 0, ..ComputeBudget::default() @@ -395,7 +422,7 @@ mod tests { ComputeBudgetInstruction::request_heap_frame(40 * 1024), Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: DEFAULT_UNITS as u64, heap_size: Some(40 * 1024), @@ -440,7 +467,7 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: DEFAULT_UNITS as u64, heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), @@ -472,7 +499,7 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ], - Ok(0), + Ok(PrioritizationFeeDetails::default()), ComputeBudget { max_units: DEFAULT_UNITS as u64 * 7, ..ComputeBudget::default() @@ -485,9 +512,12 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), ComputeBudgetInstruction::request_units(MAX_UNITS), - ComputeBudgetInstruction::set_prioritization_fee(u64::MAX), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), ], - Ok(u64::MAX), + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(u64::MAX), + MAX_UNITS as u64, + )), ComputeBudget { max_units: MAX_UNITS as u64, heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), @@ -500,7 +530,7 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), ComputeBudgetInstruction::request_units(MAX_UNITS), - ComputeBudgetInstruction::set_prioritization_fee(u64::MAX), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), ], Err(TransactionError::InstructionError( 0, @@ -515,9 +545,12 @@ mod tests { Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ComputeBudgetInstruction::request_units(1), ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES), - ComputeBudgetInstruction::set_prioritization_fee(u64::MAX), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), ], - Ok(u64::MAX), + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(u64::MAX), + 1 + )), ComputeBudget { max_units: 1, heap_size: Some(MAX_HEAP_FRAME_BYTES as usize), @@ -531,7 +564,10 @@ mod tests { request_units_deprecated(MAX_UNITS, u32::MAX), ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32), ], - Ok(u32::MAX as u64), + Ok(PrioritizationFeeDetails::new( + PrioritizationFeeType::Deprecated(u32::MAX as u64), + MAX_UNITS as u64, + )), ComputeBudget { max_units: MAX_UNITS as u64, heap_size: Some(MIN_HEAP_FRAME_BYTES as usize), @@ -564,8 +600,8 @@ mod tests { test!( &[ Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), - ComputeBudgetInstruction::set_prioritization_fee(0), - ComputeBudgetInstruction::set_prioritization_fee(u64::MAX), + ComputeBudgetInstruction::set_compute_unit_price(0), + ComputeBudgetInstruction::set_compute_unit_price(u64::MAX), ], Err(TransactionError::DuplicateInstruction(2)), ComputeBudget::default() diff --git a/program-runtime/src/lib.rs b/program-runtime/src/lib.rs index 421fca20a9..cb135bb112 100644 --- a/program-runtime/src/lib.rs +++ b/program-runtime/src/lib.rs @@ -8,6 +8,7 @@ pub mod invoke_context; pub mod log_collector; pub mod neon_evm_program; pub mod pre_account; +pub mod prioritization_fee; pub mod stable_log; pub mod sysvar_cache; pub mod timings; diff --git a/program-runtime/src/prioritization_fee.rs b/program-runtime/src/prioritization_fee.rs new file mode 100644 index 0000000000..0898b33ace --- /dev/null +++ b/program-runtime/src/prioritization_fee.rs @@ -0,0 +1,204 @@ +/// There are 10^6 micro-lamports in one lamport +const MICRO_LAMPORTS_PER_LAMPORT: u64 = 1_000_000; + +type MicroLamports = u128; + +pub enum PrioritizationFeeType { + ComputeUnitPrice(u64), + Deprecated(u64), +} + +#[derive(Default, Debug, PartialEq)] +pub struct PrioritizationFeeDetails { + fee: u64, + priority: u64, +} + +impl PrioritizationFeeDetails { + pub fn new(fee_type: PrioritizationFeeType, max_compute_units: u64) -> Self { + match fee_type { + PrioritizationFeeType::Deprecated(fee) => { + let priority = if max_compute_units == 0 { + 0 + } else { + let micro_lamport_fee: MicroLamports = + (fee as u128).saturating_mul(MICRO_LAMPORTS_PER_LAMPORT as u128); + let priority = micro_lamport_fee.saturating_div(max_compute_units as u128); + u64::try_from(priority).unwrap_or(u64::MAX) + }; + + Self { fee, priority } + } + PrioritizationFeeType::ComputeUnitPrice(cu_price) => { + let fee = { + let micro_lamport_fee: MicroLamports = + (cu_price as u128).saturating_mul(max_compute_units as u128); + let fee = micro_lamport_fee + .saturating_add(MICRO_LAMPORTS_PER_LAMPORT.saturating_sub(1) as u128) + .saturating_div(MICRO_LAMPORTS_PER_LAMPORT as u128); + u64::try_from(fee).unwrap_or(u64::MAX) + }; + + Self { + fee, + priority: cu_price, + } + } + } + } + + pub fn get_fee(&self) -> u64 { + self.fee + } + + pub fn get_priority(&self) -> u64 { + self.priority + } +} + +#[cfg(test)] +mod test { + use super::{PrioritizationFeeDetails as FeeDetails, PrioritizationFeeType as FeeType, *}; + + #[test] + fn test_new_with_no_fee() { + for compute_units in [0, 1, MICRO_LAMPORTS_PER_LAMPORT, u64::MAX] { + assert_eq!( + FeeDetails::new(FeeType::ComputeUnitPrice(0), compute_units), + FeeDetails::default(), + ); + assert_eq!( + FeeDetails::new(FeeType::Deprecated(0), compute_units), + FeeDetails::default(), + ); + } + } + + #[test] + fn test_new_with_compute_unit_price() { + assert_eq!( + FeeDetails::new(FeeType::ComputeUnitPrice(MICRO_LAMPORTS_PER_LAMPORT - 1), 1), + FeeDetails { + fee: 1, + priority: MICRO_LAMPORTS_PER_LAMPORT - 1, + }, + "should round up (<1.0) lamport fee to 1 lamport" + ); + + assert_eq!( + FeeDetails::new(FeeType::ComputeUnitPrice(MICRO_LAMPORTS_PER_LAMPORT), 1), + FeeDetails { + fee: 1, + priority: MICRO_LAMPORTS_PER_LAMPORT, + }, + ); + + assert_eq!( + FeeDetails::new(FeeType::ComputeUnitPrice(MICRO_LAMPORTS_PER_LAMPORT + 1), 1), + FeeDetails { + fee: 2, + priority: MICRO_LAMPORTS_PER_LAMPORT + 1, + }, + "should round up (>1.0) lamport fee to 2 lamports" + ); + + assert_eq!( + FeeDetails::new(FeeType::ComputeUnitPrice(200), 100_000), + FeeDetails { + fee: 20, + priority: 200, + }, + ); + + assert_eq!( + FeeDetails::new( + FeeType::ComputeUnitPrice(MICRO_LAMPORTS_PER_LAMPORT), + u64::MAX + ), + FeeDetails { + fee: u64::MAX, + priority: MICRO_LAMPORTS_PER_LAMPORT, + }, + ); + + assert_eq!( + FeeDetails::new(FeeType::ComputeUnitPrice(u64::MAX), u64::MAX), + FeeDetails { + fee: u64::MAX, + priority: u64::MAX, + }, + ); + } + + #[test] + fn test_new_with_deprecated_fee() { + assert_eq!( + FeeDetails::new(FeeType::Deprecated(1), MICRO_LAMPORTS_PER_LAMPORT / 2 - 1), + FeeDetails { + fee: 1, + priority: 2, + }, + "should round down fee rate of (>2.0) to priority value 1" + ); + + assert_eq!( + FeeDetails::new(FeeType::Deprecated(1), MICRO_LAMPORTS_PER_LAMPORT / 2), + FeeDetails { + fee: 1, + priority: 2, + }, + ); + + assert_eq!( + FeeDetails::new(FeeType::Deprecated(1), MICRO_LAMPORTS_PER_LAMPORT / 2 + 1), + FeeDetails { + fee: 1, + priority: 1, + }, + "should round down fee rate of (<2.0) to priority value 1" + ); + + assert_eq!( + FeeDetails::new(FeeType::Deprecated(1), MICRO_LAMPORTS_PER_LAMPORT), + FeeDetails { + fee: 1, + priority: 1, + }, + ); + + assert_eq!( + FeeDetails::new(FeeType::Deprecated(42), 42 * MICRO_LAMPORTS_PER_LAMPORT), + FeeDetails { + fee: 42, + priority: 1, + }, + ); + + assert_eq!( + FeeDetails::new(FeeType::Deprecated(420), 42 * MICRO_LAMPORTS_PER_LAMPORT), + FeeDetails { + fee: 420, + priority: 10, + }, + ); + + assert_eq!( + FeeDetails::new( + FeeType::Deprecated(u64::MAX), + 2 * MICRO_LAMPORTS_PER_LAMPORT + ), + FeeDetails { + fee: u64::MAX, + priority: u64::MAX / 2, + }, + ); + + assert_eq!( + FeeDetails::new(FeeType::Deprecated(u64::MAX), u64::MAX), + FeeDetails { + fee: u64::MAX, + priority: MICRO_LAMPORTS_PER_LAMPORT, + }, + ); + } +} diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 1becf1f2ea..facf005420 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -3489,7 +3489,7 @@ fn test_program_fees() { ); let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap(); - let expected_max_fee = Bank::calculate_fee( + let expected_normal_fee = Bank::calculate_fee( &sanitized_message, congestion_multiplier, &fee_structure, @@ -3500,32 +3500,31 @@ fn test_program_fees() { .send_and_confirm_message(&[&mint_keypair], message) .unwrap(); let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); - assert_eq!(pre_balance - post_balance, expected_max_fee); + assert_eq!(pre_balance - post_balance, expected_normal_fee); let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); let message = Message::new( &[ - ComputeBudgetInstruction::request_units(100), - ComputeBudgetInstruction::set_prioritization_fee(42), + ComputeBudgetInstruction::set_compute_unit_price(1), Instruction::new_with_bytes(program_id, &[], vec![]), ], Some(&mint_keypair.pubkey()), ); let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap(); - let expected_min_fee = Bank::calculate_fee( + let expected_prioritized_fee = Bank::calculate_fee( &sanitized_message, congestion_multiplier, &fee_structure, true, true, ); - assert!(expected_min_fee < expected_max_fee); + assert!(expected_normal_fee < expected_prioritized_fee); bank_client .send_and_confirm_message(&[&mint_keypair], message) .unwrap(); let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap(); - assert_eq!(pre_balance - post_balance, expected_min_fee); + assert_eq!(pre_balance - post_balance, expected_prioritized_fee); } #[test] diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index d6d51f1eef..38b64358d6 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -31,7 +31,7 @@ use { account_utils::StateMut, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, clock::{BankId, Slot, INITIAL_RENT_EPOCH}, - feature_set::{self, prioritization_fee_type_change, tx_wide_compute_cap, FeatureSet}, + feature_set::{self, add_set_compute_unit_price_ix, tx_wide_compute_cap, FeatureSet}, fee::FeeStructure, genesis_config::ClusterType, hash::Hash, @@ -525,7 +525,7 @@ impl Accounts { lamports_per_signature, fee_structure, feature_set.is_active(&tx_wide_compute_cap::id()), - feature_set.is_active(&prioritization_fee_type_change::id()), + feature_set.is_active(&add_set_compute_unit_price_ix::id()), ) } else { return (Err(TransactionError::BlockhashNotFound), None); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index f4828d8b5b..154cb08899 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -107,8 +107,9 @@ use { epoch_schedule::EpochSchedule, feature, feature_set::{ - self, default_units_per_instruction, disable_fee_calculator, nonce_must_be_writable, - prioritization_fee_type_change, requestable_heap_size, tx_wide_compute_cap, FeatureSet, + self, add_set_compute_unit_price_ix, default_units_per_instruction, + disable_fee_calculator, nonce_must_be_writable, requestable_heap_size, + tx_wide_compute_cap, FeatureSet, }, fee::FeeStructure, fee_calculator::{FeeCalculator, FeeRateGovernor}, @@ -3638,7 +3639,7 @@ impl Bank { &self.fee_structure, self.feature_set.is_active(&tx_wide_compute_cap::id()), self.feature_set - .is_active(&prioritization_fee_type_change::id()), + .is_active(&add_set_compute_unit_price_ix::id()), )) } @@ -3653,7 +3654,7 @@ impl Bank { &self.fee_structure, self.feature_set.is_active(&tx_wide_compute_cap::id()), self.feature_set - .is_active(&prioritization_fee_type_change::id()), + .is_active(&add_set_compute_unit_price_ix::id()), ) } @@ -4389,11 +4390,11 @@ impl Bank { if tx_wide_compute_cap { let mut compute_budget_process_transaction_time = Measure::start("compute_budget_process_transaction_time"); - let process_transaction_result = compute_budget.process_message( - tx.message(), + let process_transaction_result = compute_budget.process_instructions( + tx.message().program_instructions_iter(), feature_set.is_active(&requestable_heap_size::id()), feature_set.is_active(&default_units_per_instruction::id()), - feature_set.is_active(&prioritization_fee_type_change::id()), + feature_set.is_active(&add_set_compute_unit_price_ix::id()), ); compute_budget_process_transaction_time.stop(); saturating_add_assign!( @@ -4609,7 +4610,7 @@ impl Bank { lamports_per_signature: u64, fee_structure: &FeeStructure, tx_wide_compute_cap: bool, - prioritization_fee_type_change: bool, + support_set_compute_unit_price_ix: bool, ) -> u64 { if tx_wide_compute_cap { // Fee based on compute units and signatures @@ -4622,9 +4623,15 @@ impl Bank { }; let mut compute_budget = ComputeBudget::default(); - let prioritization_fee = compute_budget - .process_message(message, false, false, prioritization_fee_type_change) + let prioritization_fee_details = compute_budget + .process_instructions( + message.program_instructions_iter(), + false, + false, + support_set_compute_unit_price_ix, + ) .unwrap_or_default(); + let prioritization_fee = prioritization_fee_details.get_fee(); let signature_fee = Self::get_num_signatures_in_message(message) .saturating_mul(fee_structure.lamports_per_signature); let write_lock_fee = Self::get_num_write_locks_in_message(message) @@ -4691,7 +4698,7 @@ impl Bank { &self.fee_structure, self.feature_set.is_active(&tx_wide_compute_cap::id()), self.feature_set - .is_active(&prioritization_fee_type_change::id()), + .is_active(&add_set_compute_unit_price_ix::id()), ); // In case of instruction error, even though no accounts @@ -7242,7 +7249,11 @@ pub(crate) mod tests { status_cache::MAX_CACHE_ENTRIES, }, crossbeam_channel::{bounded, unbounded}, - solana_program_runtime::invoke_context::InvokeContext, + solana_program_runtime::{ + compute_budget::MAX_UNITS, + invoke_context::InvokeContext, + prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType}, + }, solana_sdk::{ account::Account, bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable, @@ -16765,26 +16776,19 @@ pub(crate) mod tests { // Explicit fee schedule - let expected_fee_structure = &[ - // (units requested, fee in SOL), - (0, 0.0), - (5_000, 0.0), - (10_000, 0.0), - (100_000, 0.0), - (300_000, 0.0), - (500_000, 0.0), - (700_000, 0.0), - (900_000, 0.0), - (1_100_000, 0.0), - (1_300_000, 0.0), - (1_500_000, 0.0), // ComputeBudget capped - ]; - for pair in expected_fee_structure.iter() { - const PRIORITIZATION_FEE: u64 = 42; + for requested_compute_units in [ + 0, 5_000, 10_000, 100_000, 300_000, 500_000, 700_000, 900_000, 1_100_000, 1_300_000, + MAX_UNITS, + ] { + const PRIORITIZATION_FEE_RATE: u64 = 42; + let prioritization_fee_details = PrioritizationFeeDetails::new( + PrioritizationFeeType::ComputeUnitPrice(PRIORITIZATION_FEE_RATE), + requested_compute_units as u64, + ); let message = SanitizedMessage::try_from(Message::new( &[ - ComputeBudgetInstruction::request_units(pair.0), - ComputeBudgetInstruction::set_prioritization_fee(PRIORITIZATION_FEE), + ComputeBudgetInstruction::request_units(requested_compute_units), + ComputeBudgetInstruction::set_compute_unit_price(PRIORITIZATION_FEE_RATE), Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]), ], Some(&Pubkey::new_unique()), @@ -16793,7 +16797,7 @@ pub(crate) mod tests { let fee = Bank::calculate_fee(&message, 1, &fee_structure, true, true); assert_eq!( fee, - sol_to_lamports(pair.1) + lamports_per_signature + PRIORITIZATION_FEE + lamports_per_signature + prioritization_fee_details.get_fee() ); } } diff --git a/sdk/src/compute_budget.rs b/sdk/src/compute_budget.rs index 2318d6bce5..19c510a7e7 100644 --- a/sdk/src/compute_budget.rs +++ b/sdk/src/compute_budget.rs @@ -35,9 +35,9 @@ pub enum ComputeBudgetInstruction { /// Request a specific maximum number of compute units the transaction is /// allowed to consume and an additional fee to pay. RequestUnits(u32), - /// Additional fee in lamports to charge the payer, used for transaction - /// prioritization - SetPrioritizationFee(u64), + /// Set a compute unit price in "micro-lamports" to pay a higher transaction + /// fee for higher transaction prioritization. + SetComputeUnitPrice(u64), } impl ComputeBudgetInstruction { @@ -51,8 +51,8 @@ impl ComputeBudgetInstruction { Instruction::new_with_borsh(id(), &Self::RequestUnits(units), vec![]) } - /// Create a `ComputeBudgetInstruction::SetPrioritizationFee` `Instruction` - pub fn set_prioritization_fee(fee: u64) -> Instruction { - Instruction::new_with_borsh(id(), &Self::SetPrioritizationFee(fee), vec![]) + /// Create a `ComputeBudgetInstruction::SetComputeUnitPrice` `Instruction` + pub fn set_compute_unit_price(micro_lamports: u64) -> Instruction { + Instruction::new_with_borsh(id(), &Self::SetComputeUnitPrice(micro_lamports), vec![]) } } diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index ad442c7d41..63f12fd987 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -396,10 +396,14 @@ pub mod stake_raise_minimum_delegation_to_1_sol { solana_sdk::declare_id!("4xmyBuR2VCXzy9H6qYpH9ckfgnTuMDQFPFBfTs4eBCY1"); } -pub mod prioritization_fee_type_change { +pub mod add_set_compute_unit_price_ix { solana_sdk::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g"); } +pub mod disable_deploy_of_alloc_free_syscall { + solana_sdk::declare_id!("79HWsX9rpnnJBPcdNURVqygpMAfxdrAirzAGAVmf92im"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -492,7 +496,8 @@ lazy_static! { (stake_allow_zero_undelegated_amount::id(), "Allow zero-lamport undelegated amount for initialized stakes #24670"), (require_static_program_ids_in_transaction::id(), "require static program ids in versioned transactions"), (stake_raise_minimum_delegation_to_1_sol::id(), "Raise minimum stake delegation to 1.0 SOL #24357"), - (prioritization_fee_type_change::id(), "Switch compute budget to prioritization fee"), + (add_set_compute_unit_price_ix::id(), "add compute budget ix for setting a compute unit price"), + (disable_deploy_of_alloc_free_syscall::id(), "disable new deployments of deprecated sol_alloc_free_ syscall"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()