diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 601d4c3884..7e0c1fdcc6 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -5,7 +5,8 @@ use crate::{ accounts_index::{AccountSecondaryIndexes, IndexKey}, ancestors::Ancestors, bank::{ - NonceRollbackFull, NonceRollbackInfo, TransactionCheckResult, TransactionExecutionResult, + NonceRollbackFull, NonceRollbackInfo, RentDebits, TransactionCheckResult, + TransactionExecutionResult, }, blockhash_queue::BlockhashQueue, rent_collector::RentCollector, @@ -99,12 +100,14 @@ pub type TransactionAccounts = Vec; pub type TransactionAccountDeps = Vec<(Pubkey, AccountSharedData)>; pub type TransactionRent = u64; pub type TransactionLoaders = Vec>; +pub type TransactionRentDebits = RentDebits; #[derive(PartialEq, Debug, Clone)] pub struct LoadedTransaction { pub accounts: TransactionAccounts, pub account_deps: TransactionAccountDeps, pub loaders: TransactionLoaders, pub rent: TransactionRent, + pub rent_debits: TransactionRentDebits, } pub type TransactionLoadResult = (Result, Option); @@ -206,6 +209,7 @@ impl Accounts { let demote_sysvar_write_locks = feature_set.is_active(&feature_set::demote_sysvar_write_locks::id()); let mut key_check = MessageProgramIdsCache::new(&message); + let mut rent_debits = RentDebits::default(); for (i, key) in message.account_keys.iter().enumerate() { let account = if key_check.is_non_loader_key(key, i) { if payer_index.is_none() { @@ -258,6 +262,8 @@ impl Accounts { } tx_rent += rent; + rent_debits.push(key, rent, account.lamports()); + account } } else { @@ -319,6 +325,7 @@ impl Accounts { account_deps, loaders, rent: tx_rent, + rent_debits, }) } } @@ -995,8 +1002,11 @@ impl Accounts { } } if account.rent_epoch() == INITIAL_RENT_EPOCH { - loaded_transaction.rent += - rent_collector.collect_from_created_account(&key, account); + let rent = rent_collector.collect_from_created_account(&key, account); + loaded_transaction.rent += rent; + loaded_transaction + .rent_debits + .push(key, rent, account.lamports()); } accounts.push((key, &*account)); } @@ -1968,6 +1978,7 @@ mod tests { account_deps: vec![], loaders: transaction_loaders0, rent: transaction_rent0, + rent_debits: RentDebits::default(), }), None, ); @@ -1981,6 +1992,7 @@ mod tests { account_deps: vec![], loaders: transaction_loaders1, rent: transaction_rent1, + rent_debits: RentDebits::default(), }), None, ); @@ -2363,6 +2375,7 @@ mod tests { account_deps: vec![], loaders: transaction_loaders, rent: transaction_rent, + rent_debits: RentDebits::default(), }), nonce_rollback, ); @@ -2481,6 +2494,7 @@ mod tests { account_deps: vec![], loaders: transaction_loaders, rent: transaction_rent, + rent_debits: RentDebits::default(), }), nonce_rollback, ); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index cf50ab5f2a..ef542bd5ea 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -131,6 +131,27 @@ pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0; pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5; +#[derive(Clone, Debug, Default, PartialEq)] +pub struct RentDebits(pub Vec<(Pubkey, RewardInfo)>); + +impl RentDebits { + pub fn push(&mut self, account: &Pubkey, rent: u64, post_balance: u64) { + if rent != 0 { + let rent_debit = i64::try_from(rent).ok().and_then(|r| r.checked_neg()); + if let Some(rent_debit) = rent_debit { + let reward_info = RewardInfo { + reward_type: RewardType::Rent, + lamports: rent_debit, + post_balance, + }; + self.0.push((*account, reward_info)); + } else { + warn!("out of range rent debit from {}: {}", account, rent); + } + } + } +} + #[derive(Default, Debug)] pub struct ExecuteTimings { pub check_us: u64, @@ -3547,18 +3568,22 @@ impl Bank { fn collect_rent( &self, res: &[TransactionExecutionResult], - loaded_accounts: &[TransactionLoadResult], + loaded_accounts: &mut [TransactionLoadResult], ) { let mut collected_rent: u64 = 0; - for (i, (raccs, _nonce_rollback)) in loaded_accounts.iter().enumerate() { + for (i, (raccs, _nonce_rollback)) in loaded_accounts.iter_mut().enumerate() { let (res, _nonce_rollback) = &res[i]; if res.is_err() || raccs.is_err() { continue; } - let loaded_transaction = raccs.as_ref().unwrap(); + let loaded_transaction = raccs.as_mut().unwrap(); collected_rent += loaded_transaction.rent; + self.rewards + .write() + .unwrap() + .append(&mut loaded_transaction.rent_debits.0); } self.collected_rent.fetch_add(collected_rent, Relaxed); @@ -3630,19 +3655,23 @@ impl Bank { let account_count = accounts.len(); // parallelize? - let mut rent = 0; + let mut total_rent = 0; + let mut rent_debits = RentDebits::default(); for (pubkey, mut account) in accounts { - rent += self + let rent = self .rent_collector .collect_from_existing_account(&pubkey, &mut account); + total_rent += rent; // Store all of them unconditionally to purge old AppendVec, // even if collected rent is 0 (= not updated). // Also, there's another subtle side-effect from this: this // ensures we verify the whole on-chain state (= all accounts) // via the account delta hash slowly once per an epoch. self.store_account(&pubkey, &account); + rent_debits.push(&pubkey, rent, account.lamports()); } - self.collected_rent.fetch_add(rent, Relaxed); + self.collected_rent.fetch_add(total_rent, Relaxed); + self.rewards.write().unwrap().append(&mut rent_debits.0); datapoint_info!("collect_rent_eagerly", ("accounts", account_count, i64)); } @@ -6093,13 +6122,17 @@ pub(crate) mod tests { .unwrap() .iter() .map(|(address, reward)| { - assert_eq!(reward.reward_type, RewardType::Rent); - if *address == validator_2_pubkey { - assert_eq!(reward.post_balance, validator_2_portion + 42 - tweak_2); - } else if *address == validator_3_pubkey { - assert_eq!(reward.post_balance, validator_3_portion + 42); + if reward.lamports > 0 { + assert_eq!(reward.reward_type, RewardType::Rent); + if *address == validator_2_pubkey { + assert_eq!(reward.post_balance, validator_2_portion + 42 - tweak_2); + } else if *address == validator_3_pubkey { + assert_eq!(reward.post_balance, validator_3_portion + 42); + } + reward.lamports as u64 + } else { + 0 } - reward.lamports as u64 }) .sum::() );