From a75b94a362c1b935b0f18c569b6f921a5512a557 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Thu, 22 Aug 2019 13:00:47 -0700 Subject: [PATCH 1/5] plumb some rent --- genesis/src/main.rs | 13 ++ programs/storage_api/src/storage_contract.rs | 4 +- runtime/src/accounts.rs | 194 +++++++++++++------ runtime/src/accounts_db.rs | 5 +- runtime/src/append_vec.rs | 7 +- runtime/src/bank.rs | 124 +++++++----- runtime/src/lib.rs | 1 + runtime/src/rent_collector.rs | 65 +++++++ runtime/src/system_instruction_processor.rs | 1 + sdk/src/account.rs | 8 +- sdk/src/fee_calculator.rs | 2 +- sdk/src/inflation.rs | 2 +- sdk/src/lib.rs | 3 + sdk/src/native_loader.rs | 1 + sdk/src/rent.rs | 68 ++++++- 15 files changed, 368 insertions(+), 130 deletions(-) create mode 100644 runtime/src/rent_collector.rs diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 8de7e4efda8df0..b4cacf11b31bfc 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -8,6 +8,7 @@ use solana_sdk::genesis_block::Builder; use solana_sdk::hash::{hash, Hash}; use solana_sdk::poh_config::PohConfig; use solana_sdk::pubkey::Pubkey; +use solana_sdk::rent::Rent; use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil}; use solana_sdk::system_program; use solana_sdk::timing; @@ -59,6 +60,7 @@ fn main() -> Result<(), Box> { let default_target_lamports_per_signature = &FeeCalculator::default() .target_lamports_per_signature .to_string(); + let default_lamports_per_byte_year = &Rent::default().lamports_per_byte_year.to_string(); let default_target_signatures_per_slot = &FeeCalculator::default() .target_signatures_per_slot .to_string(); @@ -161,6 +163,17 @@ fn main() -> Result<(), Box> { verification when the cluster is operating at target-signatures-per-slot", ), ) + .arg( + Arg::with_name("lamports_per_byte_year") + .long("lamports-per-byte-year") + .value_name("LAMPORTS") + .takes_value(true) + .default_value(default_lamports_per_byte_year) + .help( + "The cost in lamports that the cluster will charge per byte per year \ + for accounts with data.", + ), + ) .arg( Arg::with_name("target_signatures_per_slot") .long("target-signatures-per-slot") diff --git a/programs/storage_api/src/storage_contract.rs b/programs/storage_api/src/storage_contract.rs index e1cf91ac6871c3..4d777253673b17 100644 --- a/programs/storage_api/src/storage_contract.rs +++ b/programs/storage_api/src/storage_contract.rs @@ -547,10 +547,8 @@ mod tests { let mut account = StorageAccount { id: Pubkey::default(), account: &mut Account { - lamports: 0, - data: vec![], owner: id(), - executable: false, + ..Account::default() }, }; let segment_index = 0; diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 6986c543c7c44e..a8f4dee3a15b84 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -1,11 +1,9 @@ -use crate::accounts_db::{ - AccountInfo, AccountStorage, AccountsDB, AppendVecId, ErrorCounters, InstructionAccounts, - InstructionCredits, InstructionLoaders, -}; +use crate::accounts_db::{AccountInfo, AccountStorage, AccountsDB, AppendVecId, ErrorCounters}; use crate::accounts_index::{AccountsIndex, Fork}; use crate::append_vec::StoredAccount; use crate::blockhash_queue::BlockhashQueue; use crate::message_processor::has_duplicates; +use crate::rent_collector::RentCollector; use bincode::serialize; use log::*; use rayon::slice::ParallelSliceMut; @@ -46,6 +44,12 @@ pub struct Accounts { credit_only_account_locks: Arc>>>, } +// for the load instructions +pub type TransactionAccounts = Vec; +pub type TransactionCredits = Vec; +pub type TransactionRents = Vec; +pub type TransactionLoaders = Vec>; + impl Accounts { pub fn new(paths: Option) -> Self { let accounts_db = Arc::new(AccountsDB::new(paths)); @@ -82,7 +86,8 @@ impl Accounts { tx: &Transaction, fee: u64, error_counters: &mut ErrorCounters, - ) -> Result<(Vec, InstructionCredits)> { + rent_collector: &RentCollector, + ) -> Result<(TransactionAccounts, TransactionCredits, TransactionRents)> { // Copy all the accounts let message = tx.message(); if tx.signatures.is_empty() && fee != 0 { @@ -96,30 +101,35 @@ impl Accounts { // There is no way to predict what program will execute without an error // If a fee can pay for execution then the program will be scheduled - let mut called_accounts: Vec = vec![]; - let mut credits: InstructionCredits = vec![]; - for key in message.account_keys.iter() { - if !message.program_ids().contains(&key) { - called_accounts.push( - AccountsDB::load(storage, ancestors, accounts_index, key) - .map(|(account, _)| account) - .unwrap_or_default(), - ); - credits.push(0); - } + let mut accounts: TransactionAccounts = vec![]; + let mut credits: TransactionCredits = vec![]; + let mut rents: TransactionRents = vec![]; + for key in message + .account_keys + .iter() + .filter(|key| !message.program_ids().contains(&key)) + { + let (account, rent) = AccountsDB::load(storage, ancestors, accounts_index, key) + .and_then(|(account, _)| rent_collector.update(account)) + .unwrap_or_default(); + + accounts.push(account); + credits.push(0); + rents.push(rent); } - if called_accounts.is_empty() || called_accounts[0].lamports == 0 { + + if accounts.is_empty() || accounts[0].lamports == 0 { error_counters.account_not_found += 1; Err(TransactionError::AccountNotFound) - } else if called_accounts[0].owner != system_program::id() { + } else if accounts[0].owner != system_program::id() { error_counters.invalid_account_for_fee += 1; Err(TransactionError::InvalidAccountForFee) - } else if called_accounts[0].lamports < fee { + } else if accounts[0].lamports < fee { error_counters.insufficient_funds += 1; Err(TransactionError::InsufficientFundsForFee) } else { - called_accounts[0].lamports -= fee; - Ok((called_accounts, credits)) + accounts[0].lamports -= fee; + Ok((accounts, credits, rents)) } } } @@ -174,7 +184,7 @@ impl Accounts { accounts_index: &AccountsIndex, tx: &Transaction, error_counters: &mut ErrorCounters, - ) -> Result>> { + ) -> Result { let message = tx.message(); message .instructions @@ -203,7 +213,15 @@ impl Accounts { lock_results: Vec>, hash_queue: &BlockhashQueue, error_counters: &mut ErrorCounters, - ) -> Vec> { + rent_collector: &RentCollector, + ) -> Vec< + Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>, + > { //PERF: hold the lock to scan for the references, but not to clone the accounts //TODO: two locks usually leads to deadlocks, should this be one structure? let accounts_index = self.accounts_db.accounts_index.read().unwrap(); @@ -217,13 +235,14 @@ impl Accounts { .ok_or(TransactionError::BlockhashNotFound)?; let fee = fee_calculator.calculate_fee(tx.message()); - let (accounts, credits) = Self::load_tx_accounts( + let (accounts, credits, rents) = Self::load_tx_accounts( &storage, ancestors, &accounts_index, tx, fee, error_counters, + rent_collector, )?; let loaders = Self::load_loaders( &storage, @@ -232,7 +251,7 @@ impl Accounts { tx, error_counters, )?; - Ok((accounts, loaders, credits)) + Ok((accounts, loaders, credits, rents)) } (_, Err(e)) => Err(e), }) @@ -503,7 +522,12 @@ impl Accounts { fork: Fork, txs: &[Transaction], res: &[Result<()>], - loaded: &mut [Result<(InstructionAccounts, InstructionLoaders, InstructionCredits)>], + loaded: &mut [Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>], ) { let accounts_to_store = self.collect_accounts_to_store(txs, res, loaded); self.accounts_db.store(fork, &accounts_to_store); @@ -578,7 +602,12 @@ impl Accounts { &self, txs: &'a [Transaction], res: &'a [Result<()>], - loaded: &'a mut [Result<(InstructionAccounts, InstructionLoaders, InstructionCredits)>], + loaded: &'a mut [Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>], ) -> Vec<(&'a Pubkey, &'a Account)> { let mut accounts = Vec::new(); for (i, raccs) in loaded.iter_mut().enumerate() { @@ -651,7 +680,14 @@ mod tests { ka: &Vec<(Pubkey, Account)>, fee_calculator: &FeeCalculator, error_counters: &mut ErrorCounters, - ) -> Vec> { + ) -> Vec< + Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>, + > { let mut hash_queue = BlockhashQueue::new(100); hash_queue.register_hash(&tx.message().recent_blockhash, &fee_calculator); let accounts = Accounts::new(None); @@ -660,8 +696,15 @@ mod tests { } let ancestors = vec![(0, 0)].into_iter().collect(); - let res = - accounts.load_accounts(&ancestors, &[tx], vec![Ok(())], &hash_queue, error_counters); + let rent_collector = RentCollector::default(); + let res = accounts.load_accounts( + &ancestors, + &[tx], + vec![Ok(())], + &hash_queue, + error_counters, + &rent_collector, + ); res } @@ -669,7 +712,14 @@ mod tests { tx: Transaction, ka: &Vec<(Pubkey, Account)>, error_counters: &mut ErrorCounters, - ) -> Vec> { + ) -> Vec< + Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>, + > { let fee_calculator = FeeCalculator::default(); load_accounts_with_fee(tx, ka, &fee_calculator, error_counters) } @@ -825,10 +875,12 @@ mod tests { let key0 = keypair.pubkey(); let key1 = Pubkey::new(&[5u8; 32]); - let account = Account::new(1, 1, &Pubkey::default()); + let mut account = Account::new(1, 1, &Pubkey::default()); + account.rent_epoch = 1; accounts.push((key0, account)); - let account = Account::new(2, 1, &Pubkey::default()); + let mut account = Account::new(2, 1, &Pubkey::default()); + account.rent_epoch = 1; accounts.push((key1, account)); let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])]; @@ -845,13 +897,18 @@ mod tests { assert_eq!(error_counters.account_not_found, 0); assert_eq!(loaded_accounts.len(), 1); match &loaded_accounts[0] { - Ok((instruction_accounts, instruction_loaders, instruction_credits)) => { - assert_eq!(instruction_accounts.len(), 2); - assert_eq!(instruction_accounts[0], accounts[0].1); - assert_eq!(instruction_loaders.len(), 1); - assert_eq!(instruction_loaders[0].len(), 0); - assert_eq!(instruction_credits.len(), 2); - assert_eq!(instruction_credits, &vec![0, 0]); + Ok(( + transaction_accounts, + transaction_loaders, + transaction_credits, + _transaction_rents, + )) => { + assert_eq!(transaction_accounts.len(), 2); + assert_eq!(transaction_accounts[0], accounts[0].1); + assert_eq!(transaction_loaders.len(), 1); + assert_eq!(transaction_loaders[0].len(), 0); + assert_eq!(transaction_credits.len(), 2); + assert_eq!(transaction_credits, &vec![0, 0]); } Err(e) => Err(e).unwrap(), } @@ -996,21 +1053,25 @@ mod tests { let key2 = Pubkey::new(&[6u8; 32]); let key3 = Pubkey::new(&[7u8; 32]); - let account = Account::new(1, 1, &Pubkey::default()); + let mut account = Account::new(1, 1, &Pubkey::default()); + account.rent_epoch = 1; accounts.push((key0, account)); let mut account = Account::new(40, 1, &Pubkey::default()); account.executable = true; + account.rent_epoch = 1; account.owner = native_loader::id(); accounts.push((key1, account)); let mut account = Account::new(41, 1, &Pubkey::default()); account.executable = true; + account.rent_epoch = 1; account.owner = key1; accounts.push((key2, account)); let mut account = Account::new(42, 1, &Pubkey::default()); account.executable = true; + account.rent_epoch = 1; account.owner = key2; accounts.push((key3, account)); @@ -1031,15 +1092,20 @@ mod tests { assert_eq!(error_counters.account_not_found, 0); assert_eq!(loaded_accounts.len(), 1); match &loaded_accounts[0] { - Ok((instruction_accounts, instruction_loaders, instruction_credits)) => { - assert_eq!(instruction_accounts.len(), 1); - assert_eq!(instruction_accounts[0], accounts[0].1); - assert_eq!(instruction_loaders.len(), 2); - assert_eq!(instruction_loaders[0].len(), 1); - assert_eq!(instruction_loaders[1].len(), 2); - assert_eq!(instruction_credits.len(), 1); - assert_eq!(instruction_credits, &vec![0]); - for loaders in instruction_loaders.iter() { + Ok(( + transaction_accounts, + transaction_loaders, + transaction_credits, + _transaction_rents, + )) => { + assert_eq!(transaction_accounts.len(), 1); + assert_eq!(transaction_accounts[0], accounts[0].1); + assert_eq!(transaction_loaders.len(), 2); + assert_eq!(transaction_loaders[0].len(), 1); + assert_eq!(transaction_loaders[1].len(), 2); + assert_eq!(transaction_credits.len(), 1); + assert_eq!(transaction_credits, &vec![0]); + for loaders in transaction_loaders.iter() { for (i, accounts_subset) in loaders.iter().enumerate() { // +1 to skip first not loader account assert_eq![accounts_subset.1, accounts[i + 1].1]; @@ -1475,22 +1541,26 @@ mod tests { let account1 = Account::new(2, 0, &Pubkey::default()); let account2 = Account::new(3, 0, &Pubkey::default()); - let instruction_accounts0 = vec![account0, account2.clone()]; - let instruction_loaders0 = vec![]; - let instruction_credits0 = vec![0, 2]; + let transaction_accounts0 = vec![account0, account2.clone()]; + let transaction_loaders0 = vec![]; + let transaction_credits0 = vec![0, 2]; + let transaction_rents0 = vec![0, 0]; let loaded0 = Ok(( - instruction_accounts0, - instruction_loaders0, - instruction_credits0, + transaction_accounts0, + transaction_loaders0, + transaction_credits0, + transaction_rents0, )); - let instruction_accounts1 = vec![account1, account2.clone()]; - let instruction_loaders1 = vec![]; - let instruction_credits1 = vec![0, 3]; + let transaction_accounts1 = vec![account1, account2.clone()]; + let transaction_loaders1 = vec![]; + let transaction_credits1 = vec![0, 3]; + let transaction_rents1 = vec![0, 0]; let loaded1 = Ok(( - instruction_accounts1, - instruction_loaders1, - instruction_credits1, + transaction_accounts1, + transaction_loaders1, + transaction_credits1, + transaction_rents1, )); let mut loaded = vec![loaded0, loaded1]; diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index 0f70f50a99926c..f9a51ebafef3c0 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -30,7 +30,7 @@ use serde::de::{MapAccess, Visitor}; use serde::ser::{SerializeMap, Serializer}; use serde::{Deserialize, Serialize}; use solana_measure::measure::Measure; -use solana_sdk::account::{Account, LamportCredit}; +use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; use std::collections::{HashMap, HashSet}; use std::fmt; @@ -76,9 +76,6 @@ pub struct AccountInfo { } /// An offset into the AccountsDB::storage vector pub type AppendVecId = usize; -pub type InstructionAccounts = Vec; -pub type InstructionCredits = Vec; -pub type InstructionLoaders = Vec>; // Each fork has a set of storage entries. type ForkStores = HashMap>; diff --git a/runtime/src/append_vec.rs b/runtime/src/append_vec.rs index b06f654315a77d..a7b8e9345b0ad9 100644 --- a/runtime/src/append_vec.rs +++ b/runtime/src/append_vec.rs @@ -1,8 +1,7 @@ use bincode::{deserialize_from, serialize_into, serialized_size}; use memmap::MmapMut; use serde::{Deserialize, Serialize}; -use solana_sdk::account::Account; -use solana_sdk::pubkey::Pubkey; +use solana_sdk::{account::Account, pubkey::Pubkey, Epoch}; use std::fmt; use std::fs::{create_dir_all, remove_file, OpenOptions}; use std::io; @@ -38,6 +37,8 @@ pub struct AccountBalance { pub owner: Pubkey, /// this account's data contains a loaded program (and is now read-only) pub executable: bool, + /// the epoch at which this account will next owe rent + pub rent_epoch: Epoch, } /// References to Memory Mapped memory @@ -57,6 +58,7 @@ impl<'a> StoredAccount<'a> { lamports: self.balance.lamports, owner: self.balance.owner, executable: self.balance.executable, + rent_epoch: self.balance.rent_epoch, data: self.data.to_vec(), } } @@ -281,6 +283,7 @@ impl AppendVec { lamports: account.lamports, owner: account.owner, executable: account.executable, + rent_epoch: account.rent_epoch, }; let balance_ptr = &balance as *const AccountBalance; let data_len = storage_meta.data_len as usize; diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 30f3acc31cd758..d892d97e035311 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -2,23 +2,26 @@ //! programs. It offers a high-level API that signs transactions //! on behalf of the caller, and a low-level API for when they have //! already been signed and verified. -use crate::accounts::Accounts; -use crate::accounts_db::{ - AccountStorageEntry, AccountsDBSerialize, AppendVecId, ErrorCounters, InstructionAccounts, - InstructionCredits, InstructionLoaders, -}; -use crate::accounts_index::Fork; -use crate::blockhash_queue::BlockhashQueue; -use crate::epoch_schedule::EpochSchedule; -use crate::locked_accounts_results::LockedAccountsResults; -use crate::message_processor::{MessageProcessor, ProcessInstruction}; -use crate::serde_utils::{ - deserialize_atomicbool, deserialize_atomicusize, serialize_atomicbool, serialize_atomicusize, +use crate::{ + accounts::{ + Accounts, TransactionAccounts, TransactionCredits, TransactionLoaders, TransactionRents, + }, + accounts_db::{AccountStorageEntry, AccountsDBSerialize, AppendVecId, ErrorCounters}, + accounts_index::Fork, + blockhash_queue::BlockhashQueue, + epoch_schedule::EpochSchedule, + locked_accounts_results::LockedAccountsResults, + message_processor::{MessageProcessor, ProcessInstruction}, + rent_collector::RentCollector, + serde_utils::{ + deserialize_atomicbool, deserialize_atomicusize, serialize_atomicbool, + serialize_atomicusize, + }, + stakes::Stakes, + status_cache::{SlotDelta, StatusCache}, + storage_utils, + storage_utils::StorageAccounts, }; -use crate::stakes::Stakes; -use crate::status_cache::{SlotDelta, StatusCache}; -use crate::storage_utils; -use crate::storage_utils::StorageAccounts; use bincode::{deserialize_from, serialize_into}; use byteorder::{ByteOrder, LittleEndian}; use log::*; @@ -197,8 +200,11 @@ pub struct Bank { /// The number of slots per Storage segment slots_per_segment: u64, - /// Bank fork (i.e. slot, i.e. block) - slot: u64, + /// Bank slot (i.e. block) + slot: Slot, + + /// Bank epoch + epoch: Epoch, /// Bank height in term of banks bank_height: u64, @@ -214,6 +220,9 @@ pub struct Bank { /// Latest transaction fees for transactions processed by this bank fee_calculator: FeeCalculator, + /// latest rent collector, knows the epoch + rent_collector: RentCollector, + /// initialized from genesis epoch_schedule: EpochSchedule, @@ -282,17 +291,22 @@ impl Bank { let src = StatusCacheRc { status_cache: parent.src.status_cache.clone(), }; + let epoch_schedule = parent.epoch_schedule; + let epoch = epoch_schedule.get_epoch(slot); + let mut new = Bank { rc, src, + slot, + epoch, blockhash_queue: RwLock::new(parent.blockhash_queue.read().unwrap().clone()), // TODO: clean this up, soo much special-case copying... ticks_per_slot: parent.ticks_per_slot, slots_per_segment: parent.slots_per_segment, slots_per_year: parent.slots_per_year, - epoch_schedule: parent.epoch_schedule, - slot, + epoch_schedule, + rent_collector: parent.rent_collector.clone_with_epoch(epoch), max_tick_height: (slot + 1) * parent.ticks_per_slot - 1, bank_height: parent.bank_height + 1, fee_calculator: FeeCalculator::new_derived( @@ -300,15 +314,15 @@ impl Bank { parent.signature_count(), ), capitalization: AtomicUsize::new(parent.capitalization() as usize), - inflation: parent.inflation.clone(), + inflation: parent.inflation, transaction_count: AtomicUsize::new(parent.transaction_count() as usize), - stakes: RwLock::new(parent.stakes.read().unwrap().clone_with_epoch(0)), + stakes: RwLock::new(parent.stakes.read().unwrap().clone_with_epoch(epoch)), + epoch_stakes: parent.epoch_stakes.clone(), storage_accounts: RwLock::new(parent.storage_accounts.read().unwrap().clone()), parent_hash: parent.hash(), collector_id: *collector_id, collector_fees: AtomicUsize::new(0), ancestors: HashMap::new(), - epoch_stakes: HashMap::new(), hash: RwLock::new(Hash::default()), is_delta: AtomicBool::new(false), tick_height: AtomicUsize::new(parent.tick_height.load(Ordering::Relaxed)), @@ -316,28 +330,21 @@ impl Bank { message_processor: MessageProcessor::default(), }; - { - *new.stakes.write().unwrap() = - parent.stakes.read().unwrap().clone_with_epoch(new.epoch()); - } - datapoint_info!( "bank-new_from_parent-heights", ("slot_height", slot, i64), ("bank_height", new.bank_height, i64) ); - new.epoch_stakes = { - let mut epoch_stakes = parent.epoch_stakes.clone(); - let epoch = new.get_stakers_epoch(new.slot); - // update epoch_vote_states cache - // if my parent didn't populate for this epoch, we've - // crossed a boundary - if epoch_stakes.get(&epoch).is_none() { - epoch_stakes.insert(epoch, new.stakes.read().unwrap().clone()); - } - epoch_stakes - }; + let stakers_epoch = epoch_schedule.get_stakers_epoch(slot); + // update epoch_stakes cache + // if my parent didn't populate for this staker's epoch, we've + // crossed a boundary + if new.epoch_stakes.get(&stakers_epoch).is_none() { + new.epoch_stakes + .insert(stakers_epoch, new.stakes.read().unwrap().clone()); + } + new.ancestors.insert(new.slot(), 0); new.parents().iter().enumerate().for_each(|(i, p)| { new.ancestors.insert(p.slot(), i + 1); @@ -375,7 +382,7 @@ impl Bank { } pub fn epoch(&self) -> u64 { - self.epoch_schedule.get_epoch(self.slot) + self.epoch } pub fn freeze_lock(&self) -> RwLockReadGuard { @@ -611,7 +618,7 @@ impl Bank { genesis_block.epoch_warmup, ); - self.inflation = genesis_block.inflation.clone(); + self.inflation = genesis_block.inflation; // Add additional native programs specified in the genesis block for (name, program_id) in &genesis_block.native_instruction_processors { @@ -781,13 +788,21 @@ impl Bank { txs: &[Transaction], results: Vec>, error_counters: &mut ErrorCounters, - ) -> Vec> { + ) -> Vec< + Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>, + > { self.rc.accounts.load_accounts( &self.ancestors, txs, results, &self.blockhash_queue.read().unwrap(), error_counters, + &self.rent_collector, ) } fn check_refs( @@ -932,7 +947,14 @@ impl Bank { lock_results: &LockedAccountsResults, max_age: usize, ) -> ( - Vec>, + Vec< + Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>, + >, Vec>, Vec, usize, @@ -970,7 +992,7 @@ impl Bank { .zip(txs.iter()) .map(|(accs, tx)| match accs { Err(e) => Err(e.clone()), - Ok((ref mut accounts, ref mut loaders, ref mut credits)) => { + Ok((ref mut accounts, ref mut loaders, ref mut credits, ref mut _rents)) => { signature_count += tx.message().header.num_required_signatures as usize; self.message_processor .process_message(tx.message(), loaders, accounts, credits) @@ -1061,9 +1083,10 @@ impl Bank { &self, txs: &[Transaction], loaded_accounts: &mut [Result<( - InstructionAccounts, - InstructionLoaders, - InstructionCredits, + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, )>], executed: &[Result<()>], tx_count: usize, @@ -1332,7 +1355,12 @@ impl Bank { &self, txs: &[Transaction], res: &[Result<()>], - loaded: &[Result<(InstructionAccounts, InstructionLoaders, InstructionCredits)>], + loaded: &[Result<( + TransactionAccounts, + TransactionLoaders, + TransactionCredits, + TransactionRents, + )>], ) { for (i, raccs) in loaded.iter().enumerate() { if res[i].is_err() || raccs.is_err() { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e4be3bb4fd6526..b84b0d16108f83 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,6 +12,7 @@ pub mod loader_utils; pub mod locked_accounts_results; pub mod message_processor; mod native_loader; +pub mod rent_collector; mod serde_utils; pub mod stakes; pub mod status_cache; diff --git a/runtime/src/rent_collector.rs b/runtime/src/rent_collector.rs new file mode 100644 index 00000000000000..554ab2ff90bead --- /dev/null +++ b/runtime/src/rent_collector.rs @@ -0,0 +1,65 @@ +//! calculate and collect rent from Accounts +use crate::epoch_schedule::EpochSchedule; +use solana_sdk::{account::Account, rent::Rent, timing::Epoch}; + +#[derive(Default, Serialize, Deserialize, Clone)] +pub struct RentCollector { + pub epoch: Epoch, + pub epoch_schedule: EpochSchedule, + pub slots_per_year: f64, + pub rent: Rent, +} + +impl RentCollector { + pub fn new( + epoch: Epoch, + epoch_schedule: &EpochSchedule, + slots_per_year: f64, + rent: &Rent, + ) -> Self { + Self { + epoch, + epoch_schedule: *epoch_schedule, + slots_per_year, + rent: *rent, + } + } + + pub fn clone_with_epoch(&self, epoch: Epoch) -> Self { + Self { + epoch, + ..self.clone() + } + } + // updates this account's lamports and status and returns + // the account rent collected, if any + // + pub fn update(&self, mut account: Account) -> Option<(Account, u64)> { + if account.data.is_empty() || account.rent_epoch > self.epoch { + Some((account, 0)) + } else { + let slots_elapsed: u64 = (account.rent_epoch..=self.epoch) + .map(|epoch| self.epoch_schedule.get_slots_in_epoch(epoch + 1)) + .sum(); + + let (rent_due, exempt) = self.rent.due( + account.lamports, + account.data.len(), + slots_elapsed as f64 / self.slots_per_year, + ); + + if exempt || rent_due != 0 { + if account.lamports > rent_due { + account.rent_epoch = self.epoch + 1; + account.lamports -= rent_due; + Some((account, rent_due)) + } else { + None + } + } else { + // maybe collect rent later, leave account alone + Some((account, 0)) + } + } + } +} diff --git a/runtime/src/system_instruction_processor.rs b/runtime/src/system_instruction_processor.rs index 69d8e67ea9c940..ff7525976552f2 100644 --- a/runtime/src/system_instruction_processor.rs +++ b/runtime/src/system_instruction_processor.rs @@ -251,6 +251,7 @@ mod tests { data: vec![0, 1, 2, 3], owner: Pubkey::default(), executable: false, + rent_epoch: 0, }; let unchanged_account = populated_account.clone(); diff --git a/sdk/src/account.rs b/sdk/src/account.rs index 9e4270a9004393..9ef60a8031ec8b 100644 --- a/sdk/src/account.rs +++ b/sdk/src/account.rs @@ -1,4 +1,5 @@ use crate::pubkey::Pubkey; +use crate::Epoch; use std::{cmp, fmt}; /// An Account with data that is stored on chain @@ -13,6 +14,8 @@ pub struct Account { pub owner: Pubkey, /// this account's data contains a loaded program (and is now read-only) pub executable: bool, + /// the epoch at which this account will next owe rent + pub rent_epoch: Epoch, } impl fmt::Debug for Account { @@ -25,11 +28,12 @@ impl fmt::Debug for Account { }; write!( f, - "Account {{ lamports: {} data.len: {} owner: {} executable: {}{} }}", + "Account {{ lamports: {} data.len: {} owner: {} executable: {} rent_epoch: {}{} }}", self.lamports, self.data.len(), self.owner, self.executable, + self.rent_epoch, data_str, ) } @@ -43,6 +47,7 @@ impl Account { data: vec![0u8; space], owner: *owner, executable: false, + rent_epoch: 0, } } @@ -57,6 +62,7 @@ impl Account { data, owner: *owner, executable: false, + rent_epoch: 0, }) } diff --git a/sdk/src/fee_calculator.rs b/sdk/src/fee_calculator.rs index 652ec08b3be06f..262886377791ed 100644 --- a/sdk/src/fee_calculator.rs +++ b/sdk/src/fee_calculator.rs @@ -29,7 +29,7 @@ pub struct FeeCalculator { 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_TICKS_PER_SECOND as usize; -pub const DEFAULT_BURN_PERCENT: u8 = 127; +pub const DEFAULT_BURN_PERCENT: u8 = ((50usize * std::u8::MAX as usize) / 100usize) as u8; impl Default for FeeCalculator { fn default() -> Self { diff --git a/sdk/src/inflation.rs b/sdk/src/inflation.rs index 5d538e0c2b567b..74e9f5cd30e759 100644 --- a/sdk/src/inflation.rs +++ b/sdk/src/inflation.rs @@ -1,6 +1,6 @@ //! configuration for network inflation -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Copy)] pub struct Inflation { /// Initial inflation percentage, from time=0 pub initial: f64, diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 24287c92b6fdae..852a0cdf0fc5a8 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -28,3 +28,6 @@ pub mod transport; #[macro_use] extern crate serde_derive; + +pub type Epoch = u64; +pub type Slot = u64; diff --git a/sdk/src/native_loader.rs b/sdk/src/native_loader.rs index c38a4f5f7656e0..ce0cdded26a5d6 100644 --- a/sdk/src/native_loader.rs +++ b/sdk/src/native_loader.rs @@ -14,5 +14,6 @@ pub fn create_loadable_account(name: &str) -> Account { owner: id(), data: name.as_bytes().to_vec(), executable: true, + rent_epoch: 0, } } diff --git a/sdk/src/rent.rs b/sdk/src/rent.rs index 9e3c0acbec83db..b6999d71c93ce8 100644 --- a/sdk/src/rent.rs +++ b/sdk/src/rent.rs @@ -1,12 +1,15 @@ //! configuration for network rent -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Debug)] pub struct Rent { /// Rental rate pub lamports_per_byte_year: u64, /// exemption threshold, in years pub exemption_threshold: f64, + + // What portion of collected rent are to be destroyed, percentage-wise + pub burn_percent: u8, } /// default rental rate in lamports/byte-year, based on: @@ -14,16 +17,20 @@ pub struct Rent { /// $1 per Sol /// $0.01 per megabyte day /// $3.65 per megabyte year -pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 17_179_869_184 / 100 * 365 / (1024 * 1024); +pub const DEFAULT_LAMPORTS_PER_BYTE_YEAR: u64 = 0; //17_179_869_184 / 100 * 365 / (1024 * 1024); /// default amount of time (in years) the balance has to include rent for pub const DEFAULT_EXEMPTION_THRESHOLD: f64 = 2.0; +/// default amount of rent to burn, as a fraction of std::u8::MAX +pub const DEFAULT_BURN_PERCENT: u8 = ((50usize * std::u8::MAX as usize) / 100usize) as u8; + impl Default for Rent { fn default() -> Self { Self { lamports_per_byte_year: DEFAULT_LAMPORTS_PER_BYTE_YEAR, exemption_threshold: DEFAULT_EXEMPTION_THRESHOLD, + burn_percent: DEFAULT_BURN_PERCENT, } } } @@ -41,12 +48,14 @@ impl Rent { } /// rent due on account's data_len with balance - pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> u64 { + pub fn due(&self, balance: u64, data_len: usize, years_elapsed: f64) -> (u64, bool) { if self.is_exempt(balance, data_len) { - 0 + (0, true) } else { - let bytes = data_len as u64; - ((self.lamports_per_byte_year * bytes) as f64 * years_elapsed) as u64 + ( + ((self.lamports_per_byte_year * data_len as u64) as f64 * years_elapsed) as u64, + false, + ) } } } @@ -59,14 +68,57 @@ mod tests { fn test_due() { let rent = Rent::default(); - assert_eq!(rent.due(0, 1, 1.0), DEFAULT_LAMPORTS_PER_BYTE_YEAR); + assert_eq!(rent.due(0, 1, 1.0), (DEFAULT_LAMPORTS_PER_BYTE_YEAR, false)); assert_eq!( rent.due( DEFAULT_LAMPORTS_PER_BYTE_YEAR * DEFAULT_EXEMPTION_THRESHOLD as u64, 1, 1.0 ), - 0 + (0, true) ); } + + // uncomment me and make my eprintlns macros + // #[test] + // fn test_rent_model() { + // use crate::timing::*; + // + // const SECONDS_PER_YEAR: f64 = (365.25 * 24.0 * 60.0 * 60.0); + // const SLOTS_PER_YEAR: f64 = + // SECONDS_PER_YEAR / (DEFAULT_TICKS_PER_SLOT as f64 / DEFAULT_TICKS_PER_SECOND as f64); + // + // let rent = Rent::default(); + // + // eprintln(); + // // lamports charged per byte per slot at $1/MByear, rent per slot is zero + // eprintln( + // "{} lamports per byte-slot, rent.due(): {}", + // (1.0 / SLOTS_PER_YEAR) * DEFAULT_LAMPORTS_PER_BYTE_YEAR as f64, + // rent.due(0, 1, 1.0 / SLOTS_PER_YEAR).0, + // ); + // // lamports charged per byte per _epoch_ starts to have some significant digits + // eprintln( + // "{} lamports per byte-epoch, rent.due(): {}", + // (1.0 / SLOTS_PER_YEAR) + // * (DEFAULT_LAMPORTS_PER_BYTE_YEAR * DEFAULT_SLOTS_PER_EPOCH) as f64, + // rent.due( + // 0, + // 1, + // (1.0 / SLOTS_PER_YEAR) * DEFAULT_SLOTS_PER_EPOCH as f64 + // ) + // .0, + // ); + // // have a look at what a large-ish sysvar would cost, were it a real account... + // eprintln( + // "stake_history: {}kB == {} lamports per epoch", + // crate::sysvar::stake_history::StakeHistory::size_of() / 1024, + // rent.due( + // 0, + // crate::sysvar::stake_history::StakeHistory::size_of(), + // (1.0 / SLOTS_PER_YEAR) * DEFAULT_SLOTS_PER_EPOCH as f64 + // ) + // .0, + // ); + // } } From 1a9b1fbe81bd45344b79c84d591d35cef0f0e99c Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Fri, 23 Aug 2019 10:36:36 -0700 Subject: [PATCH 2/5] nits --- runtime/src/system_instruction_processor.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/runtime/src/system_instruction_processor.rs b/runtime/src/system_instruction_processor.rs index ff7525976552f2..7c5bd64aa29086 100644 --- a/runtime/src/system_instruction_processor.rs +++ b/runtime/src/system_instruction_processor.rs @@ -247,11 +247,8 @@ mod tests { let populated_key = Pubkey::new_rand(); let mut populated_account = Account { - lamports: 0, data: vec![0, 1, 2, 3], - owner: Pubkey::default(), - executable: false, - rent_epoch: 0, + ..Account::default() }; let unchanged_account = populated_account.clone(); From e38c311f161f02e75ba6570c3dce465b5bf9ced1 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Fri, 23 Aug 2019 11:34:08 -0700 Subject: [PATCH 3/5] fixups --- programs/move_loader_api/src/processor.rs | 5 ++--- sdk/src/account.rs | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/programs/move_loader_api/src/processor.rs b/programs/move_loader_api/src/processor.rs index 75af55fafd38ec..62a86daa02f804 100644 --- a/programs/move_loader_api/src/processor.rs +++ b/programs/move_loader_api/src/processor.rs @@ -760,7 +760,7 @@ mod tests { lamports: 1, data: bincode::serialize(&LibraAccountState::create_unallocated()).unwrap(), owner: id(), - executable: false, + ..Account::default() }; Self::new(key, account) } @@ -768,9 +768,8 @@ mod tests { pub fn create_genesis(amount: u64) -> Self { let account = Account { lamports: 1, - data: vec![], owner: id(), - executable: false, + ..Account::default() }; let mut genesis = Self::new(Pubkey::default(), account); genesis.account.data = diff --git a/sdk/src/account.rs b/sdk/src/account.rs index 9ef60a8031ec8b..9a17944d194f09 100644 --- a/sdk/src/account.rs +++ b/sdk/src/account.rs @@ -46,8 +46,7 @@ impl Account { lamports, data: vec![0u8; space], owner: *owner, - executable: false, - rent_epoch: 0, + ..Account::default() } } @@ -61,8 +60,7 @@ impl Account { lamports, data, owner: *owner, - executable: false, - rent_epoch: 0, + ..Account::default() }) } From 1f8104182b8026543fef1b5a3d1b4ab9cbcf2f35 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Fri, 23 Aug 2019 11:59:06 -0700 Subject: [PATCH 4/5] fixups --- core/src/rpc.rs | 6 ++++-- core/src/rpc_pubsub.rs | 4 +++- core/src/rpc_subscriptions.rs | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 62733d5e2927b7..e43edb71883a9f 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -871,7 +871,8 @@ pub mod tests { "owner": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], "lamports": 20, "data": [], - "executable": false + "executable": false, + "rent_epoch": 0 }, "id":1} "#; @@ -903,7 +904,8 @@ pub mod tests { "owner": {:?}, "lamports": 20, "data": [], - "executable": false + "executable": false, + "rent_epoch": 0 }}]], "id":1}} "#, diff --git a/core/src/rpc_pubsub.rs b/core/src/rpc_pubsub.rs index 9ce28bc9da10f8..c3c7c0a6596bfb 100644 --- a/core/src/rpc_pubsub.rs +++ b/core/src/rpc_pubsub.rs @@ -429,7 +429,8 @@ mod tests { "owner": budget_program_id, "lamports": 51, "data": expected_data, - "executable": executable, + "executable": executable, + "rent_epoch": 0, }, "subscription": 0, } @@ -574,6 +575,7 @@ mod tests { "lamports": 100, "data": [], "executable": false, + "rent_epoch": 0, }, "subscription": 0, } diff --git a/core/src/rpc_subscriptions.rs b/core/src/rpc_subscriptions.rs index 5fe5b252bac302..cb7aefa2746e3b 100644 --- a/core/src/rpc_subscriptions.rs +++ b/core/src/rpc_subscriptions.rs @@ -348,7 +348,7 @@ mod tests { subscriptions.check_account(&alice.pubkey(), 0, &bank_forks); let string = transport_receiver.poll(); if let Async::Ready(Some(response)) = string.unwrap() { - let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0]}},"subscription":0}}}}"#); + let expected = format!(r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0],"rent_epoch":0}},"subscription":0}}}}"#); assert_eq!(expected, response); } @@ -403,7 +403,7 @@ mod tests { subscriptions.check_program(&solana_budget_api::id(), 0, &bank_forks); let string = transport_receiver.poll(); if let Async::Ready(Some(response)) = string.unwrap() { - let expected = format!(r#"{{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["{:?}",{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0]}}],"subscription":0}}}}"#, alice.pubkey()); + let expected = format!(r#"{{"jsonrpc":"2.0","method":"programNotification","params":{{"result":["{:?}",{{"data":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"executable":false,"lamports":1,"owner":[2,203,81,223,225,24,34,35,203,214,138,130,144,208,35,77,63,16,87,51,47,198,115,123,98,188,19,160,0,0,0,0],"rent_epoch":0}}],"subscription":0}}}}"#, alice.pubkey()); assert_eq!(expected, response); } From d98536baa7f48b5ed04280ecd17460636e83f324 Mon Sep 17 00:00:00 2001 From: Rob Walker Date: Fri, 23 Aug 2019 12:59:11 -0700 Subject: [PATCH 5/5] fixups --- sdk/src/rent.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sdk/src/rent.rs b/sdk/src/rent.rs index b6999d71c93ce8..5a388a7bc8dd4c 100644 --- a/sdk/src/rent.rs +++ b/sdk/src/rent.rs @@ -68,7 +68,13 @@ mod tests { fn test_due() { let rent = Rent::default(); - assert_eq!(rent.due(0, 1, 1.0), (DEFAULT_LAMPORTS_PER_BYTE_YEAR, false)); + assert_eq!( + rent.due(0, 1, 1.0), + ( + DEFAULT_LAMPORTS_PER_BYTE_YEAR, + DEFAULT_LAMPORTS_PER_BYTE_YEAR == 0 + ) + ); assert_eq!( rent.due( DEFAULT_LAMPORTS_PER_BYTE_YEAR * DEFAULT_EXEMPTION_THRESHOLD as u64,