add fee burning (#4818)

This commit is contained in:
Rob Walker 2019-06-26 10:13:21 -07:00 committed by GitHub
parent eb47538a82
commit 8a64e1ddc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 164 additions and 57 deletions

View File

@ -806,6 +806,7 @@ mod tests {
let expected = json!({
"jsonrpc": "2.0",
"result": [ blockhash.to_string(), {
"burnPercent": 50,
"lamportsPerSignature": 0,
"maxLamportsPerSignature": 0,
"minLamportsPerSignature": 0,

View File

@ -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.

View File

@ -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
);
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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
}
}