add fee burning (#4818)
This commit is contained in:
parent
eb47538a82
commit
8a64e1ddc3
|
@ -806,6 +806,7 @@ mod tests {
|
|||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"result": [ blockhash.to_string(), {
|
||||
"burnPercent": 50,
|
||||
"lamportsPerSignature": 0,
|
||||
"maxLamportsPerSignature": 0,
|
||||
"minLamportsPerSignature": 0,
|
||||
|
|
|
@ -316,7 +316,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||
value_t_or_exit!(matches, "target_lamports_per_signature", u64);
|
||||
fee_calculator.target_signatures_per_slot =
|
||||
value_t_or_exit!(matches, "target_signatures_per_slot", usize);
|
||||
builder = builder.fee_calculator(&FeeCalculator::new_derived(&fee_calculator, 0));
|
||||
builder = builder.fee_calculator(FeeCalculator::new_derived(&fee_calculator, 0));
|
||||
|
||||
let mut poh_config = PohConfig::default();
|
||||
poh_config.target_tick_duration =
|
||||
|
@ -345,7 +345,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
|||
poh_config.hashes_per_tick = Some(value_t_or_exit!(matches, "hashes_per_tick", u64));
|
||||
}
|
||||
}
|
||||
builder = builder.poh_config(&poh_config);
|
||||
builder = builder.poh_config(poh_config);
|
||||
|
||||
if let Some(file) = matches.value_of("primordial_accounts_file") {
|
||||
builder = append_primordial_accounts(file, AccountFileFormat::Pubkey, builder)?;
|
||||
|
|
Binary file not shown.
|
@ -487,8 +487,10 @@ impl Bank {
|
|||
let mut hash = self.hash.write().unwrap();
|
||||
if *hash == Hash::default() {
|
||||
let collector_fees = self.collector_fees.load(Ordering::Relaxed) as u64;
|
||||
|
||||
if collector_fees != 0 {
|
||||
self.deposit(&self.collector_id, collector_fees);
|
||||
// burn a portion of fees
|
||||
self.deposit(&self.collector_id, self.fee_calculator.burn(collector_fees));
|
||||
}
|
||||
// freeze is a one-way trip, idempotent
|
||||
*hash = self.hash_internal_state();
|
||||
|
@ -1723,28 +1725,44 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_bank_tx_fee() {
|
||||
let arbitrary_transfer_amount = 42;
|
||||
let mint = arbitrary_transfer_amount * 100;
|
||||
let leader = Pubkey::new_rand();
|
||||
let GenesisBlockInfo {
|
||||
mut genesis_block,
|
||||
mint_keypair,
|
||||
..
|
||||
} = create_genesis_block_with_leader(100, &leader, 3);
|
||||
genesis_block.fee_calculator.lamports_per_signature = 3;
|
||||
} = create_genesis_block_with_leader(mint, &leader, 3);
|
||||
genesis_block.fee_calculator.lamports_per_signature = 4; // something divisible by 2
|
||||
|
||||
let expected_fee_paid = genesis_block.fee_calculator.lamports_per_signature;
|
||||
let expected_fee_collected = genesis_block.fee_calculator.burn(expected_fee_paid);
|
||||
|
||||
let mut bank = Bank::new(&genesis_block);
|
||||
|
||||
let key = Keypair::new();
|
||||
let tx =
|
||||
system_transaction::transfer(&mint_keypair, &key.pubkey(), 2, bank.last_blockhash());
|
||||
let tx = system_transaction::transfer(
|
||||
&mint_keypair,
|
||||
&key.pubkey(),
|
||||
arbitrary_transfer_amount,
|
||||
bank.last_blockhash(),
|
||||
);
|
||||
|
||||
let initial_balance = bank.get_balance(&leader);
|
||||
assert_eq!(bank.process_transaction(&tx), Ok(()));
|
||||
assert_eq!(bank.get_balance(&key.pubkey()), 2);
|
||||
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 100 - 5);
|
||||
assert_eq!(bank.get_balance(&key.pubkey()), arbitrary_transfer_amount);
|
||||
assert_eq!(
|
||||
bank.get_balance(&mint_keypair.pubkey()),
|
||||
mint - arbitrary_transfer_amount - expected_fee_paid
|
||||
);
|
||||
|
||||
assert_eq!(bank.get_balance(&leader), initial_balance);
|
||||
goto_end_of_slot(&mut bank);
|
||||
assert_eq!(bank.signature_count(), 1);
|
||||
assert_eq!(bank.get_balance(&leader), initial_balance + 3); // Leader collects fee after the bank is frozen
|
||||
assert_eq!(
|
||||
bank.get_balance(&leader),
|
||||
initial_balance + expected_fee_collected
|
||||
); // Leader collects fee after the bank is frozen
|
||||
|
||||
// Verify that an InstructionError collects fees, too
|
||||
let mut bank = Bank::new_from_parent(&Arc::new(bank), &leader, 1);
|
||||
|
@ -1755,13 +1773,19 @@ mod tests {
|
|||
|
||||
bank.process_transaction(&tx)
|
||||
.expect_err("instruction error");
|
||||
assert_eq!(bank.get_balance(&key.pubkey()), 2); // no change
|
||||
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 100 - 5 - 3); // mint_keypair still pays a fee
|
||||
assert_eq!(bank.get_balance(&key.pubkey()), arbitrary_transfer_amount); // no change
|
||||
assert_eq!(
|
||||
bank.get_balance(&mint_keypair.pubkey()),
|
||||
mint - arbitrary_transfer_amount - 2 * expected_fee_paid
|
||||
); // mint_keypair still pays a fee
|
||||
goto_end_of_slot(&mut bank);
|
||||
assert_eq!(bank.signature_count(), 1);
|
||||
|
||||
// Profit! 2 transaction signatures processed at 3 lamports each
|
||||
assert_eq!(bank.get_balance(&leader), initial_balance + 6);
|
||||
assert_eq!(
|
||||
bank.get_balance(&leader),
|
||||
initial_balance + 2 * expected_fee_collected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1840,11 +1864,17 @@ mod tests {
|
|||
InstructionError::new_result_with_negative_lamports(),
|
||||
)),
|
||||
];
|
||||
|
||||
let initial_balance = bank.get_balance(&leader);
|
||||
|
||||
let results = bank.filter_program_errors_and_collect_fee(&vec![tx1, tx2], &results);
|
||||
bank.freeze();
|
||||
assert_eq!(bank.get_balance(&leader), initial_balance + 2 + 2);
|
||||
assert_eq!(
|
||||
bank.get_balance(&leader),
|
||||
initial_balance
|
||||
+ bank
|
||||
.fee_calculator
|
||||
.burn(bank.fee_calculator.lamports_per_signature * 2)
|
||||
);
|
||||
assert_eq!(results[0], Ok(()));
|
||||
assert_eq!(results[1], Ok(()));
|
||||
}
|
||||
|
@ -2436,11 +2466,11 @@ mod tests {
|
|||
fn test_bank_inherit_fee_calculator() {
|
||||
let (mut genesis_block, _mint_keypair) = create_genesis_block(500);
|
||||
genesis_block.fee_calculator.target_lamports_per_signature = 123;
|
||||
assert_eq!(genesis_block.fee_calculator.target_signatures_per_slot, 0);
|
||||
|
||||
let bank0 = Arc::new(Bank::new(&genesis_block));
|
||||
let bank1 = Arc::new(new_from_parent(&bank0));
|
||||
assert_eq!(
|
||||
bank0.fee_calculator.target_lamports_per_signature,
|
||||
bank0.fee_calculator.target_lamports_per_signature / 2,
|
||||
bank1.fee_calculator.lamports_per_signature
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use solana_sdk::account::Account;
|
||||
use solana_sdk::genesis_block::{Builder, GenesisBlock};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_program;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
fee_calculator::FeeCalculator,
|
||||
genesis_block::{Builder, GenesisBlock},
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, KeypairUtil},
|
||||
system_program,
|
||||
};
|
||||
use solana_stake_api;
|
||||
use solana_vote_api::vote_state;
|
||||
|
||||
|
@ -67,7 +70,8 @@ pub fn create_genesis_block_with_leader(
|
|||
solana_bpf_loader_program!(),
|
||||
solana_vote_program!(),
|
||||
solana_stake_program!(),
|
||||
]);
|
||||
])
|
||||
.fee_calculator(FeeCalculator::new(0)); // most tests don't want fees
|
||||
|
||||
builder = solana_stake_api::rewards_pools::genesis(builder);
|
||||
builder = solana_storage_api::rewards_pools::genesis(builder);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::message::Message;
|
||||
use crate::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
|
||||
use log::*;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, Default)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FeeCalculator {
|
||||
// The current cost of a signature This amount may increase/decrease over time based on
|
||||
|
@ -19,6 +20,28 @@ pub struct FeeCalculator {
|
|||
|
||||
pub min_lamports_per_signature: u64,
|
||||
pub max_lamports_per_signature: u64,
|
||||
|
||||
// What portion of collected fees are to be destroyed, percentage-wise
|
||||
pub burn_percent: u8,
|
||||
}
|
||||
|
||||
/// TODO: determine good values for these
|
||||
pub const DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE: u64 = 42;
|
||||
pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: usize =
|
||||
710_000 * DEFAULT_TICKS_PER_SLOT as usize / DEFAULT_NUM_TICKS_PER_SECOND as usize;
|
||||
pub const DEFAULT_BURN_PERCENT: u8 = 50;
|
||||
|
||||
impl Default for FeeCalculator {
|
||||
fn default() -> Self {
|
||||
FeeCalculator {
|
||||
lamports_per_signature: 0,
|
||||
target_lamports_per_signature: DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE,
|
||||
target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
|
||||
min_lamports_per_signature: 0,
|
||||
max_lamports_per_signature: 0,
|
||||
burn_percent: DEFAULT_BURN_PERCENT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FeeCalculator {
|
||||
|
@ -26,6 +49,7 @@ impl FeeCalculator {
|
|||
let base_fee_calculator = Self {
|
||||
target_lamports_per_signature,
|
||||
lamports_per_signature: target_lamports_per_signature,
|
||||
target_signatures_per_slot: 0,
|
||||
..FeeCalculator::default()
|
||||
};
|
||||
|
||||
|
@ -48,15 +72,14 @@ impl FeeCalculator {
|
|||
me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
|
||||
|
||||
// What the cluster should charge at `latest_signatures_per_slot`
|
||||
let desired_lamports_per_signature = std::cmp::min(
|
||||
me.max_lamports_per_signature,
|
||||
std::cmp::max(
|
||||
me.min_lamports_per_signature,
|
||||
me.target_lamports_per_signature
|
||||
* std::cmp::min(latest_signatures_per_slot, std::u32::MAX as usize) as u64
|
||||
/ me.target_signatures_per_slot as u64,
|
||||
),
|
||||
);
|
||||
let desired_lamports_per_signature =
|
||||
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)
|
||||
as u64
|
||||
/ me.target_signatures_per_slot as u64,
|
||||
));
|
||||
|
||||
trace!(
|
||||
"desired_lamports_per_signature: {}",
|
||||
|
@ -82,13 +105,11 @@ impl FeeCalculator {
|
|||
gap_adjust
|
||||
);
|
||||
|
||||
me.lamports_per_signature = std::cmp::min(
|
||||
me.max_lamports_per_signature,
|
||||
std::cmp::max(
|
||||
me.min_lamports_per_signature,
|
||||
(base_fee_calculator.lamports_per_signature as i64 + gap_adjust) as u64,
|
||||
),
|
||||
);
|
||||
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,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
me.lamports_per_signature = base_fee_calculator.target_lamports_per_signature;
|
||||
|
@ -105,6 +126,11 @@ impl FeeCalculator {
|
|||
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
|
||||
pub fn burn(&self, fees: u64) -> u64 {
|
||||
fees * u64::from(100 - self.burn_percent) / 100
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -113,6 +139,19 @@ mod tests {
|
|||
use crate::pubkey::Pubkey;
|
||||
use crate::system_instruction;
|
||||
|
||||
#[test]
|
||||
fn test_fee_calculator_burn() {
|
||||
let mut fee_calculator = FeeCalculator::default();
|
||||
|
||||
assert_eq!(fee_calculator.burn(2), 1);
|
||||
|
||||
fee_calculator.burn_percent = 0;
|
||||
|
||||
assert_eq!(fee_calculator.burn(2), 2);
|
||||
fee_calculator.burn_percent = 100;
|
||||
assert_eq!(fee_calculator.burn(2), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fee_calculator_calculate_fee() {
|
||||
// Default: no fee.
|
||||
|
@ -140,18 +179,24 @@ mod tests {
|
|||
fn test_fee_calculator_derived_default() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mut f0 = FeeCalculator::default();
|
||||
assert_eq!(f0.target_signatures_per_slot, 0);
|
||||
assert_eq!(f0.target_lamports_per_signature, 0);
|
||||
let f0 = FeeCalculator::default();
|
||||
assert_eq!(
|
||||
f0.target_signatures_per_slot,
|
||||
DEFAULT_TARGET_SIGNATURES_PER_SLOT
|
||||
);
|
||||
assert_eq!(
|
||||
f0.target_lamports_per_signature,
|
||||
DEFAULT_TARGET_LAMPORTS_PER_SIGNATURE
|
||||
);
|
||||
assert_eq!(f0.lamports_per_signature, 0);
|
||||
f0.target_lamports_per_signature = 42;
|
||||
|
||||
let f1 = FeeCalculator::new_derived(&f0, 4242);
|
||||
assert_eq!(f1.target_signatures_per_slot, 0);
|
||||
let f1 = FeeCalculator::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
|
||||
assert_eq!(
|
||||
f1.target_signatures_per_slot,
|
||||
DEFAULT_TARGET_SIGNATURES_PER_SLOT
|
||||
);
|
||||
assert_eq!(f1.target_lamports_per_signature, 42);
|
||||
assert_eq!(f1.lamports_per_signature, 42);
|
||||
assert_eq!(f1.min_lamports_per_signature, 42);
|
||||
assert_eq!(f1.max_lamports_per_signature, 42);
|
||||
assert_eq!(f1.lamports_per_signature, 21); // min
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -164,27 +209,54 @@ mod tests {
|
|||
f = FeeCalculator::new_derived(&f, 0);
|
||||
|
||||
// Ramp fees up
|
||||
while f.lamports_per_signature < f.max_lamports_per_signature {
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let last_lamports_per_signature = f.lamports_per_signature;
|
||||
|
||||
f = FeeCalculator::new_derived(&f, std::usize::MAX);
|
||||
info!("[up] f.lamports_per_signature={}", f.lamports_per_signature);
|
||||
|
||||
// some maximum target reached
|
||||
if f.lamports_per_signature == last_lamports_per_signature {
|
||||
break;
|
||||
}
|
||||
// shouldn't take more than 1000 steps to get to minimum
|
||||
assert!(count < 1000);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// Ramp fees down
|
||||
while f.lamports_per_signature > f.min_lamports_per_signature {
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let last_lamports_per_signature = f.lamports_per_signature;
|
||||
f = FeeCalculator::new_derived(&f, 0);
|
||||
|
||||
info!(
|
||||
"[down] f.lamports_per_signature={}",
|
||||
f.lamports_per_signature
|
||||
);
|
||||
|
||||
// some minimum target reached
|
||||
if f.lamports_per_signature == last_lamports_per_signature {
|
||||
break;
|
||||
}
|
||||
|
||||
// shouldn't take more than 1000 steps to get to minimum
|
||||
assert!(count < 1000);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// 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);
|
||||
info!(
|
||||
"[target] f.lamports_per_signature={}",
|
||||
f.lamports_per_signature
|
||||
);
|
||||
// shouldn't take more than 100 steps to get to target
|
||||
assert!(count < 100);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,16 +130,16 @@ impl Builder {
|
|||
self.genesis_block.ticks_per_slot = ticks_per_slot;
|
||||
self
|
||||
}
|
||||
pub fn poh_config(mut self, poh_config: &PohConfig) -> Self {
|
||||
self.genesis_block.poh_config = poh_config.clone();
|
||||
pub fn poh_config(mut self, poh_config: PohConfig) -> Self {
|
||||
self.genesis_block.poh_config = poh_config;
|
||||
self
|
||||
}
|
||||
pub fn fee_calculator(mut self, fee_calculator: &FeeCalculator) -> Self {
|
||||
self.genesis_block.fee_calculator = fee_calculator.clone();
|
||||
pub fn fee_calculator(mut self, fee_calculator: FeeCalculator) -> Self {
|
||||
self.genesis_block.fee_calculator = fee_calculator;
|
||||
self
|
||||
}
|
||||
pub fn inflation(mut self, inflation: &Inflation) -> Self {
|
||||
self.genesis_block.inflation = inflation.clone();
|
||||
pub fn inflation(mut self, inflation: Inflation) -> Self {
|
||||
self.genesis_block.inflation = inflation;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue