From 90bedd7e067b5b8f3ddbb45da00a4e9cabb22c62 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Fri, 28 Feb 2020 13:27:01 -0700 Subject: [PATCH] Split signature throughput tracking out of `FeeCalculator` (#8447) * SDK: Split new `FeeRateGovernor` out of `FeeCalculator` Leaving `FeeCalculator` to *only* calculate transaction fees * Replace `FeeCalculator` with `FeeRateGovernor` as appropriate * Expose recent `FeeRateGovernor` to clients * Move `burn()` back into `FeeCalculator` Appease BPF tests * Revert "Move `burn()` back into `FeeCalculator`" This reverts commit f3035624307196722b62ff8b74c12cfcc13b1941. * Adjust BPF `Fee` sysvar test to reflect removal of `burn()` from `FeeCalculator` * Make `FeeRateGovernor`'s `lamports_per_signature` private * rebase artifacts * fmt * Drop 'Recent' * Drop _with_commitment variant * Use a more portable integer for `target_signatures_per_slot` * Add docs for `getReeRateCalculator` JSON RPC method * Don't return `lamports_per_signature` in `getFeeRateGovernor` JSONRPC reply --- bench-tps/src/bench.rs | 10 +-- bench-tps/src/cli.rs | 4 +- bench-tps/src/main.rs | 4 +- cli/src/offline.rs | 2 +- client/src/mock_rpc_client_request.rs | 6 +- client/src/rpc_client.rs | 31 ++++++- client/src/rpc_request.rs | 6 ++ client/src/rpc_response.rs | 8 +- client/src/thin_client.rs | 7 +- core/src/rpc.rs | 54 +++++++++++- core/src/validator.rs | 4 +- docs/src/apps/jsonrpc-api.md | 29 +++++++ genesis/src/main.rs | 18 ++-- genesis/src/stakes.rs | 2 +- programs/bpf/rust/sysval/src/lib.rs | 4 +- runtime/src/accounts.rs | 4 +- runtime/src/bank.rs | 63 +++++++++----- runtime/src/bank_client.rs | 6 +- runtime/src/genesis_utils.rs | 6 +- sdk/src/client.rs | 5 +- sdk/src/fee_calculator.rs | 113 +++++++++++++++++--------- sdk/src/genesis_config.rs | 30 +++---- 22 files changed, 298 insertions(+), 118 deletions(-) diff --git a/bench-tps/src/bench.rs b/bench-tps/src/bench.rs index 5ba2f08a67..265b1c2194 100644 --- a/bench-tps/src/bench.rs +++ b/bench-tps/src/bench.rs @@ -1059,8 +1059,8 @@ pub fn generate_and_fund_keypairs( // pay for the transaction fees in a new run. let enough_lamports = 8 * lamports_per_account / 10; if first_keypair_balance < enough_lamports || last_keypair_balance < enough_lamports { - let (_blockhash, fee_calculator) = get_recent_blockhash(client.as_ref()); - let max_fee = fee_calculator.max_lamports_per_signature; + let fee_rate_governor = client.get_fee_rate_governor().unwrap(); + let max_fee = fee_rate_governor.max_lamports_per_signature; let extra_fees = extra * max_fee; let total_keypairs = keypairs.len() as u64 + 1; // Add one for funding keypair let mut total = lamports_per_account * total_keypairs + extra_fees; @@ -1134,7 +1134,7 @@ mod tests { use solana_runtime::bank::Bank; use solana_runtime::bank_client::BankClient; use solana_sdk::client::SyncClient; - use solana_sdk::fee_calculator::FeeCalculator; + use solana_sdk::fee_calculator::FeeRateGovernor; use solana_sdk::genesis_config::create_genesis_config; #[test] @@ -1181,8 +1181,8 @@ mod tests { #[test] fn test_bench_tps_fund_keys_with_fees() { let (mut genesis_config, id) = create_genesis_config(10_000); - let fee_calculator = FeeCalculator::new(11, 0); - genesis_config.fee_calculator = fee_calculator; + let fee_rate_governor = FeeRateGovernor::new(11, 0); + genesis_config.fee_rate_governor = fee_rate_governor; let bank = Bank::new(&genesis_config); let client = Arc::new(BankClient::new(bank)); let keypair_count = 20; diff --git a/bench-tps/src/cli.rs b/bench-tps/src/cli.rs index 609eacf949..3efa6eb763 100644 --- a/bench-tps/src/cli.rs +++ b/bench-tps/src/cli.rs @@ -1,6 +1,6 @@ use clap::{crate_description, crate_name, App, Arg, ArgMatches}; use solana_faucet::faucet::FAUCET_PORT; -use solana_sdk::fee_calculator::FeeCalculator; +use solana_sdk::fee_calculator::FeeRateGovernor; use solana_sdk::signature::{read_keypair_file, Keypair}; use std::{net::SocketAddr, process::exit, time::Duration}; @@ -43,7 +43,7 @@ impl Default for Config { client_ids_and_stake_file: String::new(), write_to_client_file: false, read_from_client_file: false, - target_lamports_per_signature: FeeCalculator::default().target_lamports_per_signature, + target_lamports_per_signature: FeeRateGovernor::default().target_lamports_per_signature, multi_client: true, use_move: false, num_lamports_per_account: NUM_LAMPORTS_PER_ACCOUNT_DEFAULT, diff --git a/bench-tps/src/main.rs b/bench-tps/src/main.rs index 8544ab79bf..dfefa22847 100644 --- a/bench-tps/src/main.rs +++ b/bench-tps/src/main.rs @@ -3,7 +3,7 @@ use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate use solana_bench_tps::cli; use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client}; use solana_genesis::Base64Account; -use solana_sdk::fee_calculator::FeeCalculator; +use solana_sdk::fee_calculator::FeeRateGovernor; use solana_sdk::signature::{Keypair, Signer}; use solana_sdk::system_program; use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc}; @@ -41,7 +41,7 @@ fn main() { let (keypairs, _) = generate_keypairs(&id, keypair_count as u64); let num_accounts = keypairs.len() as u64; let max_fee = - FeeCalculator::new(*target_lamports_per_signature, 0).max_lamports_per_signature; + FeeRateGovernor::new(*target_lamports_per_signature, 0).max_lamports_per_signature; let num_lamports_per_account = (num_accounts - 1 + NUM_SIGNATURES_FOR_TXS * max_fee) / num_accounts + num_lamports_per_account; diff --git a/cli/src/offline.rs b/cli/src/offline.rs index 5aee7142bb..354dfe7847 100644 --- a/cli/src/offline.rs +++ b/cli/src/offline.rs @@ -201,7 +201,7 @@ mod tests { fn test_blockhashspec_get_blockhash_fee_calc() { let test_blockhash = hash(&[0u8]); let rpc_blockhash = hash(&[1u8]); - let rpc_fee_calc = FeeCalculator::new(42, 42); + let rpc_fee_calc = FeeCalculator::new(42); let get_recent_blockhash_response = json!(Response { context: RpcResponseContext { slot: 1 }, value: json!(( diff --git a/client/src/mock_rpc_client_request.rs b/client/src/mock_rpc_client_request.rs index 372c38dda4..2bfd405640 100644 --- a/client/src/mock_rpc_client_request.rs +++ b/client/src/mock_rpc_client_request.rs @@ -6,7 +6,7 @@ use crate::{ }; use serde_json::{Number, Value}; use solana_sdk::{ - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, instruction::InstructionError, transaction::{self, TransactionError}, }; @@ -71,6 +71,10 @@ impl GenericRpcClientRequest for MockRpcClientRequest { serde_json::to_value(FeeCalculator::default()).unwrap(), ), })?, + RpcRequest::GetFeeRateGovernor => serde_json::to_value(Response { + context: RpcResponseContext { slot: 1 }, + value: serde_json::to_value(FeeRateGovernor::default()).unwrap(), + })?, RpcRequest::GetSignatureStatus => { let response: Option> = if self.url == "account_in_use" { Some(Err(TransactionError::AccountInUse)) diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index a2954ff33c..c335796b87 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -6,8 +6,8 @@ use crate::{ rpc_request::RpcRequest, rpc_response::{ Response, RpcAccount, RpcBlockhashFeeCalculator, RpcConfirmedBlock, RpcContactInfo, - RpcEpochInfo, RpcKeyedAccount, RpcLeaderSchedule, RpcResponse, RpcVersionInfo, - RpcVoteAccountStatus, + RpcEpochInfo, RpcFeeRateGovernor, RpcKeyedAccount, RpcLeaderSchedule, RpcResponse, + RpcVersionInfo, RpcVoteAccountStatus, }, }; use bincode::serialize; @@ -18,7 +18,7 @@ use solana_sdk::{ clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT}, commitment_config::CommitmentConfig, epoch_schedule::EpochSchedule, - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, inflation::Inflation, pubkey::Pubkey, @@ -804,6 +804,31 @@ impl RpcClient { }) } + pub fn get_fee_rate_governor(&self) -> RpcResponse { + let response = self + .client + .send(&RpcRequest::GetFeeRateGovernor, Value::Null, 0) + .map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("GetFeeRateGovernor request failure: {:?}", e), + ) + })?; + let Response { + context, + value: RpcFeeRateGovernor { fee_rate_governor }, + } = serde_json::from_value::>(response).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("GetFeeRateGovernor parse failure: {:?}", e), + ) + })?; + Ok(Response { + context, + value: fee_rate_governor, + }) + } + pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)> { let mut num_retries = 0; let start = Instant::now(); diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index be0b539526..15fa38a0a1 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -20,6 +20,7 @@ pub enum RpcRequest { GetNumBlocksSinceSignatureConfirmation, GetProgramAccounts, GetRecentBlockhash, + GetFeeRateGovernor, GetSignatureStatus, GetSlot, GetSlotLeader, @@ -61,6 +62,7 @@ impl RpcRequest { } RpcRequest::GetProgramAccounts => "getProgramAccounts", RpcRequest::GetRecentBlockhash => "getRecentBlockhash", + RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor", RpcRequest::GetSignatureStatus => "getSignatureStatus", RpcRequest::GetSlot => "getSlot", RpcRequest::GetSlotLeader => "getSlotLeader", @@ -138,6 +140,10 @@ mod tests { let request = test_request.build_request_json(1, Value::Null); assert_eq!(request["method"], "getRecentBlockhash"); + let test_request = RpcRequest::GetFeeRateGovernor; + let request = test_request.build_request_json(1, Value::Null); + assert_eq!(request["method"], "getFeeRateGovernor"); + let test_request = RpcRequest::GetSlot; let request = test_request.build_request_json(1, Value::Null); assert_eq!(request["method"], "getSlot"); diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index c413a6c76d..0ca274bcf0 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -4,7 +4,7 @@ use jsonrpc_core::Result as JsonResult; use solana_sdk::{ account::Account, clock::{Epoch, Slot}, - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, message::MessageHeader, pubkey::Pubkey, transaction::{Result, Transaction}, @@ -152,6 +152,12 @@ pub struct RpcBlockhashFeeCalculator { pub fee_calculator: FeeCalculator, } +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct RpcFeeRateGovernor { + pub fee_rate_governor: FeeRateGovernor, +} + #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct RpcKeyedAccount { diff --git a/client/src/thin_client.rs b/client/src/thin_client.rs index ae5ba7534a..94195505f3 100644 --- a/client/src/thin_client.rs +++ b/client/src/thin_client.rs @@ -11,7 +11,7 @@ use solana_sdk::{ client::{AsyncClient, Client, SyncClient}, clock::MAX_PROCESSING_AGE, commitment_config::CommitmentConfig, - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::Message, @@ -445,6 +445,11 @@ impl SyncClient for ThinClient { } } + fn get_fee_rate_governor(&self) -> TransportResult { + let fee_rate_governor = self.rpc_client().get_fee_rate_governor()?; + Ok(fee_rate_governor.value) + } + fn get_signature_status( &self, signature: &Signature, diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 3be6d18a42..a031cc1358 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -9,9 +9,9 @@ use jsonrpc_core::{Error, Metadata, Result}; use jsonrpc_derive::rpc; use solana_client::rpc_response::{ Response, RpcAccount, RpcBlockCommitment, RpcBlockhashFeeCalculator, RpcConfirmedBlock, - RpcContactInfo, RpcEpochInfo, RpcKeyedAccount, RpcLeaderSchedule, RpcResponseContext, - RpcSignatureConfirmation, RpcStorageTurn, RpcTransactionEncoding, RpcVersionInfo, - RpcVoteAccountInfo, RpcVoteAccountStatus, + RpcContactInfo, RpcEpochInfo, RpcFeeRateGovernor, RpcKeyedAccount, RpcLeaderSchedule, + RpcResponseContext, RpcSignatureConfirmation, RpcStorageTurn, RpcTransactionEncoding, + RpcVersionInfo, RpcVoteAccountInfo, RpcVoteAccountStatus, }; use solana_faucet::faucet::request_airdrop_transaction; use solana_ledger::{ @@ -164,6 +164,17 @@ impl JsonRpcRequestProcessor { ) } + fn get_fee_rate_governor(&self) -> RpcResponse { + let bank = &*self.bank(None); + let fee_rate_governor = bank.get_fee_rate_governor(); + new_response( + bank, + RpcFeeRateGovernor { + fee_rate_governor: fee_rate_governor.clone(), + }, + ) + } + pub fn confirm_transaction( &self, signature: Result, @@ -491,6 +502,9 @@ pub trait RpcSol { commitment: Option, ) -> RpcResponse; + #[rpc(meta, name = "getFeeRateGovernor")] + fn get_fee_rate_governor(&self, meta: Self::Metadata) -> RpcResponse; + #[rpc(meta, name = "getSignatureStatus")] fn get_signature_status( &self, @@ -813,6 +827,14 @@ impl RpcSol for RpcSolImpl { .get_recent_blockhash(commitment) } + fn get_fee_rate_governor(&self, meta: Self::Metadata) -> RpcResponse { + debug!("get_fee_rate_governor rpc request received"); + meta.request_processor + .read() + .unwrap() + .get_fee_rate_governor() + } + fn get_signature_status( &self, meta: Self::Metadata, @@ -1770,8 +1792,32 @@ pub mod tests { "value":{ "blockhash": blockhash.to_string(), "feeCalculator": { - "burnPercent": DEFAULT_BURN_PERCENT, "lamportsPerSignature": 0, + } + }}, + "id": 1 + }); + let expected: Response = + serde_json::from_value(expected).expect("expected response deserialization"); + let result: Response = serde_json::from_str(&res.expect("actual response")) + .expect("actual response deserialization"); + assert_eq!(expected, result); + } + + #[test] + fn test_rpc_get_fee_rate_governor() { + let bob_pubkey = Pubkey::new_rand(); + let RpcHandler { io, meta, .. } = start_rpc_handler_with_tx(&bob_pubkey); + + let req = format!(r#"{{"jsonrpc":"2.0","id":1,"method":"getFeeRateGovernor"}}"#); + let res = io.handle_request_sync(&req, meta); + let expected = json!({ + "jsonrpc": "2.0", + "result": { + "context":{"slot":0}, + "value":{ + "feeRateGovernor": { + "burnPercent": DEFAULT_BURN_PERCENT, "maxLamportsPerSignature": 0, "minLamportsPerSignature": 0, "targetLamportsPerSignature": 0, diff --git a/core/src/validator.rs b/core/src/validator.rs index f2656ab1d0..880874932c 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -680,7 +680,7 @@ impl TestValidator { pub fn run_with_options(options: TestValidatorOptions) -> Self { use crate::genesis_utils::{create_genesis_config_with_leader_ex, GenesisConfigInfo}; - use solana_sdk::fee_calculator::FeeCalculator; + use solana_sdk::fee_calculator::FeeRateGovernor; let TestValidatorOptions { fees, @@ -706,7 +706,7 @@ impl TestValidator { genesis_config.rent.lamports_per_byte_year = 1; genesis_config.rent.exemption_threshold = 1.0; - genesis_config.fee_calculator = FeeCalculator::new(fees, 0); + genesis_config.fee_rate_governor = FeeRateGovernor::new(fees, 0); let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_config); diff --git a/docs/src/apps/jsonrpc-api.md b/docs/src/apps/jsonrpc-api.md index dffb9b3138..7a3d035d06 100644 --- a/docs/src/apps/jsonrpc-api.md +++ b/docs/src/apps/jsonrpc-api.md @@ -24,6 +24,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana- * [getConfirmedBlocks](jsonrpc-api.md#getconfirmedblocks) * [getEpochInfo](jsonrpc-api.md#getepochinfo) * [getEpochSchedule](jsonrpc-api.md#getepochschedule) +* [getFeeRateGovernor](jsonrpc-api.md#getfeerategovernor) * [getGenesisHash](jsonrpc-api.md#getgenesishash) * [getInflation](jsonrpc-api.md#getinflation) * [getLeaderSchedule](jsonrpc-api.md#getleaderschedule) @@ -403,6 +404,34 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m {"jsonrpc":"2.0","result":{"firstNormalEpoch":8,"firstNormalSlot":8160,"leaderScheduleSlotOffset":8192,"slotsPerEpoch":8192,"warmup":true},"id":1} ``` +### getFeeRateGovernor + +Returns the fee rate governor information from the root bank + +#### Parameters: + +None + +#### Results: + +The `result` field will be an `object` with the following fields: + +* `burnPercent: `, Percentage of fees collected to be destroyed +* `maxLamportsPerSignature: `, Largest value `lamportsPerSignature` can attain for the next slot +* `minLamportsPerSignature: `, Smallest value `lamportsPerSignature` can attain for the next slot +* `targetLamportsPerSignature: `, Desired fee rate for the cluster +* `targetSignaturesPerSlot: `, Desired signature rate for the cluster + +#### Example: + +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getFeeRateGovernor"}' http://localhost:8899 + +// Result +{"jsonrpc":"2.0","result":{"context":{"slot":54},"value":{"feeRateGovernor":{"burnPercent":50,"maxLamportsPerSignature":100000,"minLamportsPerSignature":5000,"targetLamportsPerSignature":10000,"targetSignaturesPerSlot":20000}}},"id":1} +``` + ### getGenesisHash Returns the genesis hash diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 4c7b4993eb..523a90a882 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -11,7 +11,7 @@ use solana_sdk::{ account::Account, clock, epoch_schedule::EpochSchedule, - fee_calculator::FeeCalculator, + fee_calculator::FeeRateGovernor, genesis_config::{GenesisConfig, OperatingMode}, native_token::sol_to_lamports, poh_config::PohConfig, @@ -99,16 +99,16 @@ pub fn load_genesis_accounts(file: &str, genesis_config: &mut GenesisConfig) -> #[allow(clippy::cognitive_complexity)] fn main() -> Result<(), Box> { - let fee_calculator = FeeCalculator::default(); + let fee_rate_governor = FeeRateGovernor::default(); let ( default_target_lamports_per_signature, default_target_signatures_per_slot, default_fee_burn_percentage, ) = { ( - &fee_calculator.target_lamports_per_signature.to_string(), - &fee_calculator.target_signatures_per_slot.to_string(), - &fee_calculator.burn_percent.to_string(), + &fee_rate_governor.target_lamports_per_signature.to_string(), + &fee_rate_governor.target_signatures_per_slot.to_string(), + &fee_rate_governor.burn_percent.to_string(), ) }; @@ -449,11 +449,11 @@ fn main() -> Result<(), Box> { let ticks_per_slot = value_t_or_exit!(matches, "ticks_per_slot", u64); - let mut fee_calculator = FeeCalculator::new( + let mut fee_rate_governor = FeeRateGovernor::new( value_t_or_exit!(matches, "target_lamports_per_signature", u64), - value_t_or_exit!(matches, "target_signatures_per_slot", usize), + value_t_or_exit!(matches, "target_signatures_per_slot", u64), ); - fee_calculator.burn_percent = value_t_or_exit!(matches, "fee_burn_percentage", u8); + fee_rate_governor.burn_percent = value_t_or_exit!(matches, "fee_burn_percentage", u8); let mut poh_config = PohConfig::default(); poh_config.target_tick_duration = if matches.is_present("target_tick_duration") { @@ -513,7 +513,7 @@ fn main() -> Result<(), Box> { ticks_per_slot, epoch_schedule, inflation, - fee_calculator, + fee_rate_governor, rent, poh_config, operating_mode, diff --git a/genesis/src/stakes.rs b/genesis/src/stakes.rs index 5f81544f03..db9104b7eb 100644 --- a/genesis/src/stakes.rs +++ b/genesis/src/stakes.rs @@ -25,7 +25,7 @@ pub struct StakerInfo { // lamports to cover TX fees (delegation) for one year, // and we support one delegation per epoch fn calculate_staker_fees(genesis_config: &GenesisConfig, years: f64) -> u64 { - genesis_config.fee_calculator.max_lamports_per_signature + genesis_config.fee_rate_governor.max_lamports_per_signature * genesis_config.epoch_schedule.get_epoch(years_as_slots( years, &genesis_config.poh_config.target_tick_duration, diff --git a/programs/bpf/rust/sysval/src/lib.rs b/programs/bpf/rust/sysval/src/lib.rs index ac5bd54052..ff68371f09 100644 --- a/programs/bpf/rust/sysval/src/lib.rs +++ b/programs/bpf/rust/sysval/src/lib.rs @@ -36,8 +36,8 @@ fn process_instruction( info!("Fees identifier:"); sysvar::fees::id().log(); let fees = Fees::from_account_info(&accounts[3]).expect("fees"); - let burn = fees.fee_calculator.burn(42); - assert_eq!(burn, (21, 21)); + let fee_calculator = fees.fee_calculator; + assert_eq!(fee_calculator.lamports_per_signature, 0); // Rewards info!("Rewards identifier:"); diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 709b06e12d..381956c7a9 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -853,7 +853,7 @@ mod tests { instructions, ); - let fee_calculator = FeeCalculator::new(10, 0); + let fee_calculator = FeeCalculator::new(10); assert_eq!(fee_calculator.calculate_fee(tx.message()), 10); let loaded_accounts = @@ -918,7 +918,7 @@ mod tests { let min_balance = rent_collector .rent .minimum_balance(nonce_state::NonceState::size()); - let fee_calculator = FeeCalculator::new(min_balance, 0); + let fee_calculator = FeeCalculator::new(min_balance); let nonce = Keypair::new(); let mut accounts = vec![( nonce.pubkey(), diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index d12a5cbd68..d9a3bc9262 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -35,7 +35,7 @@ use solana_sdk::{ account::Account, clock::{get_segment_from_slot, Epoch, Slot, UnixTimestamp, MAX_RECENT_BLOCKHASHES}, epoch_schedule::EpochSchedule, - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, genesis_config::GenesisConfig, hard_forks::HardForks, hash::{extend_and_hash, hashv, Hash}, @@ -302,6 +302,9 @@ pub struct Bank { /// Latest transaction fees for transactions processed by this bank fee_calculator: FeeCalculator, + /// Track cluster signature throughput and adjust fee rate + fee_rate_governor: FeeRateGovernor, + /// Rent that have been collected #[serde(serialize_with = "serialize_atomicu64")] #[serde(deserialize_with = "deserialize_atomicu64")] @@ -401,6 +404,9 @@ impl Bank { let epoch_schedule = parent.epoch_schedule; let epoch = epoch_schedule.get_epoch(slot); + let fee_rate_governor = + FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count()); + let mut new = Bank { rc, src, @@ -420,10 +426,8 @@ impl Bank { rent_collector: parent.rent_collector.clone_with_epoch(epoch), max_tick_height: (slot + 1) * parent.ticks_per_slot, block_height: parent.block_height + 1, - fee_calculator: FeeCalculator::new_derived( - &parent.fee_calculator, - parent.signature_count() as usize, - ), + fee_calculator: fee_rate_governor.create_fee_calculator(), + fee_rate_governor, capitalization: AtomicU64::new(parent.capitalization()), inflation: parent.inflation.clone(), transaction_count: AtomicU64::new(parent.transaction_count()), @@ -745,7 +749,7 @@ impl Bank { let collector_fees = self.collector_fees.load(Ordering::Relaxed) as u64; if collector_fees != 0 { - let (unburned, burned) = self.fee_calculator.burn(collector_fees); + let (unburned, burned) = self.fee_rate_governor.burn(collector_fees); // burn a portion of fees self.deposit(&self.collector_id, unburned); self.capitalization.fetch_sub(burned, Ordering::Relaxed); @@ -811,7 +815,8 @@ impl Bank { fn process_genesis_config(&mut self, genesis_config: &GenesisConfig) { // Bootstrap validator collects fees until `new_from_parent` is called. - self.fee_calculator = genesis_config.fee_calculator.clone(); + self.fee_rate_governor = genesis_config.fee_rate_governor.clone(); + self.fee_calculator = self.fee_rate_governor.create_fee_calculator(); self.update_fees(); for (pubkey, account) in genesis_config.accounts.iter() { @@ -905,6 +910,10 @@ impl Bank { blockhash_queue.get_fee_calculator(hash).cloned() } + pub fn get_fee_rate_governor(&self) -> &FeeRateGovernor { + &self.fee_rate_governor + } + pub fn confirmed_last_blockhash(&self) -> (Hash, FeeCalculator) { const NUM_BLOCKHASH_CONFIRMATIONS: usize = 3; @@ -3290,7 +3299,7 @@ mod tests { #[test] fn test_detect_failed_duplicate_transactions() { let (mut genesis_config, mint_keypair) = create_genesis_config(2); - genesis_config.fee_calculator.lamports_per_signature = 1; + genesis_config.fee_rate_governor = FeeRateGovernor::new(1, 0); let bank = Bank::new(&genesis_config); let dest = Keypair::new(); @@ -3450,11 +3459,14 @@ mod tests { mint_keypair, .. } = create_genesis_config_with_leader(mint, &leader, 3); - genesis_config.fee_calculator.lamports_per_signature = 4; // something divisible by 2 + genesis_config.fee_rate_governor = FeeRateGovernor::new(4, 0); // something divisible by 2 - let expected_fee_paid = genesis_config.fee_calculator.lamports_per_signature; + let expected_fee_paid = genesis_config + .fee_rate_governor + .create_fee_calculator() + .lamports_per_signature; let (expected_fee_collected, expected_fee_burned) = - genesis_config.fee_calculator.burn(expected_fee_paid); + genesis_config.fee_rate_governor.burn(expected_fee_paid); let mut bank = Bank::new(&genesis_config); @@ -3521,8 +3533,10 @@ mod tests { mint_keypair, .. } = create_genesis_config_with_leader(1_000_000, &leader, 3); - genesis_config.fee_calculator.target_lamports_per_signature = 1000; - genesis_config.fee_calculator.target_signatures_per_slot = 1; + genesis_config + .fee_rate_governor + .target_lamports_per_signature = 1000; + genesis_config.fee_rate_governor.target_signatures_per_slot = 1; let mut bank = Bank::new(&genesis_config); goto_end_of_slot(&mut bank); @@ -3571,7 +3585,7 @@ mod tests { mint_keypair, .. } = create_genesis_config_with_leader(100, &leader, 3); - genesis_config.fee_calculator.lamports_per_signature = 2; + genesis_config.fee_rate_governor = FeeRateGovernor::new(2, 0); let bank = Bank::new(&genesis_config); let key = Keypair::new(); @@ -3598,7 +3612,7 @@ mod tests { bank.get_balance(&leader), initial_balance + bank - .fee_calculator + .fee_rate_governor .burn(bank.fee_calculator.lamports_per_signature * 2) .0 ); @@ -4548,15 +4562,20 @@ mod tests { } #[test] - fn test_bank_inherit_fee_calculator() { + fn test_bank_inherit_fee_rate_governor() { let (mut genesis_config, _mint_keypair) = create_genesis_config(500); - genesis_config.fee_calculator.target_lamports_per_signature = 123; + genesis_config + .fee_rate_governor + .target_lamports_per_signature = 123; let bank0 = Arc::new(Bank::new(&genesis_config)); let bank1 = Arc::new(new_from_parent(&bank0)); assert_eq!( - bank0.fee_calculator.target_lamports_per_signature / 2, - bank1.fee_calculator.lamports_per_signature + bank0.fee_rate_governor.target_lamports_per_signature / 2, + bank1 + .fee_rate_governor + .create_fee_calculator() + .lamports_per_signature ); } @@ -4659,7 +4678,7 @@ mod tests { #[test] fn test_bank_fees_account() { let (mut genesis_config, _) = create_genesis_config(500); - genesis_config.fee_calculator.lamports_per_signature = 12345; + genesis_config.fee_rate_governor = FeeRateGovernor::new(12345, 0); let bank = Arc::new(Bank::new(&genesis_config)); let fees_account = bank.get_account(&sysvar::fees::id()).unwrap(); @@ -5443,8 +5462,8 @@ mod tests { #[test] fn test_pre_post_transaction_balances() { let (mut genesis_config, _mint_keypair) = create_genesis_config(500); - let fee_calculator = FeeCalculator::new(1, 0); - genesis_config.fee_calculator = fee_calculator; + let fee_rate_governor = FeeRateGovernor::new(1, 0); + genesis_config.fee_rate_governor = fee_rate_governor; let parent = Arc::new(Bank::new(&genesis_config)); let bank0 = Arc::new(new_from_parent(&parent)); diff --git a/runtime/src/bank_client.rs b/runtime/src/bank_client.rs index 1ac3a68ad2..51f57cc81f 100644 --- a/runtime/src/bank_client.rs +++ b/runtime/src/bank_client.rs @@ -3,7 +3,7 @@ use solana_sdk::{ account::Account, client::{AsyncClient, Client, SyncClient}, commitment_config::CommitmentConfig, - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::Message, @@ -137,6 +137,10 @@ impl SyncClient for BankClient { Ok(self.bank.last_blockhash_with_fee_calculator()) } + fn get_fee_rate_governor(&self) -> Result { + Ok(self.bank.get_fee_rate_governor().clone()) + } + fn get_signature_status( &self, signature: &Signature, diff --git a/runtime/src/genesis_utils.rs b/runtime/src/genesis_utils.rs index 11bfd33412..fdd7e183c6 100644 --- a/runtime/src/genesis_utils.rs +++ b/runtime/src/genesis_utils.rs @@ -1,6 +1,6 @@ use solana_sdk::{ account::Account, - fee_calculator::FeeCalculator, + fee_calculator::FeeRateGovernor, genesis_config::GenesisConfig, pubkey::Pubkey, rent::Rent, @@ -140,11 +140,11 @@ pub fn create_genesis_config_with_leader_ex( solana_stake_program!(), ]; - let fee_calculator = FeeCalculator::new(0, 0); // most tests can't handle transaction fees + let fee_rate_governor = FeeRateGovernor::new(0, 0); // most tests can't handle transaction fees let mut genesis_config = GenesisConfig { accounts, native_instruction_processors, - fee_calculator, + fee_rate_governor, rent, ..GenesisConfig::default() }; diff --git a/sdk/src/client.rs b/sdk/src/client.rs index 32f837b78e..48f0bcf3b8 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -11,7 +11,7 @@ use crate::{ account::Account, clock::Slot, commitment_config::CommitmentConfig, - fee_calculator::FeeCalculator, + fee_calculator::{FeeCalculator, FeeRateGovernor}, hash::Hash, instruction::Instruction, message::Message, @@ -72,6 +72,9 @@ pub trait SyncClient { commitment_config: CommitmentConfig, ) -> Result<(Hash, FeeCalculator)>; + /// Get recent fee rate governor + fn get_fee_rate_governor(&self) -> Result; + /// Get signature status. fn get_signature_status( &self, diff --git a/sdk/src/fee_calculator.rs b/sdk/src/fee_calculator.rs index 9c13d2fdad..03485117e6 100644 --- a/sdk/src/fee_calculator.rs +++ b/sdk/src/fee_calculator.rs @@ -8,6 +8,35 @@ pub struct FeeCalculator { // The current cost of a signature This amount may increase/decrease over time based on // cluster processing load. pub lamports_per_signature: u64, +} + +impl Default for FeeCalculator { + fn default() -> Self { + Self { + lamports_per_signature: 0, + } + } +} + +impl FeeCalculator { + pub fn new(lamports_per_signature: u64) -> Self { + Self { + lamports_per_signature, + } + } + + pub fn calculate_fee(&self, message: &Message) -> u64 { + self.lamports_per_signature * u64::from(message.header.num_required_signatures) + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct FeeRateGovernor { + // The current cost of a signature This amount may increase/decrease over time based on + // cluster processing load. + #[serde(skip)] + lamports_per_signature: u64, // The target cost of a signature when the cluster is operating around target_signatures_per_slot // signatures @@ -16,7 +45,7 @@ pub struct FeeCalculator { // Used to estimate the desired processing capacity of the cluster. As the signatures for // recent slots are fewer/greater than this value, lamports_per_signature will decrease/increase // for the next slot. A value of 0 disables lamports_per_signature fee adjustments - pub target_signatures_per_slot: usize, + pub target_signatures_per_slot: u64, pub min_lamports_per_signature: u64, pub max_lamports_per_signature: u64, @@ -26,15 +55,15 @@ pub struct FeeCalculator { } pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 10_000; -pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: usize = - 50_000 * DEFAULT_TICKS_PER_SLOT as usize / DEFAULT_TICKS_PER_SECOND as usize; +pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = + 50_000 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND; // Percentage of tx fees to burn pub const DEFAULT_BURN_PERCENT: u8 = 50; -impl Default for FeeCalculator { +impl Default for FeeRateGovernor { fn default() -> Self { - FeeCalculator { + Self { lamports_per_signature: 0, target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE, target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT, @@ -45,23 +74,23 @@ impl Default for FeeCalculator { } } -impl FeeCalculator { - pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: usize) -> Self { - let base_fee_calculator = Self { +impl FeeRateGovernor { + pub fn new(target_lamports_per_signature: u64, target_signatures_per_slot: u64) -> Self { + let base_fee_rate_governor = Self { target_lamports_per_signature, lamports_per_signature: target_lamports_per_signature, target_signatures_per_slot, - ..FeeCalculator::default() + ..FeeRateGovernor::default() }; - Self::new_derived(&base_fee_calculator, 0) + Self::new_derived(&base_fee_rate_governor, 0) } pub fn new_derived( - base_fee_calculator: &FeeCalculator, - latest_signatures_per_slot: usize, + base_fee_rate_governor: &FeeRateGovernor, + latest_signatures_per_slot: u64, ) -> Self { - let mut me = base_fee_calculator.clone(); + let mut me = base_fee_rate_governor.clone(); if me.target_signatures_per_slot > 0 { // lamports_per_signature can range from 50% to 1000% of @@ -74,7 +103,7 @@ impl FeeCalculator { me.max_lamports_per_signature .min(me.min_lamports_per_signature.max( me.target_lamports_per_signature - * std::cmp::min(latest_signatures_per_slot, std::u32::MAX as usize) + * std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64) as u64 / me.target_signatures_per_slot as u64, )); @@ -85,7 +114,7 @@ impl FeeCalculator { ); let gap = desired_lamports_per_signature as i64 - - base_fee_calculator.lamports_per_signature as i64; + - base_fee_rate_governor.lamports_per_signature as i64; if gap == 0 { me.lamports_per_signature = desired_lamports_per_signature; @@ -104,11 +133,12 @@ impl FeeCalculator { me.lamports_per_signature = me.max_lamports_per_signature .min(me.min_lamports_per_signature.max( - (base_fee_calculator.lamports_per_signature as i64 + gap_adjust) as u64, + (base_fee_rate_governor.lamports_per_signature as i64 + gap_adjust) + as u64, )); } } else { - me.lamports_per_signature = base_fee_calculator.target_lamports_per_signature; + me.lamports_per_signature = base_fee_rate_governor.target_lamports_per_signature; me.min_lamports_per_signature = me.target_lamports_per_signature; me.max_lamports_per_signature = me.target_lamports_per_signature; } @@ -119,15 +149,18 @@ impl FeeCalculator { me } - pub fn calculate_fee(&self, message: &Message) -> u64 { - self.lamports_per_signature * u64::from(message.header.num_required_signatures) - } - /// calculate unburned fee from a fee total, returns (unburned, burned) pub fn burn(&self, fees: u64) -> (u64, u64) { let burned = fees * u64::from(self.burn_percent) / 100; (fees - burned, burned) } + + /// create a FeeCalculator based on current cluster signature throughput + pub fn create_fee_calculator(&self) -> FeeCalculator { + FeeCalculator { + lamports_per_signature: self.lamports_per_signature, + } + } } #[cfg(test)] @@ -136,15 +169,15 @@ mod tests { use crate::{pubkey::Pubkey, system_instruction}; #[test] - fn test_fee_calculator_burn() { - let mut fee_calculator = FeeCalculator::default(); - assert_eq!(fee_calculator.burn(2), (1, 1)); + fn test_fee_rate_governor_burn() { + let mut fee_rate_governor = FeeRateGovernor::default(); + assert_eq!(fee_rate_governor.burn(2), (1, 1)); - fee_calculator.burn_percent = 0; - assert_eq!(fee_calculator.burn(2), (2, 0)); + fee_rate_governor.burn_percent = 0; + assert_eq!(fee_rate_governor.burn(2), (2, 0)); - fee_calculator.burn_percent = 100; - assert_eq!(fee_calculator.burn(2), (0, 2)); + fee_rate_governor.burn_percent = 100; + assert_eq!(fee_rate_governor.burn(2), (0, 2)); } #[test] @@ -154,27 +187,27 @@ mod tests { assert_eq!(FeeCalculator::default().calculate_fee(&message), 0); // No signature, no fee. - assert_eq!(FeeCalculator::new(1, 0).calculate_fee(&message), 0); + assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0); // One signature, a fee. let pubkey0 = Pubkey::new(&[0; 32]); let pubkey1 = Pubkey::new(&[1; 32]); let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); let message = Message::new(vec![ix0]); - assert_eq!(FeeCalculator::new(2, 0).calculate_fee(&message), 2); + assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2); // Two signatures, double the fee. let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1); let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1); let message = Message::new(vec![ix0, ix1]); - assert_eq!(FeeCalculator::new(2, 0).calculate_fee(&message), 4); + assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4); } #[test] - fn test_fee_calculator_derived_default() { + fn test_fee_rate_governor_derived_default() { solana_logger::setup(); - let f0 = FeeCalculator::default(); + let f0 = FeeRateGovernor::default(); assert_eq!( f0.target_signatures_per_slot, DEFAULT_TARGET_SIGNATURES_PER_SLOT @@ -185,7 +218,7 @@ mod tests { ); assert_eq!(f0.lamports_per_signature, 0); - let f1 = FeeCalculator::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT); + let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT); assert_eq!( f1.target_signatures_per_slot, DEFAULT_TARGET_SIGNATURES_PER_SLOT @@ -201,20 +234,20 @@ mod tests { } #[test] - fn test_fee_calculator_derived_adjust() { + fn test_fee_rate_governor_derived_adjust() { solana_logger::setup(); - let mut f = FeeCalculator::default(); + let mut f = FeeRateGovernor::default(); f.target_lamports_per_signature = 100; f.target_signatures_per_slot = 100; - f = FeeCalculator::new_derived(&f, 0); + f = FeeRateGovernor::new_derived(&f, 0); // Ramp fees up let mut count = 0; loop { let last_lamports_per_signature = f.lamports_per_signature; - f = FeeCalculator::new_derived(&f, std::usize::MAX); + f = FeeRateGovernor::new_derived(&f, std::u64::MAX); info!("[up] f.lamports_per_signature={}", f.lamports_per_signature); // some maximum target reached @@ -230,7 +263,7 @@ mod tests { let mut count = 0; loop { let last_lamports_per_signature = f.lamports_per_signature; - f = FeeCalculator::new_derived(&f, 0); + f = FeeRateGovernor::new_derived(&f, 0); info!( "[down] f.lamports_per_signature={}", @@ -250,7 +283,7 @@ mod tests { // Arrive at target rate let mut count = 0; while f.lamports_per_signature != f.target_lamports_per_signature { - f = FeeCalculator::new_derived(&f, f.target_signatures_per_slot); + f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot); info!( "[target] f.lamports_per_signature={}", f.lamports_per_signature diff --git a/sdk/src/genesis_config.rs b/sdk/src/genesis_config.rs index b465213cce..b90e3e60d3 100644 --- a/sdk/src/genesis_config.rs +++ b/sdk/src/genesis_config.rs @@ -4,7 +4,7 @@ use crate::{ account::Account, clock::{UnixTimestamp, DEFAULT_SLOTS_PER_SEGMENT, DEFAULT_TICKS_PER_SLOT}, epoch_schedule::EpochSchedule, - fee_calculator::FeeCalculator, + fee_calculator::FeeRateGovernor, hash::{hash, Hash}, inflation::Inflation, native_token::lamports_to_sol, @@ -49,7 +49,7 @@ pub struct GenesisConfig { /// network speed configuration pub poh_config: PohConfig, /// transaction fee config - pub fee_calculator: FeeCalculator, + pub fee_rate_governor: FeeRateGovernor, /// rent config pub rent: Rent, /// inflation config @@ -89,7 +89,7 @@ impl Default for GenesisConfig { slots_per_segment: DEFAULT_SLOTS_PER_SEGMENT, poh_config: PohConfig::default(), inflation: Inflation::default(), - fee_calculator: FeeCalculator::default(), + fee_rate_governor: FeeRateGovernor::default(), rent: Rent::default(), epoch_schedule: EpochSchedule::default(), operating_mode: OperatingMode::Development, @@ -182,17 +182,17 @@ impl fmt::Display for GenesisConfig { write!( f, "\ - Creation time: {}\n\ - Operating mode: {:?}\n\ - Genesis hash: {}\n\ - Shred version: {}\n\ - Hashes per tick: {:?}\n\ - Slots per epoch: {}\n\ - Warmup epochs: {}abled\n\ - {:?}\n\ - {:?}\n\ - Capitalization: {} SOL in {} accounts\n\ - ", + Creation time: {}\n\ + Operating mode: {:?}\n\ + Genesis hash: {}\n\ + Shred version: {}\n\ + Hashes per tick: {:?}\n\ + Slots per epoch: {}\n\ + Warmup epochs: {}abled\n\ + {:?}\n\ + {:?}\n\ + Capitalization: {} SOL in {} accounts\n\ + ", Utc.timestamp(self.creation_time, 0).to_rfc3339(), self.operating_mode, self.hash(), @@ -205,7 +205,7 @@ impl fmt::Display for GenesisConfig { "dis" }, self.rent, - self.fee_calculator, + self.fee_rate_governor, lamports_to_sol( self.accounts .iter()