From 91f0faa72d885b17d99b6109e4f83868b6973524 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Fri, 1 May 2020 09:02:43 -0700 Subject: [PATCH] v1.0: incinerator backport (#9837) --- runtime/src/accounts.rs | 4 +- runtime/src/bank/mod.rs | 74 ++++++++++++++++++++++++++++++++++- runtime/src/rent_collector.rs | 9 ++++- sdk/macro/src/lib.rs | 8 +++- sdk/src/incinerator.rs | 6 +++ sdk/src/lib.rs | 1 + 6 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 sdk/src/incinerator.rs diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 9b7d77458e..fdefa94950 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -165,7 +165,7 @@ impl Accounts { let (account, rent) = AccountsDB::load(storage, ancestors, accounts_index, key) .and_then(|(mut account, _)| { if message.is_writable(i) && !account.executable { - let rent_due = rent_collector.update(&mut account); + let rent_due = rent_collector.update(&key, &mut account); Some((account, rent_due)) } else { Some((account, 0)) @@ -671,7 +671,7 @@ impl Accounts { if message.is_writable(i) { if account.rent_epoch == 0 { account.rent_epoch = rent_collector.epoch; - acc.2 += rent_collector.update(account); + acc.2 += rent_collector.update(&key, account); } accounts.push((key, &*account)); } diff --git a/runtime/src/bank/mod.rs b/runtime/src/bank/mod.rs index 1b1a55d1ea..7ba96091dd 100644 --- a/runtime/src/bank/mod.rs +++ b/runtime/src/bank/mod.rs @@ -38,6 +38,7 @@ use solana_sdk::{ genesis_config::GenesisConfig, hard_forks::HardForks, hash::{extend_and_hash, hashv, Hash}, + incinerator, inflation::Inflation, native_loader, nonce, pubkey::Pubkey, @@ -792,6 +793,9 @@ impl Bank { self.collect_fees(); self.distribute_rent(); self.update_slot_history(); + if self.epoch >= incinerator::ACTIVATION_EPOCH { + self.run_incinerator(); + } // freeze is a one-way trip, idempotent *hash = self.hash_internal_state(); @@ -1629,6 +1633,14 @@ impl Bank { .fetch_add(collected_rent, Ordering::Relaxed); } + fn run_incinerator(&self) { + if let Some((account, _)) = self.get_account_modified_since_parent(&incinerator::id()) { + self.capitalization + .fetch_sub(account.lamports, Ordering::Relaxed); + self.store_account(&incinerator::id(), &Account::default()); + } + } + /// Process a batch of transactions. #[must_use] pub fn load_execute_and_commit_transactions( @@ -1741,8 +1753,10 @@ impl Bank { pub fn deposit(&self, pubkey: &Pubkey, lamports: u64) { let mut account = self.get_account(pubkey).unwrap_or_default(); - self.collected_rent - .fetch_add(self.rent_collector.update(&mut account), Ordering::Relaxed); + self.collected_rent.fetch_add( + self.rent_collector.update(pubkey, &mut account), + Ordering::Relaxed, + ); account.lamports += lamports; self.store_account(pubkey, &account); } @@ -5772,4 +5786,60 @@ mod tests { info!("account: {:?}", account); assert!(account.executable); } + + #[test] + fn test_incinerator_inactive() { + let (genesis_config, mint_keypair) = create_genesis_config(1_000_000_000_000); + let bank0 = Arc::new(Bank::new(&genesis_config)); + + // Move to the first normal slot so normal rent behaviour applies + let bank = Bank::new_from_parent( + &bank0, + &Pubkey::default(), + genesis_config.epoch_schedule.first_normal_slot, + ); + let pre_capitalization = bank.capitalization(); + + let burn_amount = bank.get_minimum_balance_for_rent_exemption(0); + + assert_eq!(bank.get_balance(&incinerator::id()), 0); + bank.transfer(burn_amount, &mint_keypair, &incinerator::id()) + .unwrap(); + assert_eq!(bank.get_balance(&incinerator::id()), burn_amount); + bank.freeze(); + + // No effect! + assert_eq!(bank.get_balance(&incinerator::id()), burn_amount); + assert_eq!(bank.capitalization(), pre_capitalization); + } + + #[test] + fn test_incinerator_active() { + let (genesis_config, mint_keypair) = create_genesis_config(1_000_000_000_000); + let bank0 = Arc::new(Bank::new(&genesis_config)); + + // Move to the incinerator activation epoch + let bank = Bank::new_from_parent( + &bank0, + &Pubkey::default(), + genesis_config + .epoch_schedule + .get_first_slot_in_epoch(incinerator::ACTIVATION_EPOCH), + ); + let pre_capitalization = bank.capitalization(); + + // Burn a non-rent exempt amount + let burn_amount = bank.get_minimum_balance_for_rent_exemption(0) - 1; + + assert_eq!(bank.get_balance(&incinerator::id()), 0); + bank.transfer(burn_amount, &mint_keypair, &incinerator::id()) + .unwrap(); + assert_eq!(bank.get_balance(&incinerator::id()), burn_amount); + bank.freeze(); + assert_eq!(bank.get_balance(&incinerator::id()), 0); + + // Ensure that no rent was collected, and the entire burn amount was removed from bank + // capitalization + assert_eq!(bank.capitalization(), pre_capitalization - burn_amount); + } } diff --git a/runtime/src/rent_collector.rs b/runtime/src/rent_collector.rs index 525d7d3b51..0e5f6f2585 100644 --- a/runtime/src/rent_collector.rs +++ b/runtime/src/rent_collector.rs @@ -1,6 +1,7 @@ //! calculate and collect rent from Accounts use solana_sdk::{ - account::Account, clock::Epoch, epoch_schedule::EpochSchedule, rent::Rent, sysvar, + account::Account, clock::Epoch, epoch_schedule::EpochSchedule, incinerator, pubkey::Pubkey, + rent::Rent, sysvar, }; #[derive(Default, Serialize, Deserialize, Clone)] @@ -35,7 +36,11 @@ impl RentCollector { // updates this account's lamports and status and returns // the account rent collected, if any // - pub fn update(&self, account: &mut Account) -> u64 { + pub fn update(&self, address: &Pubkey, account: &mut Account) -> u64 { + if *address == incinerator::id() && self.epoch >= incinerator::ACTIVATION_EPOCH { + return 0; + } + if account.executable || account.rent_epoch > self.epoch || sysvar::check_id(&account.owner) { 0 diff --git a/sdk/macro/src/lib.rs b/sdk/macro/src/lib.rs index 2f30751743..90736556a0 100644 --- a/sdk/macro/src/lib.rs +++ b/sdk/macro/src/lib.rs @@ -21,8 +21,12 @@ impl Parse for Id { let id_vec = bs58::decode(id_literal.value()) .into_vec() .map_err(|_| syn::Error::new_spanned(&id_literal, "failed to decode base58 id"))?; - let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])) - .map_err(|_| syn::Error::new_spanned(&id_literal, "id is not 32 bytes long"))?; + let id_array = <[u8; 32]>::try_from(<&[u8]>::clone(&&id_vec[..])).map_err(|_| { + syn::Error::new_spanned( + &id_literal, + format!("id is not 32 bytes long: len={}", id_vec.len()), + ) + })?; let bytes = id_array.iter().map(|b| LitByte::new(*b, Span::call_site())); quote! { ::solana_sdk::pubkey::Pubkey::new_from_array( diff --git a/sdk/src/incinerator.rs b/sdk/src/incinerator.rs new file mode 100644 index 0000000000..d9b81bbb41 --- /dev/null +++ b/sdk/src/incinerator.rs @@ -0,0 +1,6 @@ +//! Lamports credited to this address will be removed from the total supply (burned) at the end of +//! the current block. + +crate::declare_id!("1nc1nerator11111111111111111111111111111111"); + +pub const ACTIVATION_EPOCH: u64 = 30; // Incinerator activates on mainnet-beta at this epoch diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 12bbdd1dcc..5dcd402907 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -10,6 +10,7 @@ pub mod entrypoint_native; pub mod epoch_schedule; pub mod fee_calculator; pub mod hash; +pub mod incinerator; pub mod inflation; pub mod instruction; pub mod loader_instruction;