Add fees to tx-wide caps (#22081)

This commit is contained in:
Jack May 2022-02-11 16:23:16 -08:00 committed by GitHub
parent 12dffc105a
commit 3d9874b95a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1149 additions and 186 deletions

View File

@ -238,6 +238,7 @@ fn full_battery_tests(
#[test]
#[allow(clippy::redundant_closure)]
fn test_create_account_with_seed() {
const ONE_SIG_FEE: f64 = 0.000005;
solana_logger::setup();
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
@ -310,7 +311,7 @@ fn test_create_account_with_seed() {
&offline_nonce_authority_signer.pubkey(),
);
check_balance!(
sol_to_lamports(4000.999999999),
sol_to_lamports(4001.0 - ONE_SIG_FEE),
&rpc_client,
&online_nonce_creator_signer.pubkey(),
);
@ -381,12 +382,12 @@ fn test_create_account_with_seed() {
process_command(&submit_config).unwrap();
check_balance!(sol_to_lamports(241.0), &rpc_client, &nonce_address);
check_balance!(
sol_to_lamports(31.999999999),
sol_to_lamports(32.0 - ONE_SIG_FEE),
&rpc_client,
&offline_nonce_authority_signer.pubkey(),
);
check_balance!(
sol_to_lamports(4000.999999999),
sol_to_lamports(4001.0 - ONE_SIG_FEE),
&rpc_client,
&online_nonce_creator_signer.pubkey(),
);

View File

@ -18,6 +18,7 @@ use {
solana_sdk::{
account_utils::StateMut,
commitment_config::CommitmentConfig,
fee::FeeStructure,
nonce::State as NonceState,
pubkey::Pubkey,
signature::{keypair_from_seed, Keypair, Signer},
@ -876,14 +877,15 @@ fn test_stake_authorize() {
#[test]
fn test_stake_authorize_with_fee_payer() {
solana_logger::setup();
const SIG_FEE: u64 = 42;
let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
let test_validator = TestValidator::with_custom_fees(
mint_pubkey,
SIG_FEE,
1,
Some(faucet_addr),
SocketAddrSpace::Unspecified,
);
@ -912,14 +914,14 @@ fn test_stake_authorize_with_fee_payer() {
config_offline.command = CliCommand::ClusterVersion;
process_command(&config_offline).unwrap_err();
request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 100_000).unwrap();
check_balance!(100_000, &rpc_client, &config.signers[0].pubkey());
request_and_confirm_airdrop(&rpc_client, &config, &default_pubkey, 5_000_000).unwrap();
check_balance!(5_000_000, &rpc_client, &config.signers[0].pubkey());
request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 100_000).unwrap();
check_balance!(100_000, &rpc_client, &payer_pubkey);
request_and_confirm_airdrop(&rpc_client, &config_payer, &payer_pubkey, 5_000_000).unwrap();
check_balance!(5_000_000, &rpc_client, &payer_pubkey);
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
check_balance!(100_000, &rpc_client, &offline_pubkey);
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 5_000_000).unwrap();
check_balance!(5_000_000, &rpc_client, &offline_pubkey);
check_ready(&rpc_client);
@ -934,7 +936,7 @@ fn test_stake_authorize_with_fee_payer() {
withdrawer: None,
withdrawer_signer: None,
lockup: Lockup::default(),
amount: SpendAmount::Some(50_000),
amount: SpendAmount::Some(1_000_000),
sign_only: false,
dump_transaction_message: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
@ -945,8 +947,7 @@ fn test_stake_authorize_with_fee_payer() {
from: 0,
};
process_command(&config).unwrap();
// `config` balance should be 50,000 - 1 stake account sig - 1 fee sig
check_balance!(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
// Assign authority with separate fee payer
config.signers = vec![&default_signer, &payer_keypair];
@ -970,10 +971,10 @@ fn test_stake_authorize_with_fee_payer() {
};
process_command(&config).unwrap();
// `config` balance has not changed, despite submitting the TX
check_balance!(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
// `config_payer` however has paid `config`'s authority sig
// and `config_payer`'s fee sig
check_balance!(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey);
check_balance!(5_000_000 - fee_two_sig, &rpc_client, &payer_pubkey);
// Assign authority with offline fee payer
let blockhash = rpc_client.get_latest_blockhash().unwrap();
@ -1021,10 +1022,10 @@ fn test_stake_authorize_with_fee_payer() {
};
process_command(&config).unwrap();
// `config`'s balance again has not changed
check_balance!(50_000 - SIG_FEE - SIG_FEE, &rpc_client, &default_pubkey);
check_balance!(4_000_000 - fee_two_sig, &rpc_client, &default_pubkey);
// `config_offline` however has paid 1 sig due to being both authority
// and fee payer
check_balance!(100_000 - SIG_FEE, &rpc_client, &offline_pubkey);
check_balance!(5_000_000 - fee_one_sig, &rpc_client, &offline_pubkey);
}
#[test]
@ -1058,12 +1059,17 @@ fn test_stake_split() {
config_offline.command = CliCommand::ClusterVersion;
process_command(&config_offline).unwrap_err();
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
.unwrap();
check_balance!(500_000, &rpc_client, &config.signers[0].pubkey());
request_and_confirm_airdrop(
&rpc_client,
&config,
&config.signers[0].pubkey(),
50_000_000,
)
.unwrap();
check_balance!(50_000_000, &rpc_client, &config.signers[0].pubkey());
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
check_balance!(100_000, &rpc_client, &offline_pubkey);
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 1_000_000).unwrap();
check_balance!(1_000_000, &rpc_client, &offline_pubkey);
// Create stake account, identity is authority
let minimum_stake_balance = rpc_client
@ -1207,12 +1213,12 @@ fn test_stake_set_lockup() {
config_offline.command = CliCommand::ClusterVersion;
process_command(&config_offline).unwrap_err();
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 500_000)
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 5_000_000)
.unwrap();
check_balance!(500_000, &rpc_client, &config.signers[0].pubkey());
check_balance!(5_000_000, &rpc_client, &config.signers[0].pubkey());
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 100_000).unwrap();
check_balance!(100_000, &rpc_client, &offline_pubkey);
request_and_confirm_airdrop(&rpc_client, &config_offline, &offline_pubkey, 1_000_000).unwrap();
check_balance!(1_000_000, &rpc_client, &offline_pubkey);
// Create stake account, identity is authority
let minimum_stake_balance = rpc_client

View File

@ -16,6 +16,7 @@ use {
solana_faucet::faucet::run_local_faucet,
solana_sdk::{
commitment_config::CommitmentConfig,
fee::FeeStructure,
native_token::sol_to_lamports,
nonce::State as NonceState,
pubkey::Pubkey,
@ -29,6 +30,8 @@ use {
#[test]
fn test_transfer() {
solana_logger::setup();
let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
@ -77,7 +80,11 @@ fn test_transfer() {
derived_address_program_id: None,
};
process_command(&config).unwrap();
check_balance!(sol_to_lamports(4.0) - 1, &rpc_client, &sender_pubkey);
check_balance!(
sol_to_lamports(4.0) - fee_one_sig,
&rpc_client,
&sender_pubkey
);
check_balance!(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
// Plain ole transfer, failure due to InsufficientFundsForSpendAndFee
@ -98,7 +105,11 @@ fn test_transfer() {
derived_address_program_id: None,
};
assert!(process_command(&config).is_err());
check_balance!(sol_to_lamports(4.0) - 1, &rpc_client, &sender_pubkey);
check_balance!(
sol_to_lamports(4.0) - fee_one_sig,
&rpc_client,
&sender_pubkey
);
check_balance!(sol_to_lamports(1.0), &rpc_client, &recipient_pubkey);
let mut offline = CliConfig::recent_for_tests();
@ -154,7 +165,11 @@ fn test_transfer() {
derived_address_program_id: None,
};
process_command(&config).unwrap();
check_balance!(sol_to_lamports(0.5) - 1, &rpc_client, &offline_pubkey);
check_balance!(
sol_to_lamports(0.5) - fee_one_sig,
&rpc_client,
&offline_pubkey
);
check_balance!(sol_to_lamports(1.5), &rpc_client, &recipient_pubkey);
// Create nonce account
@ -172,7 +187,7 @@ fn test_transfer() {
};
process_command(&config).unwrap();
check_balance!(
sol_to_lamports(4.0) - 3 - minimum_nonce_balance,
sol_to_lamports(4.0) - fee_one_sig - fee_two_sig - minimum_nonce_balance,
&rpc_client,
&sender_pubkey,
);
@ -210,7 +225,7 @@ fn test_transfer() {
};
process_command(&config).unwrap();
check_balance!(
sol_to_lamports(3.0) - 4 - minimum_nonce_balance,
sol_to_lamports(3.0) - 2 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
&rpc_client,
&sender_pubkey,
);
@ -235,7 +250,7 @@ fn test_transfer() {
};
process_command(&config).unwrap();
check_balance!(
sol_to_lamports(3.0) - 5 - minimum_nonce_balance,
sol_to_lamports(3.0) - 3 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
&rpc_client,
&sender_pubkey,
);
@ -293,13 +308,18 @@ fn test_transfer() {
derived_address_program_id: None,
};
process_command(&config).unwrap();
check_balance!(sol_to_lamports(0.1) - 2, &rpc_client, &offline_pubkey);
check_balance!(
sol_to_lamports(0.1) - 2 * fee_one_sig,
&rpc_client,
&offline_pubkey
);
check_balance!(sol_to_lamports(2.9), &rpc_client, &recipient_pubkey);
}
#[test]
fn test_transfer_multisession_signing() {
solana_logger::setup();
let fee = FeeStructure::default().get_max_fee(2, 0);
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
@ -329,7 +349,7 @@ fn test_transfer_multisession_signing() {
&rpc_client,
&CliConfig::recent_for_tests(),
&offline_fee_payer_signer.pubkey(),
sol_to_lamports(1.0) + 3,
sol_to_lamports(1.0) + 2 * fee,
)
.unwrap();
check_balance!(
@ -338,7 +358,7 @@ fn test_transfer_multisession_signing() {
&offline_from_signer.pubkey(),
);
check_balance!(
sol_to_lamports(1.0) + 3,
sol_to_lamports(1.0) + 2 * fee,
&rpc_client,
&offline_fee_payer_signer.pubkey(),
);
@ -438,7 +458,7 @@ fn test_transfer_multisession_signing() {
&offline_from_signer.pubkey(),
);
check_balance!(
sol_to_lamports(1.0) + 1,
sol_to_lamports(1.0) + fee,
&rpc_client,
&offline_fee_payer_signer.pubkey(),
);
@ -448,6 +468,7 @@ fn test_transfer_multisession_signing() {
#[test]
fn test_transfer_all() {
solana_logger::setup();
let fee = FeeStructure::default().get_max_fee(1, 0);
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
@ -470,8 +491,8 @@ fn test_transfer_all() {
let sender_pubkey = config.signers[0].pubkey();
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
check_balance!(50_000, &rpc_client, &sender_pubkey);
request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 500_000).unwrap();
check_balance!(500_000, &rpc_client, &sender_pubkey);
check_balance!(0, &rpc_client, &recipient_pubkey);
check_ready(&rpc_client);
@ -495,7 +516,7 @@ fn test_transfer_all() {
};
process_command(&config).unwrap();
check_balance!(0, &rpc_client, &sender_pubkey);
check_balance!(49_999, &rpc_client, &recipient_pubkey);
check_balance!(500_000 - fee, &rpc_client, &recipient_pubkey);
}
#[test]
@ -554,6 +575,7 @@ fn test_transfer_unfunded_recipient() {
#[test]
fn test_transfer_with_seed() {
solana_logger::setup();
let fee = FeeStructure::default().get_max_fee(1, 0);
let mint_keypair = Keypair::new();
let mint_pubkey = mint_keypair.pubkey();
let faucet_addr = run_local_faucet(mint_keypair, None);
@ -612,7 +634,7 @@ fn test_transfer_with_seed() {
derived_address_program_id: Some(derived_address_program_id),
};
process_command(&config).unwrap();
check_balance!(sol_to_lamports(1.0) - 1, &rpc_client, &sender_pubkey);
check_balance!(sol_to_lamports(1.0) - fee, &rpc_client, &sender_pubkey);
check_balance!(sol_to_lamports(5.0), &rpc_client, &recipient_pubkey);
check_balance!(0, &rpc_client, &derived_address);
}

View File

@ -48,8 +48,12 @@ The policy is as follows:
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.
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.
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-buget).
The following operations incur a compute cost:
@ -60,12 +64,12 @@ The following operations incur a compute cost:
- cross-program invocations
- ...
For cross-program invocations the programs invoked inherit the budget of their
For cross-program invocations, the programs invoked inherit the budget of their
parent. If an invoked program consume the budget or exceeds a bound the entire
invocation chain and the parent are halted.
invocation chain is halted.
The current [compute
budget](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/src/process_instruction.rs#L65)
budget](https://github.com/solana-labs/solana/blob/0224a8b127ace4c6453dd6492a38c66cb999abd2/sdk/src/compute_budget.rs#L102)
can be found in the Solana SDK.
For example, if the current budget is:
@ -80,6 +84,7 @@ max_invoke_depth: 4,
max_call_depth: 64,
stack_frame_size: 4096,
log_pubkey_units: 100,
...
```
Then the program
@ -90,7 +95,7 @@ Then the program
- Can not exceed a BPF call depth of 64
- Cannot exceed 4 levels of cross-program invocations.
Since the compute budget is consumed incrementally as the program executes the
Since the compute budget is consumed incrementally as the program executes, the
total budget consumption will be a combination of the various costs of the
operations it performs.
@ -98,12 +103,38 @@ 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.
The budget values are conditional on feature enablement, take a look at the
compute budget's
[new](https://github.com/solana-labs/solana/blob/d3a3a7548c857f26ec2cb10e270da72d373020ec/sdk/src/process_instruction.rs#L97)
function to find out how the budget is constructed. An understanding of how
[features](runtime.md#features) work and what features are enabled on the
cluster being used are required to determine the current budget's values.
## Transaction-wide Compute Budget
Transactions are processed as a single entity and are the primary unit of block
scheduling. In order to facilitate better block scheduling and account for the
computational cost of each transaction, the compute budget is moving to a
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
default number of maximum units remains at 200k which means the sum of the
compute units used by each instruction in the transaction must not exceed that
value. The number of maximum units allows is intentionally kept small to
facilitate optimized programs and form the bases for a minimum fee level.
There are a lot of uses cases that require more than 200k units
transaction-wide. To enable these uses cases transactions can include a
[``ComputeBudgetInstruction`](https://github.com/solana-labs/solana/blob/0224a8b127ace4c6453dd6492a38c66cb999abd2/sdk/src/compute_budget.rs#L44)
requesting a higher compute unit cap. Higher compute caps will be charged
higher 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 crate
these instructions:
```rust
let instruction = ComputeBudgetInstruction::request_units(300_000);
```
## New Features

View File

@ -1,16 +1,13 @@
use {
solana_sdk::{
borsh::try_from_slice_unchecked,
compute_budget::{self, ComputeBudgetInstruction},
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
feature_set::{requestable_heap_size, FeatureSet},
instruction::InstructionError,
transaction::{SanitizedTransaction, TransactionError},
},
std::sync::Arc,
use solana_sdk::{
borsh::try_from_slice_unchecked,
compute_budget::{self, ComputeBudgetInstruction},
entrypoint::HEAP_LENGTH as MIN_HEAP_FRAME_BYTES,
instruction::InstructionError,
message::SanitizedMessage,
transaction::TransactionError,
};
const MAX_UNITS: u32 = 1_000_000;
const MAX_UNITS: u32 = 1_400_000;
const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1024;
#[cfg(RUSTC_WITH_SPECIALIZATION)]
@ -68,14 +65,19 @@ pub struct ComputeBudget {
impl Default for ComputeBudget {
fn default() -> Self {
Self::new()
Self::new(true)
}
}
impl ComputeBudget {
pub fn new() -> Self {
pub fn new(use_max_units_default: bool) -> Self {
let max_units = if use_max_units_default {
MAX_UNITS
} else {
200_000
} as u64;
ComputeBudget {
max_units: 200_000,
max_units,
log_64_units: 100,
create_program_address_units: 1500,
invoke_units: 1000,
@ -97,25 +99,27 @@ impl ComputeBudget {
}
}
pub fn process_transaction(
pub fn process_message(
&mut self,
tx: &SanitizedTransaction,
feature_set: Arc<FeatureSet>,
) -> Result<(), TransactionError> {
message: &SanitizedMessage,
requestable_heap_size: bool,
) -> Result<u64, TransactionError> {
let mut requested_additional_fee = 0;
let error = TransactionError::InstructionError(0, InstructionError::InvalidInstructionData);
// Compute budget instruction must be in the 1st 3 instructions (avoid
// nonce marker), otherwise ignored
for (program_id, instruction) in tx.message().program_instructions_iter().take(3) {
for (program_id, instruction) in message.program_instructions_iter().take(3) {
if compute_budget::check_id(program_id) {
match try_from_slice_unchecked(&instruction.data) {
Ok(ComputeBudgetInstruction::RequestUnits(units)) => {
if units > MAX_UNITS {
return Err(error);
}
self.max_units = units as u64;
Ok(ComputeBudgetInstruction::RequestUnits {
units,
additional_fee,
}) => {
self.max_units = units.min(MAX_UNITS) as u64;
requested_additional_fee = additional_fee as u64;
}
Ok(ComputeBudgetInstruction::RequestHeapFrame(bytes)) => {
if !feature_set.is_active(&requestable_heap_size::id())
if !requestable_heap_size
|| bytes > MAX_HEAP_FRAME_BYTES
|| bytes < MIN_HEAP_FRAME_BYTES as u32
|| bytes % 1024 != 0
@ -128,7 +132,7 @@ impl ComputeBudget {
}
}
}
Ok(())
Ok(requested_additional_fee)
}
}
@ -137,8 +141,13 @@ mod tests {
use {
super::*,
solana_sdk::{
hash::Hash, instruction::Instruction, message::Message, pubkey::Pubkey,
signature::Keypair, signer::Signer, transaction::Transaction,
hash::Hash,
instruction::Instruction,
message::Message,
pubkey::Pubkey,
signature::Keypair,
signer::Signer,
transaction::{SanitizedTransaction, Transaction},
},
};
@ -150,24 +159,23 @@ mod tests {
Message::new($instructions, Some(&payer_keypair.pubkey())),
Hash::default(),
));
let feature_set = Arc::new(FeatureSet::all_enabled());
let mut compute_budget = ComputeBudget::default();
let result = compute_budget.process_transaction(&tx, feature_set);
assert_eq!($expected_error as Result<(), TransactionError>, result);
let result = compute_budget.process_message(&tx.message(), true);
assert_eq!($expected_error, result);
assert_eq!(compute_budget, $expected_budget);
};
}
#[test]
fn test_process_transaction() {
fn test_process_mesage() {
// Units
test!(&[], Ok(()), ComputeBudget::default());
test!(&[], Ok(0), ComputeBudget::default());
test!(
&[
ComputeBudgetInstruction::request_units(1),
ComputeBudgetInstruction::request_units(1, 0),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(()),
Ok(0),
ComputeBudget {
max_units: 1,
..ComputeBudget::default()
@ -175,21 +183,18 @@ mod tests {
);
test!(
&[
ComputeBudgetInstruction::request_units(MAX_UNITS + 1),
ComputeBudgetInstruction::request_units(MAX_UNITS + 1, 0),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Err(TransactionError::InstructionError(
0,
InstructionError::InvalidInstructionData,
)),
Ok(0),
ComputeBudget::default()
);
test!(
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_units(MAX_UNITS),
ComputeBudgetInstruction::request_units(MAX_UNITS, 0),
],
Ok(()),
Ok(0),
ComputeBudget {
max_units: MAX_UNITS as u64,
..ComputeBudget::default()
@ -200,20 +205,20 @@ 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),
ComputeBudgetInstruction::request_units(1, 0),
],
Ok(()),
Ok(0),
ComputeBudget::default()
);
// HeapFrame
test!(&[], Ok(()), ComputeBudget::default());
test!(&[], Ok(0), ComputeBudget::default());
test!(
&[
ComputeBudgetInstruction::request_heap_frame(40 * 1024),
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
],
Ok(()),
Ok(0),
ComputeBudget {
heap_size: Some(40 * 1024),
..ComputeBudget::default()
@ -257,7 +262,7 @@ mod tests {
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
],
Ok(()),
Ok(0),
ComputeBudget {
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),
..ComputeBudget::default()
@ -270,7 +275,7 @@ mod tests {
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_heap_frame(1), // ignored
],
Ok(()),
Ok(0),
ComputeBudget::default()
);
@ -279,9 +284,9 @@ mod tests {
&[
Instruction::new_with_bincode(Pubkey::new_unique(), &0, vec![]),
ComputeBudgetInstruction::request_heap_frame(MAX_HEAP_FRAME_BYTES),
ComputeBudgetInstruction::request_units(MAX_UNITS),
ComputeBudgetInstruction::request_units(MAX_UNITS, 0),
],
Ok(()),
Ok(0),
ComputeBudget {
max_units: MAX_UNITS as u64,
heap_size: Some(MAX_HEAP_FRAME_BYTES as usize),

View File

@ -1610,11 +1610,12 @@ mod tests {
let mut transaction_context = TransactionContext::new(accounts, 1, 3);
let mut invoke_context = InvokeContext::new_mock(&mut transaction_context, &[]);
invoke_context.feature_set = Arc::new(feature_set);
invoke_context.compute_budget = ComputeBudget::new(false);
invoke_context.push(&[], &[0], &[]).unwrap();
assert_eq!(
*invoke_context.get_compute_budget(),
ComputeBudget::default()
ComputeBudget::new(false)
);
invoke_context.pop().unwrap();
@ -1622,7 +1623,7 @@ mod tests {
let expected_compute_budget = ComputeBudget {
max_units: 500_000,
heap_size: Some(256_usize.saturating_mul(1024)),
..ComputeBudget::default()
..ComputeBudget::new(false)
};
assert_eq!(
*invoke_context.get_compute_budget(),
@ -1633,7 +1634,7 @@ mod tests {
invoke_context.push(&[], &[0], &[]).unwrap();
assert_eq!(
*invoke_context.get_compute_budget(),
ComputeBudget::default()
ComputeBudget::new(false)
);
invoke_context.pop().unwrap();
}

View File

@ -46,6 +46,9 @@ use solana_sdk::{
clock::MAX_PROCESSING_AGE,
compute_budget::ComputeBudgetInstruction,
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
feature_set::FeatureSet,
fee::FeeStructure,
fee_calculator::FeeRateGovernor,
instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError},
loader_instruction,
message::{v0::LoadedAddresses, Message, SanitizedMessage},
@ -397,7 +400,7 @@ fn execute_transactions(
),
}
.expect("lamports_per_signature must be available");
let fee = Bank::get_fee_for_message_with_lamports_per_signature(
let fee = bank.get_fee_for_message_with_lamports_per_signature(
&SanitizedMessage::try_from(tx.message().clone()).unwrap(),
lamports_per_signature,
);
@ -1383,7 +1386,7 @@ fn test_program_bpf_compute_budget() {
);
let message = Message::new(
&[
ComputeBudgetInstruction::request_units(1),
ComputeBudgetInstruction::request_units(1, 0),
Instruction::new_with_bincode(program_id, &0, vec![]),
],
Some(&mint_keypair.pubkey()),
@ -2886,8 +2889,8 @@ fn test_program_bpf_realloc() {
.unwrap();
}
#[cfg(feature = "bpf_rust")]
#[test]
#[cfg(feature = "bpf_rust")]
fn test_program_bpf_realloc_invoke() {
solana_logger::setup();
@ -3418,3 +3421,76 @@ fn test_program_bpf_processed_inner_instruction() {
.send_and_confirm_message(&[&mint_keypair], message)
.is_ok());
}
#[test]
#[cfg(feature = "bpf_rust")]
fn test_program_fees() {
solana_logger::setup();
let congestion_multiplier = 1;
let GenesisConfigInfo {
mut genesis_config,
mint_keypair,
..
} = create_genesis_config(500_000_000);
genesis_config.fee_rate_governor = FeeRateGovernor::new(congestion_multiplier, 0);
let mut bank = Bank::new_for_tests(&genesis_config);
let fee_structure =
FeeStructure::new(0.000005, 0.0, vec![(200, 0.0000005), (1400000, 0.000005)]);
bank.fee_structure = fee_structure.clone();
bank.feature_set = Arc::new(FeatureSet::all_enabled());
let (name, id, entrypoint) = solana_bpf_loader_program!();
bank.add_builtin(&name, &id, entrypoint);
let bank_client = BankClient::new(bank);
let program_id = load_bpf_program(
&bank_client,
&bpf_loader::id(),
&mint_keypair,
"solana_bpf_rust_noop",
);
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
let message = Message::new(
&[Instruction::new_with_bytes(program_id, &[], vec![])],
Some(&mint_keypair.pubkey()),
);
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
let expected_max_fee = Bank::calculate_fee(
&sanitized_message,
congestion_multiplier,
&fee_structure,
true,
);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
.unwrap();
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
assert_eq!(pre_balance - post_balance, expected_max_fee);
let pre_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
let message = Message::new(
&[
ComputeBudgetInstruction::request_units(100, 42),
Instruction::new_with_bytes(program_id, &[], vec![]),
],
Some(&mint_keypair.pubkey()),
);
let sanitized_message = SanitizedMessage::try_from(message.clone()).unwrap();
let expected_min_fee = Bank::calculate_fee(
&sanitized_message,
congestion_multiplier,
&fee_structure,
true,
);
assert!(expected_min_fee < expected_max_fee);
bank_client
.send_and_confirm_message(&[&mint_keypair], message)
.unwrap();
let post_balance = bank_client.get_balance(&mint_keypair.pubkey()).unwrap();
assert_eq!(pre_balance - post_balance, expected_min_fee);
}

View File

@ -7,7 +7,7 @@ use {
blockstore_processor::{TransactionStatusBatch, TransactionStatusMessage},
},
solana_runtime::bank::{
Bank, DurableNonceFee, TransactionExecutionDetails, TransactionExecutionResult,
DurableNonceFee, TransactionExecutionDetails, TransactionExecutionResult,
},
solana_transaction_status::{
extract_and_fmt_memos, InnerInstructions, Reward, TransactionStatusMeta,
@ -109,7 +109,7 @@ impl TransactionStatusService {
),
}
.expect("lamports_per_signature must be available");
let fee = Bank::get_fee_for_message_with_lamports_per_signature(
let fee = bank.get_fee_for_message_with_lamports_per_signature(
transaction.message(),
lamports_per_signature,
);
@ -204,7 +204,7 @@ pub(crate) mod tests {
dashmap::DashMap,
solana_account_decoder::parse_token::token_amount_to_ui_amount,
solana_ledger::{genesis_utils::create_genesis_config, get_tmp_ledger_path},
solana_runtime::bank::{NonceFull, NoncePartial, RentDebits, TransactionBalancesSet},
solana_runtime::bank::{Bank, NonceFull, NoncePartial, RentDebits, TransactionBalancesSet},
solana_sdk::{
account_utils::StateMut,
clock::Slot,

View File

@ -28,7 +28,8 @@ use {
account_utils::StateMut,
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
clock::{BankId, Slot, INITIAL_RENT_EPOCH},
feature_set::{self, FeatureSet},
feature_set::{self, tx_wide_compute_cap, FeatureSet},
fee::FeeStructure,
genesis_config::ClusterType,
hash::Hash,
message::{
@ -472,6 +473,7 @@ impl Accounts {
error_counters: &mut ErrorCounters,
rent_collector: &RentCollector,
feature_set: &FeatureSet,
fee_structure: &FeeStructure,
) -> Vec<TransactionLoadResult> {
txs.iter()
.zip(lock_results)
@ -484,7 +486,12 @@ impl Accounts {
hash_queue.get_lamports_per_signature(tx.message().recent_blockhash())
});
let fee = if let Some(lamports_per_signature) = lamports_per_signature {
Bank::calculate_fee(tx.message(), lamports_per_signature)
Bank::calculate_fee(
tx.message(),
lamports_per_signature,
fee_structure,
feature_set.is_active(&tx_wide_compute_cap::id()),
)
} else {
return (Err(TransactionError::BlockhashNotFound), None);
};
@ -1359,6 +1366,8 @@ mod tests {
lamports_per_signature: u64,
rent_collector: &RentCollector,
error_counters: &mut ErrorCounters,
feature_set: &FeatureSet,
fee_structure: &FeeStructure,
) -> Vec<TransactionLoadResult> {
let mut hash_queue = BlockhashQueue::new(100);
hash_queue.register_hash(&tx.message().recent_blockhash, lamports_per_signature);
@ -1382,7 +1391,8 @@ mod tests {
&hash_queue,
error_counters,
rent_collector,
&FeatureSet::all_enabled(),
feature_set,
fee_structure,
)
}
@ -1398,6 +1408,8 @@ mod tests {
lamports_per_signature,
&RentCollector::default(),
error_counters,
&FeatureSet::all_enabled(),
&FeeStructure::default(),
)
}
@ -1549,6 +1561,8 @@ mod tests {
let fee = Bank::calculate_fee(
&SanitizedMessage::try_from(tx.message().clone()).unwrap(),
10,
&FeeStructure::default(),
false,
);
assert_eq!(fee, 10);
@ -1595,6 +1609,8 @@ mod tests {
#[test]
fn test_load_accounts_fee_payer_is_nonce() {
let mut error_counters = ErrorCounters::default();
let mut feature_set = FeatureSet::all_enabled();
feature_set.deactivate(&tx_wide_compute_cap::id());
let rent_collector = RentCollector::new(
0,
&EpochSchedule::default(),
@ -1631,6 +1647,8 @@ mod tests {
min_balance,
&rent_collector,
&mut error_counters,
&feature_set,
&FeeStructure::default(),
);
assert_eq!(loaded_accounts.len(), 1);
let (load_res, _nonce) = &loaded_accounts[0];
@ -1645,6 +1663,8 @@ mod tests {
min_balance,
&rent_collector,
&mut error_counters,
&feature_set,
&FeeStructure::default(),
);
assert_eq!(loaded_accounts.len(), 1);
let (load_res, _nonce) = &loaded_accounts[0];
@ -1658,6 +1678,8 @@ mod tests {
min_balance,
&rent_collector,
&mut error_counters,
&feature_set,
&FeeStructure::default(),
);
assert_eq!(loaded_accounts.len(), 1);
let (load_res, _nonce) = &loaded_accounts[0];
@ -2982,6 +3004,7 @@ mod tests {
&mut error_counters,
&rent_collector,
&FeatureSet::all_enabled(),
&FeeStructure::default(),
)
}

File diff suppressed because it is too large Load Diff

View File

@ -272,7 +272,7 @@ mod tests {
None,
executors.clone(),
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),
@ -314,7 +314,7 @@ mod tests {
None,
executors.clone(),
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),
@ -346,7 +346,7 @@ mod tests {
None,
executors,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),
@ -481,7 +481,7 @@ mod tests {
None,
executors.clone(),
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),
@ -514,7 +514,7 @@ mod tests {
None,
executors.clone(),
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),
@ -544,7 +544,7 @@ mod tests {
None,
executors,
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),
@ -623,7 +623,7 @@ mod tests {
None,
Rc::new(RefCell::new(Executors::default())),
Arc::new(FeatureSet::all_enabled()),
ComputeBudget::new(),
ComputeBudget::default(),
&mut ExecuteTimings::default(),
&sysvar_cache,
Hash::default(),

View File

@ -2,38 +2,49 @@
use {
crate::instruction::Instruction,
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
borsh::{BorshDeserialize, BorshSerialize},
};
crate::declare_id!("ComputeBudget111111111111111111111111111111");
/// Compute Budget Instructions
#[derive(
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
BorshSchema,
Debug,
Clone,
PartialEq,
AbiExample,
AbiEnumVisitor,
BorshDeserialize,
BorshSerialize,
Clone,
Debug,
Deserialize,
PartialEq,
Serialize,
)]
pub enum ComputeBudgetInstruction {
/// Request a specific maximum number of compute units the transaction is
/// allowed to consume.
RequestUnits(u32),
/// Request a specific transaction-wide program heap frame size in bytes.
/// The value requested must be a multiple of 1024. This new heap frame size
/// applies to each program executed, including all calls to CPIs.
/// allowed to consume and an additional fee to pay.
RequestUnits {
/// Units to request
units: u32,
/// Additional fee to add
additional_fee: u32,
},
/// 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.
RequestHeapFrame(u32),
}
impl ComputeBudgetInstruction {
/// Create a `ComputeBudgetInstruction::RequestUnits` `Instruction`
pub fn request_units(units: u32) -> Instruction {
Instruction::new_with_borsh(id(), &ComputeBudgetInstruction::RequestUnits(units), vec![])
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`

67
sdk/src/fee.rs Normal file
View File

@ -0,0 +1,67 @@
use crate::native_token::sol_to_lamports;
/// A fee and its associated compute unit limit
#[derive(Debug, Default, Clone)]
pub struct FeeBin {
/// maximum compute units for which this fee will be charged
pub limit: u64,
/// fee in lamports
pub fee: u64,
}
/// Information used to calculate fees
#[derive(Debug, Clone)]
pub struct FeeStructure {
/// lamports per signature
pub lamports_per_signature: u64,
/// lamports_per_write_lock
pub lamports_per_write_lock: u64,
/// Compute unit fee bins
pub compute_fee_bins: Vec<FeeBin>,
}
impl FeeStructure {
pub fn new(
sol_per_signature: f64,
sol_per_write_lock: f64,
compute_fee_bins: Vec<(u64, f64)>,
) -> Self {
let compute_fee_bins = compute_fee_bins
.iter()
.map(|(limit, sol)| FeeBin {
limit: *limit,
fee: sol_to_lamports(*sol),
})
.collect::<Vec<_>>();
FeeStructure {
lamports_per_signature: sol_to_lamports(sol_per_signature),
lamports_per_write_lock: sol_to_lamports(sol_per_write_lock),
compute_fee_bins,
}
}
pub fn get_max_fee(&self, num_signatures: u64, num_write_locks: u64) -> u64 {
num_signatures
.saturating_mul(self.lamports_per_signature)
.saturating_add(num_write_locks.saturating_mul(self.lamports_per_write_lock))
.saturating_add(
self.compute_fee_bins
.last()
.map(|bin| bin.fee)
.unwrap_or_default(),
)
}
}
impl Default for FeeStructure {
fn default() -> Self {
Self::new(0.000005, 0.0, vec![(1_400_000, 0.0)])
}
}
#[cfg(RUSTC_WITH_SPECIALIZATION)]
impl ::solana_frozen_abi::abi_example::AbiExample for FeeStructure {
fn example() -> Self {
FeeStructure::default()
}
}

View File

@ -25,6 +25,7 @@ pub mod example_mocks;
pub mod exit;
pub mod feature;
pub mod feature_set;
pub mod fee;
pub mod genesis_config;
pub mod hard_forks;
pub mod hash;

View File

@ -1585,13 +1585,10 @@ mod tests {
#[test]
fn test_check_payer_balances_distribute_tokens_single_payer() {
let fees = 10_000;
let fees_in_sol = lamports_to_sol(fees);
let alice = Keypair::new();
let test_validator = TestValidator::with_custom_fees(
alice.pubkey(),
fees,
10_000,
None,
SocketAddrSpace::Unspecified,
);
@ -1601,6 +1598,11 @@ mod tests {
let sender_keypair_file = tmp_file_path("keypair_file", &alice.pubkey());
write_keypair_file(&alice, &sender_keypair_file).unwrap();
let fees = client
.get_fee_for_message(&one_signer_message(&client))
.unwrap();
let fees_in_sol = lamports_to_sol(fees);
let allocation_amount = 1000.0;
// Fully funded payer
@ -1678,12 +1680,10 @@ mod tests {
#[test]
fn test_check_payer_balances_distribute_tokens_separate_payers() {
let fees = 10_000;
let fees_in_sol = lamports_to_sol(fees);
let alice = Keypair::new();
let test_validator = TestValidator::with_custom_fees(
alice.pubkey(),
fees,
10_000,
None,
SocketAddrSpace::Unspecified,
);
@ -1691,6 +1691,11 @@ mod tests {
let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
let fees = client
.get_fee_for_message(&one_signer_message(&client))
.unwrap();
let fees_in_sol = lamports_to_sol(fees);
let sender_keypair_file = tmp_file_path("keypair_file", &alice.pubkey());
write_keypair_file(&alice, &sender_keypair_file).unwrap();
@ -1802,18 +1807,21 @@ mod tests {
#[test]
fn test_check_payer_balances_distribute_stakes_single_payer() {
let fees = 10_000;
let fees_in_sol = lamports_to_sol(fees);
let alice = Keypair::new();
let test_validator = TestValidator::with_custom_fees(
alice.pubkey(),
fees,
10_000,
None,
SocketAddrSpace::Unspecified,
);
let url = test_validator.rpc_url();
let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
let fees = client
.get_fee_for_message(&one_signer_message(&client))
.unwrap();
let fees_in_sol = lamports_to_sol(fees);
let sender_keypair_file = tmp_file_path("keypair_file", &alice.pubkey());
write_keypair_file(&alice, &sender_keypair_file).unwrap();
@ -1925,12 +1933,10 @@ mod tests {
#[test]
fn test_check_payer_balances_distribute_stakes_separate_payers() {
let fees = 10_000;
let fees_in_sol = lamports_to_sol(fees);
let alice = Keypair::new();
let test_validator = TestValidator::with_custom_fees(
alice.pubkey(),
fees,
10_000,
None,
SocketAddrSpace::Unspecified,
);
@ -1938,6 +1944,11 @@ mod tests {
let client = RpcClient::new_with_commitment(url, CommitmentConfig::processed());
let fees = client
.get_fee_for_message(&one_signer_message(&client))
.unwrap();
let fees_in_sol = lamports_to_sol(fees);
let sender_keypair_file = tmp_file_path("keypair_file", &alice.pubkey());
write_keypair_file(&alice, &sender_keypair_file).unwrap();