Fix bad rent in Bank::deposit as if since epoch 0 (#10468)

* Fix bad rent in Bank::deposit as if since epoch 0

* Remove redundant predicate

* Rename

* Start to add tests with some cleanup

* Forgot to add refactor code...

* Enchance test

* Really fix rent timing in deposit with robust test

* Simplify new behavior by disabling rent altogether
This commit is contained in:
Ryo Onodera
2020-08-12 00:04:32 +09:00
committed by GitHub
parent 2910fd467f
commit 6c242f3fec
5 changed files with 120 additions and 22 deletions

View File

@ -139,8 +139,9 @@ impl Accounts {
let (account, rent) =
AccountsDB::load(storage, ancestors, accounts_index, key)
.map(|(mut account, _)| {
if message.is_writable(i) && !account.executable {
let rent_due = rent_collector.update(&key, &mut account);
if message.is_writable(i) {
let rent_due = rent_collector
.collect_from_existing_account(&key, &mut account);
(account, rent_due)
} else {
(account, 0)
@ -735,8 +736,7 @@ impl Accounts {
);
if message.is_writable(i) {
if account.rent_epoch == 0 {
account.rent_epoch = rent_collector.epoch;
acc.2 += rent_collector.update(&key, account);
acc.2 += rent_collector.collect_from_created_account(&key, account);
}
accounts.push((key, &*account));
}

View File

@ -1163,18 +1163,13 @@ impl Bank {
.unwrap()
.genesis_hash(&genesis_config.hash(), &self.fee_calculator);
self.hashes_per_tick = genesis_config.poh_config.hashes_per_tick;
self.ticks_per_slot = genesis_config.ticks_per_slot;
self.ns_per_slot = genesis_config.poh_config.target_tick_duration.as_nanos()
* genesis_config.ticks_per_slot as u128;
self.hashes_per_tick = genesis_config.hashes_per_tick();
self.ticks_per_slot = genesis_config.ticks_per_slot();
self.ns_per_slot = genesis_config.ns_per_slot();
self.genesis_creation_time = genesis_config.creation_time;
self.unused = genesis_config.unused;
self.max_tick_height = (self.slot + 1) * self.ticks_per_slot;
self.slots_per_year = years_as_slots(
1.0,
&genesis_config.poh_config.target_tick_duration,
self.ticks_per_slot,
);
self.slots_per_year = genesis_config.slots_per_year();
self.epoch_schedule = genesis_config.epoch_schedule;
@ -2108,7 +2103,9 @@ impl Bank {
// parallelize?
let mut rent = 0;
for (pubkey, mut account) in accounts {
rent += self.rent_collector.update(&pubkey, &mut account);
rent += self
.rent_collector
.collect_from_existing_account(&pubkey, &mut account);
// Store all of them unconditionally to purge old AppendVec,
// even if collected rent is 0 (= not updated).
self.store_account(&pubkey, &account);
@ -2545,10 +2542,25 @@ 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(pubkey, &mut account),
Ordering::Relaxed,
);
let should_be_in_new_behavior = match self.operating_mode() {
OperatingMode::Development => true,
OperatingMode::Preview => self.epoch() >= Epoch::max_value(),
OperatingMode::Stable => self.epoch() >= Epoch::max_value(),
};
// don't collect rents if we're in the new behavior;
// in genral, it's not worthwhile to account for rents outside the runtime (transactions)
// there are too many and subtly nuanced modification codepaths
if !should_be_in_new_behavior {
// previously we're too much collecting rents as if it existed since epoch 0...
self.collected_rent.fetch_add(
self.rent_collector
.collect_from_existing_account(pubkey, &mut account),
Ordering::Relaxed,
);
}
account.lamports += lamports;
self.store_account(pubkey, &account);
}

View File

@ -1,10 +1,10 @@
//! calculate and collect rent from Accounts
use solana_sdk::{
account::Account, clock::Epoch, epoch_schedule::EpochSchedule, incinerator, pubkey::Pubkey,
rent::Rent, sysvar,
account::Account, clock::Epoch, epoch_schedule::EpochSchedule, genesis_config::GenesisConfig,
incinerator, pubkey::Pubkey, rent::Rent, sysvar,
};
#[derive(Default, Serialize, Deserialize, Clone, PartialEq, Debug, AbiExample)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, AbiExample)]
pub struct RentCollector {
pub epoch: Epoch,
pub epoch_schedule: EpochSchedule,
@ -12,6 +12,18 @@ pub struct RentCollector {
pub rent: Rent,
}
impl Default for RentCollector {
fn default() -> Self {
Self {
epoch: Epoch::default(),
epoch_schedule: EpochSchedule::default(),
// derive default value using GenesisConfig::default()
slots_per_year: GenesisConfig::default().slots_per_year(),
rent: Rent::default(),
}
}
}
impl RentCollector {
pub fn new(
epoch: Epoch,
@ -36,7 +48,8 @@ impl RentCollector {
// updates this account's lamports and status and returns
// the account rent collected, if any
//
pub fn update(&self, address: &Pubkey, account: &mut Account) -> u64 {
#[must_use = "add to Bank::collected_rent"]
pub fn collect_from_existing_account(&self, address: &Pubkey, account: &mut Account) -> u64 {
if account.executable
|| account.rent_epoch > self.epoch
|| sysvar::check_id(&account.owner)
@ -75,4 +88,48 @@ impl RentCollector {
}
}
}
#[must_use = "add to Bank::collected_rent"]
pub fn collect_from_created_account(&self, address: &Pubkey, account: &mut Account) -> u64 {
// initialize rent_epoch as created at this epoch
account.rent_epoch = self.epoch;
self.collect_from_existing_account(address, account)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collect_from_account_created_and_existing() {
let old_lamports = 1000;
let old_epoch = 1;
let new_epoch = 3;
let (mut created_account, mut existing_account) = {
let mut account = Account::default();
account.lamports = old_lamports;
account.rent_epoch = old_epoch;
(account.clone(), account)
};
let rent_collector = RentCollector::default().clone_with_epoch(new_epoch);
let collected =
rent_collector.collect_from_created_account(&Pubkey::new_rand(), &mut created_account);
assert!(created_account.lamports < old_lamports);
assert_eq!(created_account.lamports + collected, old_lamports);
assert_ne!(created_account.rent_epoch, old_epoch);
let collected = rent_collector
.collect_from_existing_account(&Pubkey::new_rand(), &mut existing_account);
assert!(existing_account.lamports < old_lamports);
assert_eq!(existing_account.lamports + collected, old_lamports);
assert_ne!(existing_account.rent_epoch, old_epoch);
assert!(created_account.lamports > existing_account.lamports);
assert_eq!(created_account.rent_epoch, existing_account.rent_epoch);
}
}