support 64-bit prioritization fee (#25027)
This commit is contained in:
parent
896729f25e
commit
cde15ff687
|
@ -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,
|
||||
|
|
|
@ -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()],
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -127,43 +127,96 @@ 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)) => {
|
||||
requested_heap_size = Some((bytes, 0));
|
||||
}
|
||||
_ => {
|
||||
return Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::InvalidInstructionData,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(error);
|
||||
return Err(TransactionError::InstructionError(
|
||||
i,
|
||||
InstructionError::InvalidInstructionData,
|
||||
));
|
||||
}
|
||||
self.heap_size = Some(bytes as usize);
|
||||
}
|
||||
_ => return Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.max_units = if default_units_per_instruction {
|
||||
requested_units
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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())))
|
||||
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);
|
||||
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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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![])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
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();
|
||||
|
|
Loading…
Reference in New Issue