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 <justin@solana.com>
This commit is contained in:
parent
52db2e19bc
commit
b1b3702e6d
|
@ -89,7 +89,7 @@ pub enum CliCommand {
|
|||
timeout: Duration,
|
||||
blockhash: Option<Hash>,
|
||||
print_timestamp: bool,
|
||||
prioritization_fee: Option<u64>,
|
||||
compute_unit_price: Option<u64>,
|
||||
},
|
||||
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,
|
||||
|
|
|
@ -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<Hash>,
|
||||
print_timestamp: bool,
|
||||
prioritization_fee: &Option<u64>,
|
||||
compute_unit_price: &Option<u64>,
|
||||
) -> 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()],
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Bank>>) -> Result<Self, DeserializedPacketError> {
|
||||
Self::new_internal(packet, bank, None)
|
||||
pub fn new(packet: Packet) -> Result<Self, DeserializedPacketError> {
|
||||
Self::new_internal(packet, None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn new_with_fee_per_cu(
|
||||
packet: Packet,
|
||||
fee_per_cu: u64,
|
||||
) -> Result<Self, DeserializedPacketError> {
|
||||
Self::new_internal(packet, None, Some(fee_per_cu))
|
||||
fn new_with_priority(packet: Packet, priority: u64) -> Result<Self, DeserializedPacketError> {
|
||||
Self::new_internal(packet, Some(priority))
|
||||
}
|
||||
|
||||
pub fn new_internal(
|
||||
packet: Packet,
|
||||
bank: Option<&Arc<Bank>>,
|
||||
fee_per_cu: Option<u64>,
|
||||
priority: Option<u64>,
|
||||
) -> Result<Self, DeserializedPacketError> {
|
||||
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<DeserializedPacket>`,
|
||||
/// weighted first by the fee-per-cu, then the stake of the sender.
|
||||
/// Insert new `deserialized_packet_batch` into inner `MinMaxHeap<DeserializedPacket>`,
|
||||
/// 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<Bank>>,
|
||||
) -> impl Iterator<Item = DeserializedPacket> + '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<u64> {
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<Item = (&'a Pubkey, &'a CompiledInstruction)>,
|
||||
requestable_heap_size: bool,
|
||||
default_units_per_instruction: bool,
|
||||
prioritization_fee_type_change: bool,
|
||||
) -> Result<u64, TransactionError> {
|
||||
let mut num_instructions = message.instructions().len();
|
||||
support_set_compute_unit_price_ix: bool,
|
||||
) -> Result<PrioritizationFeeDetails, TransactionError> {
|
||||
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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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![])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Pubkey, &'static str> = [
|
||||
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue