support 64-bit prioritization fee (#25027)

This commit is contained in:
Jack May 2022-05-12 11:07:36 -07:00 committed by GitHub
parent 896729f25e
commit cde15ff687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 395 additions and 133 deletions

View File

@ -89,7 +89,7 @@ pub enum CliCommand {
timeout: Duration,
blockhash: Option<Hash>,
print_timestamp: bool,
additional_fee: Option<u32>,
prioritization_fee: Option<u64>,
},
Rent {
data_length: usize,
@ -877,7 +877,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
timeout,
blockhash,
print_timestamp,
additional_fee,
prioritization_fee,
} => process_ping(
&rpc_client,
config,
@ -886,7 +886,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
timeout,
blockhash,
*print_timestamp,
additional_fee,
prioritization_fee,
),
CliCommand::Rent {
data_length,

View File

@ -33,7 +33,6 @@ use {
rpc_request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
rpc_response::SlotInfo,
},
solana_program_runtime::compute_budget,
solana_remote_wallet::remote_wallet::RemoteWalletManager,
solana_sdk::{
account::from_account,
@ -272,11 +271,12 @@ impl ClusterQuerySubCommands for App<'_, '_> {
.help("Wait up to timeout seconds for transaction confirmation"),
)
.arg(
Arg::with_name("additional_fee")
.long("additional-fee")
Arg::with_name("prioritization-fee")
.long("prioritization-fee")
.alias("additional-fee")
.value_name("NUMBER")
.takes_value(true)
.help("Request additional-fee for transaction"),
.help("Set prioritization-fee for transaction"),
)
.arg(blockhash_arg()),
)
@ -523,7 +523,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 additional_fee = value_of(matches, "additional_fee");
let prioritization_fee = value_of(matches, "prioritization_fee");
Ok(CliCommandInfo {
command: CliCommand::Ping {
interval,
@ -531,7 +531,7 @@ pub fn parse_cluster_ping(
timeout,
blockhash,
print_timestamp,
additional_fee,
prioritization_fee,
},
signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
})
@ -1364,7 +1364,7 @@ pub fn process_ping(
timeout: &Duration,
fixed_blockhash: &Option<Hash>,
print_timestamp: bool,
additional_fee: &Option<u32>,
prioritization_fee: &Option<u64>,
) -> ProcessResult {
let (signal_sender, signal_receiver) = unbounded();
ctrlc::set_handler(move || {
@ -1409,10 +1409,9 @@ pub fn process_ping(
&to,
lamports,
)];
if let Some(additional_fee) = additional_fee {
ixs.push(ComputeBudgetInstruction::request_units(
compute_budget::DEFAULT_UNITS,
*additional_fee,
if let Some(prioritization_fee) = prioritization_fee {
ixs.push(ComputeBudgetInstruction::set_prioritization_fee(
*prioritization_fee,
));
}
Message::new(&ixs, Some(&config.signers[0].pubkey()))
@ -2339,7 +2338,7 @@ mod tests {
Hash::from_str("4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX").unwrap()
),
print_timestamp: true,
additional_fee: None,
prioritization_fee: None,
},
signers: vec![default_keypair.into()],
}

View File

@ -49,9 +49,9 @@ To prevent a program from abusing computation resources, each instruction in a
transaction is given a compute budget. The budget consists of computation units
that are consumed as the program performs various operations and bounds that the
program may not exceed. When the program consumes its entire budget or exceeds
a bound, then the runtime halts the program and returns an error.
a bound, the runtime halts the program and returns an error.
Note: The compute budget currently applies per-instruction but is moving toward
Note: The compute budget currently applies per-instruction, but is moving toward
a per-transaction model. For more information see [Transaction-wide Compute
Budget](#transaction-wide-compute-budget).
@ -101,6 +101,23 @@ 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.
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 budget instruction, duplicate types will result in an error.
The `ComputeBudgetInstruction::request_units` function can be used to create
these instructions:
```rust
let instruction = ComputeBudgetInstruction::request_units(300_000);
```
## Transaction-wide Compute Budget
Transactions are processed as a single entity and are the primary unit of block
@ -111,32 +128,17 @@ transaction-wide budget rather than per-instruction.
For information on what the compute budget is and how it is applied see [Compute
Budget](#compute-budget).
With a transaction-wide compute budget the `max_units` cap is applied to the
entire transaction rather than to each instruction within the transaction. The
transaction-wide default maximum number of units will be calculated as the product
of the instruction count and the existing per-instruction maximum units. This means
that the sum of the compute units used by each instruction in the transaction must
not exceed that value. This default maximum value attempts to retain existing behavior
to avoid breaking client logic.
### Reduce transaction fees
_Note: At the time of writing, transaction fees are still charged by the number of
signatures but will eventually be calculated from requested compute unit cap._
Most transactions won't use the default number of compute units so they can include a
[``ComputeBudgetInstruction`](https://github.com/solana-labs/solana/blob/db32549c00a1b5370fcaf128981ad3323bbd9570/sdk/src/compute_budget.rs#L39)
to lower the compute unit cap. **Important: Lower compute caps will be charged lower fees.**
Compute Budget instructions don't require any accounts and must lie in the first
3 instructions of a transaction otherwise they will be ignored.
The `ComputeBudgetInstruction::request_units` function can be used to create
these instructions:
```rust
let instruction = ComputeBudgetInstruction::request_units(300_000);
```
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.
## New Features

View File

@ -127,44 +127,97 @@ impl ComputeBudget {
message: &SanitizedMessage,
requestable_heap_size: bool,
default_units_per_instruction: bool,
prioritization_fee_type_change: bool,
) -> Result<u64, TransactionError> {
let mut num_instructions = message.instructions().len();
let mut requested_additional_fee = 0;
let mut requested_units = None;
let mut requested_heap_size = None;
let mut prioritization_fee = None;
let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData);
for (i, (program_id, instruction)) in message.program_instructions_iter().enumerate() {
if compute_budget::check_id(program_id) {
// don't include request instructions in default max calc
num_instructions = num_instructions.saturating_sub(1);
// Compute budget instruction must be in the 1st 3 instructions (avoid
// nonce marker), otherwise ignored
if i < 3 {
if prioritization_fee_type_change {
let invalid_instruction_data_error = TransactionError::InstructionError(
i as u8,
InstructionError::InvalidInstructionData,
);
let duplicate_instruction_error =
TransactionError::DuplicateInstruction(i as u8);
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnits {
Ok(ComputeBudgetInstruction::RequestUnitsDeprecated {
units,
additional_fee,
}) => {
if requested_units.is_some() {
return Err(duplicate_instruction_error);
}
if prioritization_fee.is_some() {
return Err(duplicate_instruction_error);
}
requested_units = Some(units as u64);
prioritization_fee = Some(additional_fee as u64);
}
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if requested_heap_size.is_some() {
return Err(duplicate_instruction_error);
}
requested_heap_size = Some((bytes, i as u8));
}
Ok(ComputeBudgetInstruction::RequestUnits(units)) => {
if requested_units.is_some() {
return Err(duplicate_instruction_error);
}
requested_units = Some(units as u64);
}
Ok(ComputeBudgetInstruction::SetPrioritizationFee(fee)) => {
if prioritization_fee.is_some() {
return Err(duplicate_instruction_error);
}
prioritization_fee = Some(fee);
}
_ => return Err(invalid_instruction_data_error),
}
} else if i < 3 {
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnitsDeprecated {
units,
additional_fee,
}) => {
requested_units = Some(units as u64);
requested_additional_fee = additional_fee as u64;
prioritization_fee = Some(additional_fee as u64);
}
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if !requestable_heap_size
|| bytes > MAX_HEAP_FRAME_BYTES
|| bytes < MIN_HEAP_FRAME_BYTES as u32
|| bytes % 1024 != 0
{
return Err(error);
}
self.heap_size = Some(bytes as usize);
requested_heap_size = Some((bytes, 0));
}
_ => {
return Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
))
}
_ => return Err(error),
}
}
}
}
if let Some((bytes, i)) = requested_heap_size {
if !requestable_heap_size
|| bytes > MAX_HEAP_FRAME_BYTES
|| bytes < MIN_HEAP_FRAME_BYTES as u32
|| bytes % 1024 != 0
{
return Err(TransactionError::InstructionError(
i,
InstructionError::InvalidInstructionData,
));
}
self.heap_size = Some(bytes as usize);
}
self.max_units = if default_units_per_instruction {
requested_units
.or_else(|| Some(num_instructions.saturating_mul(DEFAULT_UNITS as usize) as u64))
@ -174,7 +227,7 @@ impl ComputeBudget {
.unwrap_or(MAX_UNITS as u64)
.min(MAX_UNITS as u64);
Ok(requested_additional_fee)
Ok(prioritization_fee.unwrap_or(0))
}
}
@ -193,8 +246,19 @@ mod tests {
},
};
fn request_units_deprecated(units: u32, additional_fee: u32) -> Instruction {
Instruction::new_with_borsh(
compute_budget::id(),
&ComputeBudgetInstruction::RequestUnitsDeprecated {
units,
additional_fee,
},
vec![],
)
}
macro_rules! test {
( $instructions: expr, $expected_error: expr, $expected_budget: expr ) => {
( $instructions: expr, $expected_error: expr, $expected_budget: expr, $type_change: expr ) => {
let payer_keypair = Keypair::new();
let tx = SanitizedTransaction::from_transaction_for_tests(Transaction::new(
&[&payer_keypair],
@ -202,10 +266,13 @@ mod tests {
Hash::default(),
));
let mut compute_budget = ComputeBudget::default();
let result = compute_budget.process_message(&tx.message(), true, true);
let result = compute_budget.process_message(&tx.message(), true, true, $type_change);
assert_eq!($expected_error, result);
assert_eq!(compute_budget, $expected_budget);
};
( $instructions: expr, $expected_error: expr, $expected_budget: expr) => {
test!($instructions, $expected_error, $expected_budget, true);
};
}
#[test]
@ -221,7 +288,7 @@ mod tests {
);
test!(
&[
ComputeBudgetInstruction::request_units(1, 0),
ComputeBudgetInstruction::request_units(1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(0),
@ -232,7 +299,7 @@ mod tests {
);
test!(
&[
ComputeBudgetInstruction::request_units(MAX_UNITS + 1, 0),
ComputeBudgetInstruction::request_units(MAX_UNITS + 1),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(0),
@ -244,7 +311,7 @@ mod tests {
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_units(MAX_UNITS, 0),
ComputeBudgetInstruction::request_units(MAX_UNITS),
],
Ok(0),
ComputeBudget {
@ -257,18 +324,46 @@ 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![]),
ComputeBudgetInstruction::request_units(1, 0), // ignored
ComputeBudgetInstruction::request_units(1),
],
Ok(0),
ComputeBudget {
max_units: 1,
..ComputeBudget::default()
}
);
test!(
&[
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![]),
ComputeBudgetInstruction::request_units(1), // ignored
],
Ok(0),
ComputeBudget {
max_units: DEFAULT_UNITS as u64 * 3,
..ComputeBudget::default()
}
},
false
);
// Additional fee
// Prioritization fee
test!(
&[ComputeBudgetInstruction::request_units(1, 42),],
&[request_units_deprecated(1, 42)],
Ok(42),
ComputeBudget {
max_units: 1,
..ComputeBudget::default()
},
false
);
test!(
&[
ComputeBudgetInstruction::request_units(1),
ComputeBudgetInstruction::set_prioritization_fee(42)
],
Ok(42),
ComputeBudget {
max_units: 1,
@ -276,6 +371,16 @@ mod tests {
}
);
test!(
&[request_units_deprecated(1, u32::MAX)],
Ok(u32::MAX as u64),
ComputeBudget {
max_units: 1,
..ComputeBudget::default()
},
false
);
// HeapFrame
test!(
&[],
@ -347,13 +452,13 @@ 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![]),
ComputeBudgetInstruction::request_heap_frame(1), // ignored
ComputeBudgetInstruction::request_heap_frame(1),
],
Ok(0),
ComputeBudget {
max_units: DEFAULT_UNITS as u64 * 3,
..ComputeBudget::default()
}
Err(TransactionError::InstructionError(
3,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default()
);
test!(
@ -379,14 +484,91 @@ mod tests {
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
ComputeBudgetInstruction::request_units(MAX_UNITS, 0),
ComputeBudgetInstruction::request_units(MAX_UNITS),
ComputeBudgetInstruction::set_prioritization_fee(u64::MAX),
],
Ok(0),
Ok(u64::MAX),
ComputeBudget {
max_units: MAX_UNITS as u64,
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
..ComputeBudget::default()
}
);
test!(
&[
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),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
ComputeBudget::default(),
false
);
test!(
&[
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),
],
Ok(u64::MAX),
ComputeBudget {
max_units: 1,
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
..ComputeBudget::default()
}
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
request_units_deprecated(MAX_UNITS, u32::MAX),
ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32),
],
Ok(u32::MAX as u64),
ComputeBudget {
max_units: MAX_UNITS as u64,
heap_size: Some(MIN_HEAP_FRAME_BYTES as usize),
..ComputeBudget::default()
},
false
);
// Duplicates
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_units(MAX_UNITS),
ComputeBudgetInstruction::request_units(MAX_UNITS - 1),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_heap_frame(MIN_HEAP_FRAME_BYTES as u32),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES as u32),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::set_prioritization_fee(0),
ComputeBudgetInstruction::set_prioritization_fee(u64::MAX),
],
Err(TransactionError::DuplicateInstruction(2)),
ComputeBudget::default()
);
}
}

View File

@ -1480,7 +1480,7 @@ fn test_program_bpf_compute_budget() {
);
let message = Message::new(
&[
ComputeBudgetInstruction::request_units(1, 0),
ComputeBudgetInstruction::request_units(1),
Instruction::new_with_bincode(program_id, &0, vec![]),
],
Some(&mint_keypair.pubkey()),
@ -3576,6 +3576,7 @@ fn test_program_fees() {
congestion_multiplier,
&fee_structure,
true,
true,
);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
@ -3586,7 +3587,8 @@ fn test_program_fees() {
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
let message = Message::new(
&[
ComputeBudgetInstruction::request_units(100, 42),
ComputeBudgetInstruction::request_units(100),
ComputeBudgetInstruction::set_prioritization_fee(42),
Instruction::new_with_bytes(program_id, &[], vec![]),
],
Some(&mint_keypair.pubkey()),
@ -3597,6 +3599,7 @@ fn test_program_fees() {
congestion_multiplier,
&fee_structure,
true,
true,
);
assert!(expected_min_fee < expected_max_fee);

View File

@ -31,7 +31,7 @@ use {
account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{BankId, Slot, INITIAL_RENT_EPOCH},
feature_set::{self, tx_wide_compute_cap, FeatureSet},
feature_set::{self, prioritization_fee_type_change, tx_wide_compute_cap, FeatureSet},
fee::FeeStructure,
genesis_config::ClusterType,
hash::Hash,
@ -525,6 +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()),
)
} else {
return (Err(TransactionError::BlockhashNotFound), None);
@ -1629,6 +1630,7 @@ mod tests {
10,
&FeeStructure::default(),
false,
true,
);
assert_eq!(fee, 10);

View File

@ -108,7 +108,7 @@ use {
feature,
feature_set::{
self, default_units_per_instruction, disable_fee_calculator, nonce_must_be_writable,
requestable_heap_size, tx_wide_compute_cap, FeatureSet,
prioritization_fee_type_change, requestable_heap_size, tx_wide_compute_cap, FeatureSet,
},
fee::FeeStructure,
fee_calculator::{FeeCalculator, FeeRateGovernor},
@ -239,7 +239,7 @@ impl RentDebits {
}
type BankStatusCache = StatusCache<Result<()>>;
#[frozen_abi(digest = "BQcJmh4VRCiNNtqjKPyphs9ULFbSUKGGfx6hz9SWBtqU")]
#[frozen_abi(digest = "HSBFEjhoubTjeGKeBJvRDAiCBTVeFfcWNPZSbDW1w4H4")]
pub type BankSlotDelta = SlotDelta<Result<()>>;
// Eager rent collection repeats in cyclic manner.
@ -3637,6 +3637,8 @@ impl Bank {
lamports_per_signature,
&self.fee_structure,
self.feature_set.is_active(&tx_wide_compute_cap::id()),
self.feature_set
.is_active(&prioritization_fee_type_change::id()),
))
}
@ -3650,6 +3652,8 @@ impl Bank {
lamports_per_signature,
&self.fee_structure,
self.feature_set.is_active(&tx_wide_compute_cap::id()),
self.feature_set
.is_active(&prioritization_fee_type_change::id()),
)
}
@ -4389,6 +4393,7 @@ impl Bank {
tx.message(),
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()),
);
compute_budget_process_transaction_time.stop();
saturating_add_assign!(
@ -4604,6 +4609,7 @@ impl Bank {
lamports_per_signature: u64,
fee_structure: &FeeStructure,
tx_wide_compute_cap: bool,
prioritization_fee_type_change: bool,
) -> u64 {
if tx_wide_compute_cap {
// Fee based on compute units and signatures
@ -4616,8 +4622,8 @@ impl Bank {
};
let mut compute_budget = ComputeBudget::default();
let additional_fee = compute_budget
.process_message(message, false, false)
let prioritization_fee = compute_budget
.process_message(message, false, false, prioritization_fee_type_change)
.unwrap_or_default();
let signature_fee = Self::get_num_signatures_in_message(message)
.saturating_mul(fee_structure.lamports_per_signature);
@ -4636,7 +4642,7 @@ impl Bank {
.unwrap_or_default()
});
((additional_fee
((prioritization_fee
.saturating_add(signature_fee)
.saturating_add(write_lock_fee)
.saturating_add(compute_fee) as f64)
@ -4684,6 +4690,8 @@ impl Bank {
lamports_per_signature,
&self.fee_structure,
self.feature_set.is_active(&tx_wide_compute_cap::id()),
self.feature_set
.is_active(&prioritization_fee_type_change::id()),
);
// In case of instruction error, even though no accounts
@ -9985,6 +9993,7 @@ pub(crate) mod tests {
.lamports_per_signature,
&FeeStructure::default(),
true,
true,
);
let (expected_fee_collected, expected_fee_burned) =
@ -10167,6 +10176,7 @@ pub(crate) mod tests {
cheap_lamports_per_signature,
&FeeStructure::default(),
true,
true,
);
assert_eq!(
bank.get_balance(&mint_keypair.pubkey()),
@ -10184,6 +10194,7 @@ pub(crate) mod tests {
expensive_lamports_per_signature,
&FeeStructure::default(),
true,
true,
);
assert_eq!(
bank.get_balance(&mint_keypair.pubkey()),
@ -10300,6 +10311,7 @@ pub(crate) mod tests {
.lamports_per_signature,
&FeeStructure::default(),
true,
true,
) * 2
)
.0
@ -16405,7 +16417,7 @@ pub(crate) mod tests {
let message = Message::new(
&[
ComputeBudgetInstruction::request_units(1, 0),
ComputeBudgetInstruction::request_units(1),
ComputeBudgetInstruction::request_heap_frame(48 * 1024),
Instruction::new_with_bincode(program_id, &0, vec![]),
],
@ -16449,7 +16461,7 @@ pub(crate) mod tests {
let message = Message::new(
&[
ComputeBudgetInstruction::request_units(1, 0),
ComputeBudgetInstruction::request_units(1),
ComputeBudgetInstruction::request_heap_frame(48 * 1024),
Instruction::new_with_bincode(program_id, &0, vec![]),
],
@ -16509,7 +16521,7 @@ pub(crate) mod tests {
// This message will be processed successfully
let message1 = Message::new(
&[
ComputeBudgetInstruction::request_units(1, 0),
ComputeBudgetInstruction::request_units(1),
ComputeBudgetInstruction::request_heap_frame(48 * 1024),
Instruction::new_with_bincode(program_id, &0, vec![]),
],
@ -16695,13 +16707,13 @@ pub(crate) mod tests {
let message =
SanitizedMessage::try_from(Message::new(&[], Some(&Pubkey::new_unique()))).unwrap();
assert_eq!(
Bank::calculate_fee(&message, 0, &FeeStructure::default(), false),
Bank::calculate_fee(&message, 0, &FeeStructure::default(), false, true),
0
);
// One signature, a fee.
assert_eq!(
Bank::calculate_fee(&message, 1, &FeeStructure::default(), false),
Bank::calculate_fee(&message, 1, &FeeStructure::default(), false, true),
1
);
@ -16712,7 +16724,7 @@ pub(crate) mod tests {
let ix1 = system_instruction::transfer(&key1, &key0, 1);
let message = SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&key0))).unwrap();
assert_eq!(
Bank::calculate_fee(&message, 2, &FeeStructure::default(), false),
Bank::calculate_fee(&message, 2, &FeeStructure::default(), false, true),
4
);
}
@ -16728,7 +16740,7 @@ pub(crate) mod tests {
let message =
SanitizedMessage::try_from(Message::new(&[], Some(&Pubkey::new_unique()))).unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &fee_structure, true),
Bank::calculate_fee(&message, 1, &fee_structure, true, true),
max_fee + lamports_per_signature
);
@ -16740,7 +16752,7 @@ pub(crate) mod tests {
SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&Pubkey::new_unique())))
.unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &fee_structure, true),
Bank::calculate_fee(&message, 1, &fee_structure, true, true),
max_fee + 3 * lamports_per_signature
);
@ -16761,16 +16773,20 @@ pub(crate) mod tests {
(1_500_000, 0.0), // ComputeBudget capped
];
for pair in expected_fee_structure.iter() {
const ADDITIONAL_FEE: u64 = 42;
let ix0 = ComputeBudgetInstruction::request_units(pair.0, ADDITIONAL_FEE as u32);
let ix1 = Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]);
let message =
SanitizedMessage::try_from(Message::new(&[ix0, ix1], Some(&Pubkey::new_unique())))
.unwrap();
let fee = Bank::calculate_fee(&message, 1, &fee_structure, true);
const PRIORITIZATION_FEE: u64 = 42;
let message = SanitizedMessage::try_from(Message::new(
&[
ComputeBudgetInstruction::request_units(pair.0),
ComputeBudgetInstruction::set_prioritization_fee(PRIORITIZATION_FEE),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Some(&Pubkey::new_unique()),
))
.unwrap();
let fee = Bank::calculate_fee(&message, 1, &fee_structure, true, true);
assert_eq!(
fee,
sol_to_lamports(pair.1) + lamports_per_signature + ADDITIONAL_FEE
sol_to_lamports(pair.1) + lamports_per_signature + PRIORITIZATION_FEE
);
}
}
@ -16802,7 +16818,7 @@ pub(crate) mod tests {
))
.unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &FeeStructure::default(), false),
Bank::calculate_fee(&message, 1, &FeeStructure::default(), false, true),
2
);
@ -16814,7 +16830,7 @@ pub(crate) mod tests {
))
.unwrap();
assert_eq!(
Bank::calculate_fee(&message, 1, &FeeStructure::default(), false),
Bank::calculate_fee(&message, 1, &FeeStructure::default(), false, true),
11
);
}

View File

@ -20,9 +20,8 @@ crate::declare_id!("ComputeBudget111111111111111111111111111111");
Serialize,
)]
pub enum ComputeBudgetInstruction {
/// Request a specific maximum number of compute units the transaction is
/// allowed to consume and an additional fee to pay.
RequestUnits {
/// Deprecated
RequestUnitsDeprecated {
/// Units to request
units: u32,
/// Additional fee to add
@ -30,29 +29,30 @@ pub enum ComputeBudgetInstruction {
},
/// Request a specific transaction-wide program heap region size in bytes.
/// The value requested must be a multiple of 1024. This new heap region
/// size applies to each program executed, including all calls to CPIs.
/// size applies to each program executed in the transaction, including all
/// calls to CPIs.
RequestHeapFrame(u32),
/// 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),
}
impl ComputeBudgetInstruction {
/// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction`
pub fn request_units(units: u32, additional_fee: u32) -> Instruction {
Instruction::new_with_borsh(
id(),
&ComputeBudgetInstruction::RequestUnits {
units,
additional_fee,
},
vec![],
)
}
/// Create a `ComputeBudgetInstruction::RequestHeapFrame` `Instruction`
pub fn request_heap_frame(bytes: u32) -> Instruction {
Instruction::new_with_borsh(
id(),
&ComputeBudgetInstruction::RequestHeapFrame(bytes),
vec![],
)
Instruction::new_with_borsh(id(), &Self::RequestHeapFrame(bytes), vec![])
}
/// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction`
pub fn request_units(units: u32) -> Instruction {
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![])
}
}

View File

@ -400,6 +400,10 @@ pub mod disable_deploy_of_alloc_free_syscall {
solana_sdk::declare_id!("79HWsX9rpnnJBPcdNURVqygpMAfxdrAirzAGAVmf92im");
}
pub mod prioritization_fee_type_change {
solana_sdk::declare_id!("98std1NSHqXi9WYvFShfVepRdCoq1qvsp8fsR2XZtG8g");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -493,6 +497,7 @@ lazy_static! {
(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"),
(disable_deploy_of_alloc_free_syscall::id(), "disable new deployments of deprecated sol_alloc_free_ syscall"),
(prioritization_fee_type_change::id(), "Switch compute budget to prioritization fee"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()

View File

@ -137,6 +137,10 @@ pub enum TransactionError {
/// Transaction would exceed total account data limit
#[error("Transaction would exceed total account data limit")]
WouldExceedAccountDataTotalLimit,
/// Transaction contains a duplicate instruction that is not allowed
#[error("Transaction contains a duplicate instruction ({0}) that is not allowed")]
DuplicateInstruction(u8),
}
impl From<SanitizeError> for TransactionError {

View File

@ -21,6 +21,7 @@ message Memo {
message TransactionError {
TransactionErrorType transaction_error = 1;
InstructionError instruction_error = 2;
DuplicateInstructionError duplicate_instruction_error = 3;
}
enum TransactionErrorType {
@ -54,6 +55,7 @@ enum TransactionErrorType {
INVALID_RENT_PAYING_ACCOUNT = 27;
WOULD_EXCEED_MAX_VOTE_COST_LIMIT = 28;
WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT = 29;
DUPLICATE_INSTRUCTION = 30;
}
message InstructionError {
@ -62,6 +64,10 @@ message InstructionError {
CustomError custom = 3;
}
message DuplicateInstructionError {
uint32 index = 1;
}
enum InstructionErrorType {
GENERIC_ERROR = 0;
INVALID_ARGUMENT = 1;

View File

@ -716,6 +716,12 @@ impl TryFrom<tx_by_addr::TransactionError> for TransactionError {
}
}
if let Some(duplicate_instruction) = transaction_error.duplicate_instruction_error {
return Ok(TransactionError::DuplicateInstruction(
duplicate_instruction.index as u8,
));
}
Ok(match transaction_error.transaction_error {
0 => TransactionError::AccountInUse,
1 => TransactionError::AccountLoadedTwice,
@ -843,6 +849,9 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
TransactionError::WouldExceedAccountDataTotalLimit => {
tx_by_addr::TransactionErrorType::WouldExceedAccountDataTotalLimit
}
TransactionError::DuplicateInstruction(_) => {
tx_by_addr::TransactionErrorType::DuplicateInstruction
}
} as i32,
instruction_error: match transaction_error {
TransactionError::InstructionError(index, ref instruction_error) => {
@ -1014,6 +1023,14 @@ impl From<TransactionError> for tx_by_addr::TransactionError {
}
_ => None,
},
duplicate_instruction_error: match transaction_error {
TransactionError::DuplicateInstruction(index) => {
Some(tx_by_addr::DuplicateInstructionError {
index: index as u32,
})
}
_ => None,
},
}
}
}
@ -1681,6 +1698,14 @@ mod test {
transaction_error,
tx_by_addr_transaction_error.try_into().unwrap()
);
let transaction_error = TransactionError::DuplicateInstruction(10);
let tx_by_addr_transaction_error: tx_by_addr::TransactionError =
transaction_error.clone().into();
assert_eq!(
transaction_error,
tx_by_addr_transaction_error.try_into().unwrap()
);
}
#[test]
@ -1689,15 +1714,31 @@ mod test {
let custom_error = 42;
for error in tx_by_addr::TransactionErrorType::into_enum_iter() {
if error != tx_by_addr::TransactionErrorType::InstructionError {
let tx_by_addr_error = tx_by_addr::TransactionError {
transaction_error: error as i32,
instruction_error: None,
};
let transaction_error: TransactionError = tx_by_addr_error
.clone()
.try_into()
.unwrap_or_else(|_| panic!("{:?} conversion implemented?", error));
assert_eq!(tx_by_addr_error, transaction_error.into());
if error == tx_by_addr::TransactionErrorType::DuplicateInstruction {
let tx_by_addr_error = tx_by_addr::TransactionError {
transaction_error: error as i32,
instruction_error: None,
duplicate_instruction_error: Some(tx_by_addr::DuplicateInstructionError {
index: ix_index,
}),
};
let transaction_error: TransactionError = tx_by_addr_error
.clone()
.try_into()
.unwrap_or_else(|_| panic!("{:?} conversion implemented?", error));
assert_eq!(tx_by_addr_error, transaction_error.into());
} else {
let tx_by_addr_error = tx_by_addr::TransactionError {
transaction_error: error as i32,
instruction_error: None,
duplicate_instruction_error: None,
};
let transaction_error: TransactionError = tx_by_addr_error
.clone()
.try_into()
.unwrap_or_else(|_| panic!("{:?} conversion implemented?", error));
assert_eq!(tx_by_addr_error, transaction_error.into());
}
} else {
for ix_error in tx_by_addr::InstructionErrorType::into_enum_iter() {
if ix_error != tx_by_addr::InstructionErrorType::Custom {
@ -1708,6 +1749,7 @@ mod test {
error: ix_error as i32,
custom: None,
}),
duplicate_instruction_error: None,
};
let transaction_error: TransactionError = tx_by_addr_error
.clone()
@ -1724,6 +1766,7 @@ mod test {
custom: custom_error,
}),
}),
duplicate_instruction_error: None,
};
let transaction_error: TransactionError =
tx_by_addr_error.clone().try_into().unwrap();