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
This commit is contained in:
@ -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<FeeRateGovernor>;
|
||||
|
||||
/// Get signature status.
|
||||
fn get_signature_status(
|
||||
&self,
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
Reference in New Issue
Block a user