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,
|
timeout: Duration,
|
||||||
blockhash: Option<Hash>,
|
blockhash: Option<Hash>,
|
||||||
print_timestamp: bool,
|
print_timestamp: bool,
|
||||||
prioritization_fee: Option<u64>,
|
compute_unit_price: Option<u64>,
|
||||||
},
|
},
|
||||||
Rent {
|
Rent {
|
||||||
data_length: usize,
|
data_length: usize,
|
||||||
|
@ -877,7 +877,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
timeout,
|
timeout,
|
||||||
blockhash,
|
blockhash,
|
||||||
print_timestamp,
|
print_timestamp,
|
||||||
prioritization_fee,
|
compute_unit_price,
|
||||||
} => process_ping(
|
} => process_ping(
|
||||||
&rpc_client,
|
&rpc_client,
|
||||||
config,
|
config,
|
||||||
|
@ -886,7 +886,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||||
timeout,
|
timeout,
|
||||||
blockhash,
|
blockhash,
|
||||||
*print_timestamp,
|
*print_timestamp,
|
||||||
prioritization_fee,
|
compute_unit_price,
|
||||||
),
|
),
|
||||||
CliCommand::Rent {
|
CliCommand::Rent {
|
||||||
data_length,
|
data_length,
|
||||||
|
|
|
@ -271,12 +271,11 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||||
.help("Wait up to timeout seconds for transaction confirmation"),
|
.help("Wait up to timeout seconds for transaction confirmation"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("prioritization-fee")
|
Arg::with_name("compute_unit_price")
|
||||||
.long("prioritization-fee")
|
.long("compute-unit-price")
|
||||||
.alias("additional-fee")
|
.value_name("MICRO-LAMPORTS")
|
||||||
.value_name("NUMBER")
|
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Set prioritization-fee for transaction"),
|
.help("Set the price in micro-lamports of each transaction compute unit"),
|
||||||
)
|
)
|
||||||
.arg(blockhash_arg()),
|
.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 timeout = Duration::from_secs(value_t_or_exit!(matches, "timeout", u64));
|
||||||
let blockhash = value_of(matches, BLOCKHASH_ARG.name);
|
let blockhash = value_of(matches, BLOCKHASH_ARG.name);
|
||||||
let print_timestamp = matches.is_present("print_timestamp");
|
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 {
|
Ok(CliCommandInfo {
|
||||||
command: CliCommand::Ping {
|
command: CliCommand::Ping {
|
||||||
interval,
|
interval,
|
||||||
|
@ -531,7 +530,7 @@ pub fn parse_cluster_ping(
|
||||||
timeout,
|
timeout,
|
||||||
blockhash,
|
blockhash,
|
||||||
print_timestamp,
|
print_timestamp,
|
||||||
prioritization_fee,
|
compute_unit_price,
|
||||||
},
|
},
|
||||||
signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
|
signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
|
||||||
})
|
})
|
||||||
|
@ -1364,7 +1363,7 @@ pub fn process_ping(
|
||||||
timeout: &Duration,
|
timeout: &Duration,
|
||||||
fixed_blockhash: &Option<Hash>,
|
fixed_blockhash: &Option<Hash>,
|
||||||
print_timestamp: bool,
|
print_timestamp: bool,
|
||||||
prioritization_fee: &Option<u64>,
|
compute_unit_price: &Option<u64>,
|
||||||
) -> ProcessResult {
|
) -> ProcessResult {
|
||||||
let (signal_sender, signal_receiver) = unbounded();
|
let (signal_sender, signal_receiver) = unbounded();
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
|
@ -1409,9 +1408,9 @@ pub fn process_ping(
|
||||||
&to,
|
&to,
|
||||||
lamports,
|
lamports,
|
||||||
)];
|
)];
|
||||||
if let Some(prioritization_fee) = prioritization_fee {
|
if let Some(compute_unit_price) = compute_unit_price {
|
||||||
ixs.push(ComputeBudgetInstruction::set_prioritization_fee(
|
ixs.push(ComputeBudgetInstruction::set_compute_unit_price(
|
||||||
*prioritization_fee,
|
*compute_unit_price,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Message::new(&ixs, Some(&config.signers[0].pubkey()))
|
Message::new(&ixs, Some(&config.signers[0].pubkey()))
|
||||||
|
@ -2338,7 +2337,7 @@ mod tests {
|
||||||
Hash::from_str("4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX").unwrap()
|
Hash::from_str("4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX").unwrap()
|
||||||
),
|
),
|
||||||
print_timestamp: true,
|
print_timestamp: true,
|
||||||
prioritization_fee: None,
|
compute_unit_price: None,
|
||||||
},
|
},
|
||||||
signers: vec![default_keypair.into()],
|
signers: vec![default_keypair.into()],
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ fn insert_packet_batches(
|
||||||
} else {
|
} else {
|
||||||
build_packet_batch(packet_per_batch_count)
|
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);
|
unprocessed_packet_batches.insert_batch(deserialized_packets);
|
||||||
});
|
});
|
||||||
timer.stop();
|
timer.stop();
|
||||||
|
|
|
@ -2110,7 +2110,7 @@ impl BankingStage {
|
||||||
|
|
||||||
let number_of_dropped_packets = unprocessed_packet_batches.insert_batch(
|
let number_of_dropped_packets = unprocessed_packet_batches.insert_batch(
|
||||||
// Passing `None` for bank for now will make all packet weights 0
|
// 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);
|
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 transaction = system_transaction::transfer(&keypair, &pubkey, 1, blockhash);
|
||||||
let mut p = Packet::from_data(None, &transaction).unwrap();
|
let mut p = Packet::from_data(None, &transaction).unwrap();
|
||||||
p.meta.port = packets_id;
|
p.meta.port = packets_id;
|
||||||
DeserializedPacket::new(p, None).unwrap()
|
DeserializedPacket::new(p).unwrap()
|
||||||
})
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
|
|
||||||
|
@ -4022,7 +4022,7 @@ mod tests {
|
||||||
Hash::new_unique(),
|
Hash::new_unique(),
|
||||||
);
|
);
|
||||||
let packet = Packet::from_data(None, &tx).unwrap();
|
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 genesis_config_info = create_slow_genesis_config(10_000);
|
||||||
let GenesisConfigInfo {
|
let GenesisConfigInfo {
|
||||||
|
@ -4101,14 +4101,14 @@ mod tests {
|
||||||
let transaction = system_transaction::transfer(&keypair, &pubkey, 1, fwd_block_hash);
|
let transaction = system_transaction::transfer(&keypair, &pubkey, 1, fwd_block_hash);
|
||||||
let mut packet = Packet::from_data(None, &transaction).unwrap();
|
let mut packet = Packet::from_data(None, &transaction).unwrap();
|
||||||
packet.meta.flags |= PacketFlags::FORWARDED;
|
packet.meta.flags |= PacketFlags::FORWARDED;
|
||||||
DeserializedPacket::new(packet, None).unwrap()
|
DeserializedPacket::new(packet).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let normal_block_hash = Hash::new_unique();
|
let normal_block_hash = Hash::new_unique();
|
||||||
let normal_packet = {
|
let normal_packet = {
|
||||||
let transaction = system_transaction::transfer(&keypair, &pubkey, 1, normal_block_hash);
|
let transaction = system_transaction::transfer(&keypair, &pubkey, 1, normal_block_hash);
|
||||||
let packet = Packet::from_data(None, &transaction).unwrap();
|
let packet = Packet::from_data(None, &transaction).unwrap();
|
||||||
DeserializedPacket::new(packet, None).unwrap()
|
DeserializedPacket::new(packet).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut unprocessed_packet_batches: UnprocessedPacketBatches =
|
let mut unprocessed_packet_batches: UnprocessedPacketBatches =
|
||||||
|
@ -4229,7 +4229,7 @@ mod tests {
|
||||||
|
|
||||||
packet_vector
|
packet_vector
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|p| DeserializedPacket::new(p, None).unwrap())
|
.map(|p| DeserializedPacket::new(p).unwrap())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
min_max_heap::MinMaxHeap,
|
min_max_heap::MinMaxHeap,
|
||||||
solana_perf::packet::{limited_deserialize, Packet, PacketBatch},
|
solana_perf::packet::{limited_deserialize, Packet, PacketBatch},
|
||||||
solana_runtime::bank::Bank,
|
solana_program_runtime::compute_budget::ComputeBudget,
|
||||||
solana_sdk::{
|
solana_sdk::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
message::{Message, SanitizedVersionedMessage},
|
message::{Message, SanitizedVersionedMessage},
|
||||||
|
@ -15,7 +15,6 @@ use {
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{hash_map::Entry, HashMap},
|
||||||
mem::size_of,
|
mem::size_of,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
|
||||||
},
|
},
|
||||||
thiserror::Error,
|
thiserror::Error,
|
||||||
};
|
};
|
||||||
|
@ -31,6 +30,8 @@ pub enum DeserializedPacketError {
|
||||||
SignatureOverflowed(usize),
|
SignatureOverflowed(usize),
|
||||||
#[error("packet failed sanitization {0}")]
|
#[error("packet failed sanitization {0}")]
|
||||||
SanitizeError(#[from] SanitizeError),
|
SanitizeError(#[from] SanitizeError),
|
||||||
|
#[error("transaction failed prioritization")]
|
||||||
|
PrioritizationFailure,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -39,7 +40,7 @@ pub struct ImmutableDeserializedPacket {
|
||||||
transaction: SanitizedVersionedTransaction,
|
transaction: SanitizedVersionedTransaction,
|
||||||
message_hash: Hash,
|
message_hash: Hash,
|
||||||
is_simple_vote: bool,
|
is_simple_vote: bool,
|
||||||
fee_per_cu: u64,
|
priority: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImmutableDeserializedPacket {
|
impl ImmutableDeserializedPacket {
|
||||||
|
@ -63,8 +64,8 @@ impl ImmutableDeserializedPacket {
|
||||||
self.is_simple_vote
|
self.is_simple_vote
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fee_per_cu(&self) -> u64 {
|
pub fn priority(&self) -> u64 {
|
||||||
self.fee_per_cu
|
self.priority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,22 +78,18 @@ pub struct DeserializedPacket {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeserializedPacket {
|
impl DeserializedPacket {
|
||||||
pub fn new(packet: Packet, bank: Option<&Arc<Bank>>) -> Result<Self, DeserializedPacketError> {
|
pub fn new(packet: Packet) -> Result<Self, DeserializedPacketError> {
|
||||||
Self::new_internal(packet, bank, None)
|
Self::new_internal(packet, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn new_with_fee_per_cu(
|
fn new_with_priority(packet: Packet, priority: u64) -> Result<Self, DeserializedPacketError> {
|
||||||
packet: Packet,
|
Self::new_internal(packet, Some(priority))
|
||||||
fee_per_cu: u64,
|
|
||||||
) -> Result<Self, DeserializedPacketError> {
|
|
||||||
Self::new_internal(packet, None, Some(fee_per_cu))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_internal(
|
pub fn new_internal(
|
||||||
packet: Packet,
|
packet: Packet,
|
||||||
bank: Option<&Arc<Bank>>,
|
priority: Option<u64>,
|
||||||
fee_per_cu: Option<u64>,
|
|
||||||
) -> Result<Self, DeserializedPacketError> {
|
) -> Result<Self, DeserializedPacketError> {
|
||||||
let versioned_transaction: VersionedTransaction =
|
let versioned_transaction: VersionedTransaction =
|
||||||
limited_deserialize(&packet.data[0..packet.meta.size])?;
|
limited_deserialize(&packet.data[0..packet.meta.size])?;
|
||||||
|
@ -101,18 +98,18 @@ impl DeserializedPacket {
|
||||||
let message_hash = Message::hash_raw_message(message_bytes);
|
let message_hash = Message::hash_raw_message(message_bytes);
|
||||||
let is_simple_vote = packet.meta.is_simple_vote_tx();
|
let is_simple_vote = packet.meta.is_simple_vote_tx();
|
||||||
|
|
||||||
let fee_per_cu = fee_per_cu.unwrap_or_else(|| {
|
// drop transaction if prioritization fails.
|
||||||
bank.as_ref()
|
let priority = priority
|
||||||
.map(|bank| compute_fee_per_cu(sanitized_transaction.get_message(), bank))
|
.or_else(|| get_priority(sanitized_transaction.get_message()))
|
||||||
.unwrap_or(0)
|
.ok_or(DeserializedPacketError::PrioritizationFailure)?;
|
||||||
});
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
immutable_section: Rc::new(ImmutableDeserializedPacket {
|
immutable_section: Rc::new(ImmutableDeserializedPacket {
|
||||||
original_packet: packet,
|
original_packet: packet,
|
||||||
transaction: sanitized_transaction,
|
transaction: sanitized_transaction,
|
||||||
message_hash,
|
message_hash,
|
||||||
is_simple_vote,
|
is_simple_vote,
|
||||||
fee_per_cu,
|
priority,
|
||||||
}),
|
}),
|
||||||
forwarded: false,
|
forwarded: false,
|
||||||
})
|
})
|
||||||
|
@ -133,8 +130,8 @@ impl Ord for DeserializedPacket {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
match self
|
match self
|
||||||
.immutable_section()
|
.immutable_section()
|
||||||
.fee_per_cu()
|
.priority()
|
||||||
.cmp(&other.immutable_section().fee_per_cu())
|
.cmp(&other.immutable_section().priority())
|
||||||
{
|
{
|
||||||
Ordering::Equal => self
|
Ordering::Equal => self
|
||||||
.immutable_section()
|
.immutable_section()
|
||||||
|
@ -153,7 +150,7 @@ impl PartialOrd for ImmutableDeserializedPacket {
|
||||||
|
|
||||||
impl Ord for ImmutableDeserializedPacket {
|
impl Ord for ImmutableDeserializedPacket {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
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::Equal => self.sender_stake().cmp(&other.sender_stake()),
|
||||||
ordering => ordering,
|
ordering => ordering,
|
||||||
}
|
}
|
||||||
|
@ -193,8 +190,8 @@ impl UnprocessedPacketBatches {
|
||||||
self.message_hash_to_transaction.clear();
|
self.message_hash_to_transaction.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert new `deserizlized_packet_batch` into inner `MinMaxHeap<DeserializedPacket>`,
|
/// Insert new `deserialized_packet_batch` into inner `MinMaxHeap<DeserializedPacket>`,
|
||||||
/// weighted first by the fee-per-cu, then the stake of the sender.
|
/// 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
|
/// If buffer is at the max limit, the lowest weighted packet is dropped
|
||||||
///
|
///
|
||||||
/// Returns tuple of number of packets dropped
|
/// Returns tuple of number of packets dropped
|
||||||
|
@ -351,10 +348,9 @@ impl UnprocessedPacketBatches {
|
||||||
pub fn deserialize_packets<'a>(
|
pub fn deserialize_packets<'a>(
|
||||||
packet_batch: &'a PacketBatch,
|
packet_batch: &'a PacketBatch,
|
||||||
packet_indexes: &'a [usize],
|
packet_indexes: &'a [usize],
|
||||||
bank: Option<&'a Arc<Bank>>,
|
|
||||||
) -> impl Iterator<Item = DeserializedPacket> + 'a {
|
) -> impl Iterator<Item = DeserializedPacket> + 'a {
|
||||||
packet_indexes.iter().filter_map(move |packet_index| {
|
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))
|
.ok_or(DeserializedPacketError::SignatureOverflowed(sig_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes `(addition_fee + base_fee / requested_cu)` for `deserialized_packet`
|
fn get_priority(message: &SanitizedVersionedMessage) -> Option<u64> {
|
||||||
fn compute_fee_per_cu(_message: &SanitizedVersionedMessage, _bank: &Bank) -> u64 {
|
let mut compute_budget = ComputeBudget::default();
|
||||||
1
|
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(
|
pub fn transactions_to_deserialized_packets(
|
||||||
|
@ -384,7 +388,7 @@ pub fn transactions_to_deserialized_packets(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|transaction| {
|
.map(|transaction| {
|
||||||
let packet = Packet::from_data(None, transaction)?;
|
let packet = Packet::from_data(None, transaction)?;
|
||||||
DeserializedPacket::new(packet, None)
|
DeserializedPacket::new(packet)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -409,10 +413,10 @@ mod tests {
|
||||||
if let Some(ip) = ip {
|
if let Some(ip) = ip {
|
||||||
packet.meta.addr = 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(
|
let tx = system_transaction::transfer(
|
||||||
&Keypair::new(),
|
&Keypair::new(),
|
||||||
&solana_sdk::pubkey::new_rand(),
|
&solana_sdk::pubkey::new_rand(),
|
||||||
|
@ -420,7 +424,7 @@ mod tests {
|
||||||
Hash::new_unique(),
|
Hash::new_unique(),
|
||||||
);
|
);
|
||||||
let packet = Packet::from_data(None, &tx).unwrap();
|
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]
|
#[test]
|
||||||
|
@ -441,10 +445,10 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_unprocessed_packet_batches_insert_minimum_packet_over_capacity() {
|
fn test_unprocessed_packet_batches_insert_minimum_packet_over_capacity() {
|
||||||
let heavier_packet_weight = 2;
|
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_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
|
// Test that the heavier packet is actually heavier
|
||||||
let mut unprocessed_packet_batches = UnprocessedPacketBatches::with_capacity(2);
|
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)
|
[debugging](developing/on-chain-programs/debugging.md#monitoring-compute-budget-consumption)
|
||||||
for more information.
|
for more information.
|
||||||
|
|
||||||
A transaction may request a specific level of `max_units` it is allowed to
|
A transaction may set the maximum number of compute units it is allowed to
|
||||||
consume by including a
|
consume by including a "request units"
|
||||||
[``ComputeBudgetInstruction`](https://github.com/solana-labs/solana/blob/db32549c00a1b5370fcaf128981ad3323bbd9570/sdk/src/compute_budget.rs#L39).
|
[`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
|
Note that a transaction's prioritization fee is calculated from multiplying the
|
||||||
should request the minimum amount of compute units required for them to process.
|
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 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
|
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
|
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 rather than to each instruction within the transaction. The default
|
||||||
transaction-wide `max_units` will be calculated as the product of the number of
|
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
|
instructions in the transaction (excluding [Compute Budget](#compute-budget)
|
||||||
currently 200k. During processing, the sum of the compute units used by each
|
instructions) by the default per-instruction units, which is currently 200k.
|
||||||
instruction in the transaction must not exceed that value. This default value
|
During processing, the sum of the compute units used by each instruction in the
|
||||||
attempts to retain existing behavior to avoid breaking clients. Transactions can
|
transaction must not exceed that value. This default value attempts to retain
|
||||||
request a specific number of `max_units` via [Compute Budget](#compute-budget)
|
existing behavior to avoid breaking clients. Transactions can request a specific
|
||||||
instructions. Clients should request only what they need; requesting the
|
number of `max_units` via [Compute Budget](#compute-budget) instructions.
|
||||||
minimum amount of units required to process the transaction will improve their
|
Clients should request only what they need; requesting the minimum amount of
|
||||||
fee/compute-unit ratio, which transaction prioritization is based on.
|
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
|
## New Features
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use solana_sdk::{
|
use {
|
||||||
|
crate::prioritization_fee::{PrioritizationFeeDetails, PrioritizationFeeType},
|
||||||
|
solana_sdk::{
|
||||||
borsh::try_from_slice_unchecked,
|
borsh::try_from_slice_unchecked,
|
||||||
compute_budget::{self, ComputeBudgetInstruction},
|
compute_budget::{self, ComputeBudgetInstruction},
|
||||||
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
|
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
|
||||||
instruction::InstructionError,
|
instruction::{CompiledInstruction, InstructionError},
|
||||||
message::SanitizedMessage,
|
pubkey::Pubkey,
|
||||||
transaction::TransactionError,
|
transaction::TransactionError,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DEFAULT_UNITS: u32 = 200_000;
|
pub const DEFAULT_UNITS: u32 = 200_000;
|
||||||
|
@ -122,24 +125,21 @@ impl ComputeBudget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_message(
|
pub fn process_instructions<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
message: &SanitizedMessage,
|
instructions: impl Iterator<Item = (&'a Pubkey, &'a CompiledInstruction)>,
|
||||||
requestable_heap_size: bool,
|
requestable_heap_size: bool,
|
||||||
default_units_per_instruction: bool,
|
default_units_per_instruction: bool,
|
||||||
prioritization_fee_type_change: bool,
|
support_set_compute_unit_price_ix: bool,
|
||||||
) -> Result<u64, TransactionError> {
|
) -> Result<PrioritizationFeeDetails, TransactionError> {
|
||||||
let mut num_instructions = message.instructions().len();
|
let mut num_non_compute_budget_instructions: usize = 0;
|
||||||
let mut requested_units = None;
|
let mut requested_units = None;
|
||||||
let mut requested_heap_size = None;
|
let mut requested_heap_size = None;
|
||||||
let mut prioritization_fee = 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) {
|
if compute_budget::check_id(program_id) {
|
||||||
// don't include request instructions in default max calc
|
if support_set_compute_unit_price_ix {
|
||||||
num_instructions = num_instructions.saturating_sub(1);
|
|
||||||
|
|
||||||
if prioritization_fee_type_change {
|
|
||||||
let invalid_instruction_data_error = TransactionError::InstructionError(
|
let invalid_instruction_data_error = TransactionError::InstructionError(
|
||||||
i as u8,
|
i as u8,
|
||||||
InstructionError::InvalidInstructionData,
|
InstructionError::InvalidInstructionData,
|
||||||
|
@ -159,7 +159,8 @@ impl ComputeBudget {
|
||||||
return Err(duplicate_instruction_error);
|
return Err(duplicate_instruction_error);
|
||||||
}
|
}
|
||||||
requested_units = Some(units as u64);
|
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)) => {
|
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
|
||||||
if requested_heap_size.is_some() {
|
if requested_heap_size.is_some() {
|
||||||
|
@ -173,11 +174,12 @@ impl ComputeBudget {
|
||||||
}
|
}
|
||||||
requested_units = Some(units as u64);
|
requested_units = Some(units as u64);
|
||||||
}
|
}
|
||||||
Ok(ComputeBudgetInstruction::SetPrioritizationFee(fee)) => {
|
Ok(ComputeBudgetInstruction::SetComputeUnitPrice(micro_lamports)) => {
|
||||||
if prioritization_fee.is_some() {
|
if prioritization_fee.is_some() {
|
||||||
return Err(duplicate_instruction_error);
|
return Err(duplicate_instruction_error);
|
||||||
}
|
}
|
||||||
prioritization_fee = Some(fee);
|
prioritization_fee =
|
||||||
|
Some(PrioritizationFeeType::ComputeUnitPrice(micro_lamports));
|
||||||
}
|
}
|
||||||
_ => return Err(invalid_instruction_data_error),
|
_ => return Err(invalid_instruction_data_error),
|
||||||
}
|
}
|
||||||
|
@ -188,7 +190,8 @@ impl ComputeBudget {
|
||||||
additional_fee,
|
additional_fee,
|
||||||
}) => {
|
}) => {
|
||||||
requested_units = Some(units as u64);
|
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)) => {
|
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
|
||||||
requested_heap_size = Some((bytes, 0));
|
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 {
|
self.max_units = if default_units_per_instruction {
|
||||||
requested_units
|
requested_units.or_else(|| {
|
||||||
.or_else(|| Some(num_instructions.saturating_mul(DEFAULT_UNITS as usize) as u64))
|
Some(
|
||||||
|
num_non_compute_budget_instructions.saturating_mul(DEFAULT_UNITS as usize)
|
||||||
|
as u64,
|
||||||
|
)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
requested_units
|
requested_units
|
||||||
}
|
}
|
||||||
.unwrap_or(MAX_UNITS as u64)
|
.unwrap_or(MAX_UNITS as u64)
|
||||||
.min(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 {
|
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 payer_keypair = Keypair::new();
|
||||||
let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
|
let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
|
||||||
&[&payer_keypair],
|
&[&payer_keypair],
|
||||||
|
@ -266,21 +279,26 @@ mod tests {
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
));
|
));
|
||||||
let mut compute_budget = ComputeBudget::default();
|
let mut compute_budget = ComputeBudget::default();
|
||||||
let result = compute_budget.process_message(&tx.message(), true, true, $type_change);
|
let result = compute_budget.process_instructions(
|
||||||
assert_eq!($expected_error, result);
|
tx.message().program_instructions_iter(),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
$type_change,
|
||||||
|
);
|
||||||
|
assert_eq!($expected_result, result);
|
||||||
assert_eq!(compute_budget, $expected_budget);
|
assert_eq!(compute_budget, $expected_budget);
|
||||||
};
|
};
|
||||||
( $instructions: expr, $expected_error: expr, $expected_budget: expr) => {
|
( $instructions: expr, $expected_result: expr, $expected_budget: expr) => {
|
||||||
test!($instructions, $expected_error, $expected_budget, true);
|
test!($instructions, $expected_result, $expected_budget, true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_mesage() {
|
fn test_process_instructions() {
|
||||||
// Units
|
// Units
|
||||||
test!(
|
test!(
|
||||||
&[],
|
&[],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: 0,
|
max_units: 0,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -291,7 +309,7 @@ mod tests {
|
||||||
ComputeBudgetInstruction::request_units(1),
|
ComputeBudgetInstruction::request_units(1),
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: 1,
|
max_units: 1,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -302,7 +320,7 @@ mod tests {
|
||||||
ComputeBudgetInstruction::request_units(MAX_UNITS + 1),
|
ComputeBudgetInstruction::request_units(MAX_UNITS + 1),
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: MAX_UNITS as u64,
|
max_units: MAX_UNITS as u64,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -313,7 +331,7 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_units(MAX_UNITS),
|
ComputeBudgetInstruction::request_units(MAX_UNITS),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: MAX_UNITS as u64,
|
max_units: MAX_UNITS as u64,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -326,7 +344,7 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_units(1),
|
ComputeBudgetInstruction::request_units(1),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: 1,
|
max_units: 1,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -340,7 +358,7 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_units(1), // ignored
|
ComputeBudgetInstruction::request_units(1), // ignored
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: DEFAULT_UNITS as u64 * 3,
|
max_units: DEFAULT_UNITS as u64 * 3,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -351,7 +369,10 @@ mod tests {
|
||||||
// Prioritization fee
|
// Prioritization fee
|
||||||
test!(
|
test!(
|
||||||
&[request_units_deprecated(1, 42)],
|
&[request_units_deprecated(1, 42)],
|
||||||
Ok(42),
|
Ok(PrioritizationFeeDetails::new(
|
||||||
|
PrioritizationFeeType::Deprecated(42),
|
||||||
|
1,
|
||||||
|
)),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: 1,
|
max_units: 1,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -362,9 +383,12 @@ mod tests {
|
||||||
test!(
|
test!(
|
||||||
&[
|
&[
|
||||||
ComputeBudgetInstruction::request_units(1),
|
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 {
|
ComputeBudget {
|
||||||
max_units: 1,
|
max_units: 1,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -373,7 +397,10 @@ mod tests {
|
||||||
|
|
||||||
test!(
|
test!(
|
||||||
&[request_units_deprecated(1, u32::MAX)],
|
&[request_units_deprecated(1, u32::MAX)],
|
||||||
Ok(u32::MAX as u64),
|
Ok(PrioritizationFeeDetails::new(
|
||||||
|
PrioritizationFeeType::Deprecated(u32::MAX as u64),
|
||||||
|
1
|
||||||
|
)),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: 1,
|
max_units: 1,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -384,7 +411,7 @@ mod tests {
|
||||||
// HeapFrame
|
// HeapFrame
|
||||||
test!(
|
test!(
|
||||||
&[],
|
&[],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: 0,
|
max_units: 0,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -395,7 +422,7 @@ mod tests {
|
||||||
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
|
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: DEFAULT_UNITS as u64,
|
max_units: DEFAULT_UNITS as u64,
|
||||||
heap_size: Some(40 * 1024),
|
heap_size: Some(40 * 1024),
|
||||||
|
@ -440,7 +467,7 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: DEFAULT_UNITS as u64,
|
max_units: DEFAULT_UNITS as u64,
|
||||||
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
|
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![]),
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
],
|
],
|
||||||
Ok(0),
|
Ok(PrioritizationFeeDetails::default()),
|
||||||
ComputeBudget {
|
ComputeBudget {
|
||||||
max_units: DEFAULT_UNITS as u64 * 7,
|
max_units: DEFAULT_UNITS as u64 * 7,
|
||||||
..ComputeBudget::default()
|
..ComputeBudget::default()
|
||||||
|
@ -485,9 +512,12 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
||||||
ComputeBudgetInstruction::request_units(MAX_UNITS),
|
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 {
|
ComputeBudget {
|
||||||
max_units: MAX_UNITS as u64,
|
max_units: MAX_UNITS as u64,
|
||||||
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
|
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
|
||||||
|
@ -500,7 +530,7 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
||||||
ComputeBudgetInstruction::request_units(MAX_UNITS),
|
ComputeBudgetInstruction::request_units(MAX_UNITS),
|
||||||
ComputeBudgetInstruction::set_prioritization_fee(u64::MAX),
|
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
|
||||||
],
|
],
|
||||||
Err(TransactionError::InstructionError(
|
Err(TransactionError::InstructionError(
|
||||||
0,
|
0,
|
||||||
|
@ -515,9 +545,12 @@ mod tests {
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::request_units(1),
|
ComputeBudgetInstruction::request_units(1),
|
||||||
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
|
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 {
|
ComputeBudget {
|
||||||
max_units: 1,
|
max_units: 1,
|
||||||
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
|
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
|
||||||
|
@ -531,7 +564,10 @@ mod tests {
|
||||||
request_units_deprecated(MAX_UNITS, u32::MAX),
|
request_units_deprecated(MAX_UNITS, u32::MAX),
|
||||||
ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32),
|
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 {
|
ComputeBudget {
|
||||||
max_units: MAX_UNITS as u64,
|
max_units: MAX_UNITS as u64,
|
||||||
heap_size: Some(MIN_HEAP_FRAME_BYTES as usize),
|
heap_size: Some(MIN_HEAP_FRAME_BYTES as usize),
|
||||||
|
@ -564,8 +600,8 @@ mod tests {
|
||||||
test!(
|
test!(
|
||||||
&[
|
&[
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
ComputeBudgetInstruction::set_prioritization_fee(0),
|
ComputeBudgetInstruction::set_compute_unit_price(0),
|
||||||
ComputeBudgetInstruction::set_prioritization_fee(u64::MAX),
|
ComputeBudgetInstruction::set_compute_unit_price(u64::MAX),
|
||||||
],
|
],
|
||||||
Err(TransactionError::DuplicateInstruction(2)),
|
Err(TransactionError::DuplicateInstruction(2)),
|
||||||
ComputeBudget::default()
|
ComputeBudget::default()
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub mod invoke_context;
|
||||||
pub mod log_collector;
|
pub mod log_collector;
|
||||||
pub mod neon_evm_program;
|
pub mod neon_evm_program;
|
||||||
pub mod pre_account;
|
pub mod pre_account;
|
||||||
|
pub mod prioritization_fee;
|
||||||
pub mod stable_log;
|
pub mod stable_log;
|
||||||
pub mod sysvar_cache;
|
pub mod sysvar_cache;
|
||||||
pub mod timings;
|
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 sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
|
||||||
let expected_max_fee = Bank::calculate_fee(
|
let expected_normal_fee = Bank::calculate_fee(
|
||||||
&sanitized_message,
|
&sanitized_message,
|
||||||
congestion_multiplier,
|
congestion_multiplier,
|
||||||
&fee_structure,
|
&fee_structure,
|
||||||
|
@ -3500,32 +3500,31 @@ fn test_program_fees() {
|
||||||
.send_and_confirm_message(&[&mint_keypair], message)
|
.send_and_confirm_message(&[&mint_keypair], message)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).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 pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
|
||||||
let message = Message::new(
|
let message = Message::new(
|
||||||
&[
|
&[
|
||||||
ComputeBudgetInstruction::request_units(100),
|
ComputeBudgetInstruction::set_compute_unit_price(1),
|
||||||
ComputeBudgetInstruction::set_prioritization_fee(42),
|
|
||||||
Instruction::new_with_bytes(program_id, &[], vec![]),
|
Instruction::new_with_bytes(program_id, &[], vec![]),
|
||||||
],
|
],
|
||||||
Some(&mint_keypair.pubkey()),
|
Some(&mint_keypair.pubkey()),
|
||||||
);
|
);
|
||||||
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
|
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,
|
&sanitized_message,
|
||||||
congestion_multiplier,
|
congestion_multiplier,
|
||||||
&fee_structure,
|
&fee_structure,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
assert!(expected_min_fee < expected_max_fee);
|
assert!(expected_normal_fee < expected_prioritized_fee);
|
||||||
|
|
||||||
bank_client
|
bank_client
|
||||||
.send_and_confirm_message(&[&mint_keypair], message)
|
.send_and_confirm_message(&[&mint_keypair], message)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).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]
|
#[test]
|
||||||
|
|
|
@ -31,7 +31,7 @@ use {
|
||||||
account_utils::StateMut,
|
account_utils::StateMut,
|
||||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||||
clock::{BankId, Slot, INITIAL_RENT_EPOCH},
|
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,
|
fee::FeeStructure,
|
||||||
genesis_config::ClusterType,
|
genesis_config::ClusterType,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
@ -525,7 +525,7 @@ impl Accounts {
|
||||||
lamports_per_signature,
|
lamports_per_signature,
|
||||||
fee_structure,
|
fee_structure,
|
||||||
feature_set.is_active(&tx_wide_compute_cap::id()),
|
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 {
|
} else {
|
||||||
return (Err(TransactionError::BlockhashNotFound), None);
|
return (Err(TransactionError::BlockhashNotFound), None);
|
||||||
|
|
|
@ -107,8 +107,9 @@ use {
|
||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
feature,
|
feature,
|
||||||
feature_set::{
|
feature_set::{
|
||||||
self, default_units_per_instruction, disable_fee_calculator, nonce_must_be_writable,
|
self, add_set_compute_unit_price_ix, default_units_per_instruction,
|
||||||
prioritization_fee_type_change, requestable_heap_size, tx_wide_compute_cap, FeatureSet,
|
disable_fee_calculator, nonce_must_be_writable, requestable_heap_size,
|
||||||
|
tx_wide_compute_cap, FeatureSet,
|
||||||
},
|
},
|
||||||
fee::FeeStructure,
|
fee::FeeStructure,
|
||||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||||
|
@ -3638,7 +3639,7 @@ impl Bank {
|
||||||
&self.fee_structure,
|
&self.fee_structure,
|
||||||
self.feature_set.is_active(&tx_wide_compute_cap::id()),
|
self.feature_set.is_active(&tx_wide_compute_cap::id()),
|
||||||
self.feature_set
|
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.fee_structure,
|
||||||
self.feature_set.is_active(&tx_wide_compute_cap::id()),
|
self.feature_set.is_active(&tx_wide_compute_cap::id()),
|
||||||
self.feature_set
|
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 {
|
if tx_wide_compute_cap {
|
||||||
let mut compute_budget_process_transaction_time =
|
let mut compute_budget_process_transaction_time =
|
||||||
Measure::start("compute_budget_process_transaction_time");
|
Measure::start("compute_budget_process_transaction_time");
|
||||||
let process_transaction_result = compute_budget.process_message(
|
let process_transaction_result = compute_budget.process_instructions(
|
||||||
tx.message(),
|
tx.message().program_instructions_iter(),
|
||||||
feature_set.is_active(&requestable_heap_size::id()),
|
feature_set.is_active(&requestable_heap_size::id()),
|
||||||
feature_set.is_active(&default_units_per_instruction::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();
|
compute_budget_process_transaction_time.stop();
|
||||||
saturating_add_assign!(
|
saturating_add_assign!(
|
||||||
|
@ -4609,7 +4610,7 @@ impl Bank {
|
||||||
lamports_per_signature: u64,
|
lamports_per_signature: u64,
|
||||||
fee_structure: &FeeStructure,
|
fee_structure: &FeeStructure,
|
||||||
tx_wide_compute_cap: bool,
|
tx_wide_compute_cap: bool,
|
||||||
prioritization_fee_type_change: bool,
|
support_set_compute_unit_price_ix: bool,
|
||||||
) -> u64 {
|
) -> u64 {
|
||||||
if tx_wide_compute_cap {
|
if tx_wide_compute_cap {
|
||||||
// Fee based on compute units and signatures
|
// Fee based on compute units and signatures
|
||||||
|
@ -4622,9 +4623,15 @@ impl Bank {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut compute_budget = ComputeBudget::default();
|
let mut compute_budget = ComputeBudget::default();
|
||||||
let prioritization_fee = compute_budget
|
let prioritization_fee_details = compute_budget
|
||||||
.process_message(message, false, false, prioritization_fee_type_change)
|
.process_instructions(
|
||||||
|
message.program_instructions_iter(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
support_set_compute_unit_price_ix,
|
||||||
|
)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let prioritization_fee = prioritization_fee_details.get_fee();
|
||||||
let signature_fee = Self::get_num_signatures_in_message(message)
|
let signature_fee = Self::get_num_signatures_in_message(message)
|
||||||
.saturating_mul(fee_structure.lamports_per_signature);
|
.saturating_mul(fee_structure.lamports_per_signature);
|
||||||
let write_lock_fee = Self::get_num_write_locks_in_message(message)
|
let write_lock_fee = Self::get_num_write_locks_in_message(message)
|
||||||
|
@ -4691,7 +4698,7 @@ impl Bank {
|
||||||
&self.fee_structure,
|
&self.fee_structure,
|
||||||
self.feature_set.is_active(&tx_wide_compute_cap::id()),
|
self.feature_set.is_active(&tx_wide_compute_cap::id()),
|
||||||
self.feature_set
|
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
|
// In case of instruction error, even though no accounts
|
||||||
|
@ -7242,7 +7249,11 @@ pub(crate) mod tests {
|
||||||
status_cache::MAX_CACHE_ENTRIES,
|
status_cache::MAX_CACHE_ENTRIES,
|
||||||
},
|
},
|
||||||
crossbeam_channel::{bounded, unbounded},
|
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::{
|
solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
|
||||||
|
@ -16765,26 +16776,19 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
// Explicit fee schedule
|
// Explicit fee schedule
|
||||||
|
|
||||||
let expected_fee_structure = &[
|
for requested_compute_units in [
|
||||||
// (units requested, fee in SOL),
|
0, 5_000, 10_000, 100_000, 300_000, 500_000, 700_000, 900_000, 1_100_000, 1_300_000,
|
||||||
(0, 0.0),
|
MAX_UNITS,
|
||||||
(5_000, 0.0),
|
] {
|
||||||
(10_000, 0.0),
|
const PRIORITIZATION_FEE_RATE: u64 = 42;
|
||||||
(100_000, 0.0),
|
let prioritization_fee_details = PrioritizationFeeDetails::new(
|
||||||
(300_000, 0.0),
|
PrioritizationFeeType::ComputeUnitPrice(PRIORITIZATION_FEE_RATE),
|
||||||
(500_000, 0.0),
|
requested_compute_units as u64,
|
||||||
(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;
|
|
||||||
let message = SanitizedMessage::try_from(Message::new(
|
let message = SanitizedMessage::try_from(Message::new(
|
||||||
&[
|
&[
|
||||||
ComputeBudgetInstruction::request_units(pair.0),
|
ComputeBudgetInstruction::request_units(requested_compute_units),
|
||||||
ComputeBudgetInstruction::set_prioritization_fee(PRIORITIZATION_FEE),
|
ComputeBudgetInstruction::set_compute_unit_price(PRIORITIZATION_FEE_RATE),
|
||||||
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
|
||||||
],
|
],
|
||||||
Some(&Pubkey::new_unique()),
|
Some(&Pubkey::new_unique()),
|
||||||
|
@ -16793,7 +16797,7 @@ pub(crate) mod tests {
|
||||||
let fee = Bank::calculate_fee(&message, 1, &fee_structure, true, true);
|
let fee = Bank::calculate_fee(&message, 1, &fee_structure, true, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fee,
|
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
|
/// Request a specific maximum number of compute units the transaction is
|
||||||
/// allowed to consume and an additional fee to pay.
|
/// allowed to consume and an additional fee to pay.
|
||||||
RequestUnits(u32),
|
RequestUnits(u32),
|
||||||
/// Additional fee in lamports to charge the payer, used for transaction
|
/// Set a compute unit price in "micro-lamports" to pay a higher transaction
|
||||||
/// prioritization
|
/// fee for higher transaction prioritization.
|
||||||
SetPrioritizationFee(u64),
|
SetComputeUnitPrice(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputeBudgetInstruction {
|
impl ComputeBudgetInstruction {
|
||||||
|
@ -51,8 +51,8 @@ impl ComputeBudgetInstruction {
|
||||||
Instruction::new_with_borsh(id(), &Self::RequestUnits(units), vec![])
|
Instruction::new_with_borsh(id(), &Self::RequestUnits(units), vec![])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `ComputeBudgetInstruction::SetPrioritizationFee` `Instruction`
|
/// Create a `ComputeBudgetInstruction::SetComputeUnitPrice` `Instruction`
|
||||||
pub fn set_prioritization_fee(fee: u64) -> Instruction {
|
pub fn set_compute_unit_price(micro_lamports: u64) -> Instruction {
|
||||||
Instruction::new_with_borsh(id(), &Self::SetPrioritizationFee(fee), vec![])
|
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");
|
solana_sdk::declare_id!("4xmyBuR2VCXzy9H6qYpH9ckfgnTuMDQFPFBfTs4eBCY1");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prioritization_fee_type_change {
|
pub mod add_set_compute_unit_price_ix {
|
||||||
solana_sdk::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g");
|
solana_sdk::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod disable_deploy_of_alloc_free_syscall {
|
||||||
|
solana_sdk::declare_id!("79HWsX9rpnnJBPcdNURVqygpMAfxdrAirzAGAVmf92im");
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// Map of feature identifiers to user-visible description
|
/// Map of feature identifiers to user-visible description
|
||||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
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"),
|
(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"),
|
(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"),
|
(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 ***************/
|
/*************** ADD NEW FEATURES HERE ***************/
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
|
Loading…
Reference in New Issue