use {
crate::{
accounts_db::AccountsDb,
accounts_hash::HashStats,
bank::{Bank, PartitionIndex, Rewrites},
rent_collector::{RentCollector, RentResult},
},
solana_sdk::{
account::{AccountSharedData, ReadableAccount, WritableAccount},
clock::{Epoch, Slot},
epoch_schedule::EpochSchedule,
hash::Hash,
pubkey::Pubkey,
},
std::sync::atomic::Ordering,
};
#[derive(Debug, PartialEq, Eq)]
pub struct ExpectedRentCollection {
partition_from_pubkey: PartitionIndex,
epoch_of_max_storage_slot: Epoch,
partition_index_from_max_slot: PartitionIndex,
first_slot_in_max_epoch: Slot,
expected_rent_collection_slot_max_epoch: Slot,
rent_epoch: Epoch,
}
#[derive(Default, Copy, Clone)]
pub struct SlotInfoInEpoch {
slot: Slot,
epoch_info: Option<SlotInfoInEpochInner>,
}
#[derive(Default, Copy, Clone)]
pub struct SlotInfoInEpochInner {
epoch: Epoch,
partition_index: PartitionIndex,
slots_in_epoch: Slot,
}
impl SlotInfoInEpoch {
pub fn new(slot: Slot, epoch_schedule: &EpochSchedule) -> Self {
let mut result = Self::new_small(slot);
result.epoch_info = Some(result.get_epoch_info(epoch_schedule));
result
}
pub fn new_small(slot: Slot) -> Self {
SlotInfoInEpoch {
slot,
..SlotInfoInEpoch::default()
}
}
pub fn get_epoch_info(&self, epoch_schedule: &EpochSchedule) -> SlotInfoInEpochInner {
if let Some(inner) = &self.epoch_info {
*inner
} else {
let (epoch, partition_index) = epoch_schedule.get_epoch_and_slot_index(self.slot);
SlotInfoInEpochInner {
epoch,
partition_index,
slots_in_epoch: epoch_schedule.get_slots_in_epoch(epoch),
}
}
}
}
impl ExpectedRentCollection {
pub(crate) fn maybe_update_rent_epoch_on_load(
account: &mut AccountSharedData,
storage_slot: &SlotInfoInEpoch,
bank_slot: &SlotInfoInEpoch,
epoch_schedule: &EpochSchedule,
rent_collector: &RentCollector,
pubkey: &Pubkey,
rewrites_skipped_this_slot: &Rewrites,
) {
let result = Self::get_corrected_rent_epoch_on_load(
account,
storage_slot,
bank_slot,
epoch_schedule,
rent_collector,
pubkey,
rewrites_skipped_this_slot,
);
if let Some(rent_epoch) = result {
account.set_rent_epoch(rent_epoch);
}
}
fn get_corrected_rent_epoch_on_load(
account: &AccountSharedData,
storage_slot: &SlotInfoInEpoch,
bank_slot: &SlotInfoInEpoch,
epoch_schedule: &EpochSchedule,
rent_collector: &RentCollector,
pubkey: &Pubkey,
rewrites_skipped_this_slot: &Rewrites,
) -> Option<Epoch> {
let next_epoch = match rent_collector.calculate_rent_result(
pubkey, account, None, false, ) {
RentResult::LeaveAloneNoRent => return None,
RentResult::CollectRent {
new_rent_epoch,
rent_due: 0,
} => new_rent_epoch,
RentResult::CollectRent { .. } => return None,
};
{
let bank_info = bank_slot.get_epoch_info(epoch_schedule);
let (current_epoch, partition_from_current_slot) =
(bank_info.epoch, bank_info.partition_index);
let storage_info = storage_slot.get_epoch_info(epoch_schedule);
let (storage_epoch, storage_slot_partition) =
(storage_info.epoch, storage_info.partition_index);
let partition_from_pubkey =
Bank::partition_from_pubkey(pubkey, bank_info.slots_in_epoch);
let mut possibly_update = true;
if current_epoch == storage_epoch {
if partition_from_pubkey > partition_from_current_slot {
possibly_update = false;
}
} else if current_epoch == storage_epoch + 1 {
if storage_slot_partition >= partition_from_pubkey
&& partition_from_pubkey > partition_from_current_slot
{
possibly_update = false;
}
}
let rewrites_skipped_this_pubkey_this_slot = || {
rewrites_skipped_this_slot
.read()
.unwrap()
.contains_key(pubkey)
};
let rent_epoch = account.rent_epoch();
if possibly_update && rent_epoch == 0 && current_epoch > 1 {
if rewrites_skipped_this_pubkey_this_slot() {
return Some(next_epoch);
} else {
return None;
}
}
if possibly_update && rent_epoch < current_epoch {
let new_rent_epoch = if partition_from_pubkey < partition_from_current_slot
|| (partition_from_pubkey == partition_from_current_slot
&& rewrites_skipped_this_pubkey_this_slot())
{
next_epoch
} else {
next_epoch.saturating_sub(1)
};
if rent_epoch != new_rent_epoch {
return Some(new_rent_epoch);
}
} else if !possibly_update {
assert!(!rewrites_skipped_this_pubkey_this_slot(), "did not update rent_epoch: {}, new value for rent_epoch: {}, old: {}, current epoch: {}", pubkey, rent_epoch, next_epoch, current_epoch);
}
}
None
}
#[allow(clippy::too_many_arguments)]
pub fn maybe_rehash_skipped_rewrite(
loaded_account: &impl ReadableAccount,
loaded_hash: &Hash,
pubkey: &Pubkey,
storage_slot: Slot,
epoch_schedule: &EpochSchedule,
rent_collector: &RentCollector,
stats: &HashStats,
max_slot_in_storages_inclusive: &SlotInfoInEpoch,
find_unskipped_slot: impl Fn(Slot) -> Option<Slot>,
filler_account_suffix: Option<&Pubkey>,
) -> Option<Hash> {
use solana_measure::measure::Measure;
let mut m = Measure::start("rehash_calc_us");
let expected = ExpectedRentCollection::new(
pubkey,
loaded_account,
storage_slot,
epoch_schedule,
rent_collector,
max_slot_in_storages_inclusive,
find_unskipped_slot,
filler_account_suffix,
);
m.stop();
stats.rehash_calc_us.fetch_add(m.as_us(), Ordering::Relaxed);
let expected = match expected {
None => {
return None;
}
Some(expected) => expected,
};
let mut m = Measure::start("rehash_hash_us");
let recalc_hash = AccountsDb::hash_account_with_rent_epoch(
expected.expected_rent_collection_slot_max_epoch,
loaded_account,
pubkey,
expected.rent_epoch,
);
m.stop();
stats.rehash_hash_us.fetch_add(m.as_us(), Ordering::Relaxed);
if &recalc_hash == loaded_hash {
stats.rehash_unnecessary.fetch_add(1, Ordering::Relaxed);
return None;
}
stats.rehash_required.fetch_add(1, Ordering::Relaxed);
Some(recalc_hash)
}
fn new(
pubkey: &Pubkey,
loaded_account: &impl ReadableAccount,
storage_slot: Slot,
epoch_schedule: &EpochSchedule,
rent_collector_max_epoch: &RentCollector,
max_slot_in_storages_inclusive: &SlotInfoInEpoch,
find_unskipped_slot: impl Fn(Slot) -> Option<Slot>,
filler_account_suffix: Option<&Pubkey>,
) -> Option<Self> {
let mut rent_collector = rent_collector_max_epoch;
let SlotInfoInEpochInner {
epoch: epoch_of_max_storage_slot,
partition_index: partition_index_from_max_slot,
slots_in_epoch: slots_per_epoch_max_epoch,
} = max_slot_in_storages_inclusive.get_epoch_info(epoch_schedule);
let mut partition_from_pubkey =
crate::bank::Bank::partition_from_pubkey(pubkey, slots_per_epoch_max_epoch);
let first_slot_in_max_epoch =
max_slot_in_storages_inclusive.slot - partition_index_from_max_slot;
let mut expected_rent_collection_slot_max_epoch =
first_slot_in_max_epoch + partition_from_pubkey;
let calculated_from_index_expected_rent_collection_slot_max_epoch =
expected_rent_collection_slot_max_epoch;
if expected_rent_collection_slot_max_epoch <= max_slot_in_storages_inclusive.slot {
if let Some(find) =
find_unskipped_slot(calculated_from_index_expected_rent_collection_slot_max_epoch)
{
expected_rent_collection_slot_max_epoch = find;
}
}
let mut use_previous_epoch_rent_collector = false;
if expected_rent_collection_slot_max_epoch > max_slot_in_storages_inclusive.slot {
let previous_epoch = epoch_of_max_storage_slot.saturating_sub(1);
let slots_per_epoch_previous_epoch = epoch_schedule.get_slots_in_epoch(previous_epoch);
expected_rent_collection_slot_max_epoch =
if slots_per_epoch_previous_epoch == slots_per_epoch_max_epoch {
calculated_from_index_expected_rent_collection_slot_max_epoch
.saturating_sub(slots_per_epoch_max_epoch)
} else {
partition_from_pubkey = crate::bank::Bank::partition_from_pubkey(
pubkey,
slots_per_epoch_previous_epoch,
);
first_slot_in_max_epoch
.saturating_sub(slots_per_epoch_previous_epoch)
.saturating_add(partition_from_pubkey)
};
if let Some(find) = find_unskipped_slot(expected_rent_collection_slot_max_epoch) {
expected_rent_collection_slot_max_epoch = find;
}
use_previous_epoch_rent_collector = true;
}
if storage_slot > expected_rent_collection_slot_max_epoch
|| loaded_account.rent_epoch() == 0
{
return None;
}
let rent_collector_previous;
if use_previous_epoch_rent_collector {
let mut rent_collector_temp = rent_collector.clone();
rent_collector_temp.epoch = rent_collector.epoch.saturating_sub(1); rent_collector_previous = Some(rent_collector_temp);
rent_collector = rent_collector_previous.as_ref().unwrap();
}
let rent_result = rent_collector.calculate_rent_result(
pubkey,
loaded_account,
filler_account_suffix,
false, );
let current_rent_epoch = loaded_account.rent_epoch();
let new_rent_epoch = match rent_result {
RentResult::CollectRent {
new_rent_epoch: next_epoch,
rent_due,
} => {
if next_epoch > current_rent_epoch && rent_due != 0 {
return None;
}
std::cmp::max(next_epoch, current_rent_epoch)
}
RentResult::LeaveAloneNoRent => {
current_rent_epoch
}
};
if expected_rent_collection_slot_max_epoch == storage_slot
&& new_rent_epoch == loaded_account.rent_epoch()
{
return None;
}
Some(Self {
partition_from_pubkey,
epoch_of_max_storage_slot,
partition_index_from_max_slot,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: new_rent_epoch,
})
}
}
#[cfg(test)]
pub mod tests {
use {
super::*,
solana_sdk::{
account::{AccountSharedData, WritableAccount},
genesis_config::GenesisConfig,
},
};
#[test]
fn test_expected_rent_collection() {
solana_logger::setup();
let pubkey = Pubkey::new(&[5; 32]);
let owner = solana_sdk::pubkey::new_rand();
let mut account = AccountSharedData::new(1, 0, &owner);
let max_slot_in_storages_inclusive = 0;
let epoch_schedule = EpochSchedule::default();
let first_normal_slot = epoch_schedule.first_normal_slot;
let storage_slot = first_normal_slot;
let epoch = epoch_schedule.get_epoch(storage_slot);
assert_eq!(
(epoch, 0),
epoch_schedule.get_epoch_and_slot_index(storage_slot)
);
let genesis_config = GenesisConfig::default();
let mut rent_collector = RentCollector::new(
epoch,
epoch_schedule,
genesis_config.slots_per_year(),
genesis_config.rent,
);
rent_collector.rent.lamports_per_byte_year = 0; let find_unskipped_slot = Some;
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert!(result.is_none());
let slots_per_epoch = 432_000;
assert_eq!(
slots_per_epoch,
epoch_schedule.get_slots_in_epoch(epoch_schedule.get_epoch(storage_slot))
);
let partition_index_max_inclusive = slots_per_epoch - 1;
account.set_rent_epoch(rent_collector.epoch);
let max_slot_in_storages_inclusive = slots_per_epoch * 3 + first_normal_slot - 1;
rent_collector.epoch = epoch_schedule.get_epoch(max_slot_in_storages_inclusive);
let partition_from_pubkey = 8470; let first_slot_in_max_epoch = 1388256;
let expected_rent_collection_slot_max_epoch =
first_slot_in_max_epoch + partition_from_pubkey;
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
result,
Some(ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: rent_collector.epoch,
})
);
for leave_alone in [true, false] {
account.set_executable(leave_alone);
let result = ExpectedRentCollection::new(
&pubkey,
&account,
expected_rent_collection_slot_max_epoch,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
result,
(!leave_alone).then(|| ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: rent_collector.epoch,
}),
"leave_alone: {}",
leave_alone
);
}
for greater in [false, true] {
let result = ExpectedRentCollection::new(
&pubkey,
&account,
expected_rent_collection_slot_max_epoch + if greater { 1 } else { 0 },
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
result,
(!greater).then(|| ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: rent_collector.epoch,
})
);
}
for previous_epoch in [false, true] {
let result = ExpectedRentCollection::new(
&pubkey,
&account,
expected_rent_collection_slot_max_epoch,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(
max_slot_in_storages_inclusive
+ if previous_epoch { slots_per_epoch } else { 0 },
&epoch_schedule,
),
find_unskipped_slot,
None,
);
let epoch_delta = if previous_epoch { 1 } else { 0 };
let slot_delta = epoch_delta * slots_per_epoch;
assert_eq!(
result,
Some(ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch + epoch_delta,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch: first_slot_in_max_epoch + slot_delta,
expected_rent_collection_slot_max_epoch: expected_rent_collection_slot_max_epoch
+ slot_delta,
rent_epoch: rent_collector.epoch,
}),
"previous_epoch: {}",
previous_epoch,
);
}
let original_rent_epoch = account.rent_epoch();
for already_collected in [true, false] {
account.set_rent_epoch(original_rent_epoch + if already_collected { 1 } else { 0 });
let result = ExpectedRentCollection::new(
&pubkey,
&account,
expected_rent_collection_slot_max_epoch,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
result,
Some(ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: std::cmp::max(rent_collector.epoch, account.rent_epoch()),
}),
"rent_collector.epoch: {}, already_collected: {}",
rent_collector.epoch,
already_collected
);
}
account.set_rent_epoch(original_rent_epoch);
let storage_slot = max_slot_in_storages_inclusive - slots_per_epoch;
for end_partition_index in [0, 1, 2, 100, slots_per_epoch - 2, slots_per_epoch - 1] {
let range = crate::bank::Bank::pubkey_range_from_partition((
end_partition_index.saturating_sub(1), end_partition_index,
epoch_schedule.get_slots_in_epoch(rent_collector.epoch),
));
for pubkey in [&range.start(), &range.end()] {
let result = ExpectedRentCollection::new(
pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
result,
Some(ExpectedRentCollection {
partition_from_pubkey: end_partition_index,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch: first_slot_in_max_epoch + end_partition_index,
rent_epoch: rent_collector.epoch,
}),
"range: {:?}, pubkey: {:?}, end_partition_index: {}, max_slot_in_storages_inclusive: {}",
range,
pubkey,
end_partition_index,
max_slot_in_storages_inclusive,
);
}
}
let first_slot_in_max_epoch = first_normal_slot + slots_per_epoch;
rent_collector.epoch = epoch_schedule.get_epoch(first_slot_in_max_epoch);
let storage_slot = first_normal_slot;
for partition_index in [
0,
1,
2,
partition_from_pubkey - 1,
partition_from_pubkey,
partition_from_pubkey + 1,
100,
slots_per_epoch - 2,
slots_per_epoch - 1,
] {
let max_slot_in_storages_inclusive = first_slot_in_max_epoch + partition_index;
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
let partition_index_passed_pubkey = partition_from_pubkey <= partition_index;
let expected_rent_epoch =
rent_collector.epoch - if partition_index_passed_pubkey { 0 } else { 1 };
let expected_rent_collection_slot_max_epoch = first_slot_in_max_epoch
+ partition_from_pubkey
- if partition_index_passed_pubkey {
0
} else {
slots_per_epoch
};
assert_eq!(
result,
Some(ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot: partition_index,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: expected_rent_epoch,
}),
"partition_index: {}, max_slot_in_storages_inclusive: {}, storage_slot: {}, first_normal_slot: {}",
partition_index,
max_slot_in_storages_inclusive,
storage_slot,
first_normal_slot,
);
}
let first_slot_in_max_epoch = 1388256;
for account_rent_epoch in [0, epoch] {
account.set_rent_epoch(account_rent_epoch);
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
result,
(account_rent_epoch != 0).then(|| ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch + 1,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: rent_collector.epoch,
})
);
}
for find_unskipped_slot in [
|_| None,
Some, |slot| Some(slot + 1), |_| Some(Slot::MAX), ] {
let test_value = 10;
let find_result = find_unskipped_slot(test_value);
let increment = find_result.unwrap_or_default() == test_value + 1;
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
let prior_epoch = (partition_from_pubkey > partition_index_max_inclusive)
|| find_unskipped_slot(0) == Some(Slot::MAX);
assert_eq!(
result,
Some(ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch + 1,
partition_index_from_max_slot: partition_index_max_inclusive,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch: if find_result.unwrap_or_default()
== Slot::MAX
{
Slot::MAX
} else if increment {
expected_rent_collection_slot_max_epoch + 1
} else {
expected_rent_collection_slot_max_epoch
},
rent_epoch: rent_collector.epoch - if prior_epoch { 1 } else { 0 },
}),
"find_unskipped_slot(0): {:?}, rent_collector.epoch: {}, prior_epoch: {}",
find_unskipped_slot(0),
rent_collector.epoch,
prior_epoch,
);
}
}
#[test]
fn test_simplified_rent_collection() {
solana_logger::setup();
let pubkey = Pubkey::new(&[5; 32]);
let owner = solana_sdk::pubkey::new_rand();
let mut account = AccountSharedData::new(1, 0, &owner);
let mut epoch_schedule = EpochSchedule {
first_normal_epoch: 0,
..EpochSchedule::default()
};
epoch_schedule.first_normal_slot = 0;
let first_normal_slot = epoch_schedule.first_normal_slot;
let slots_per_epoch = 432_000;
let partition_from_pubkey = 8470; let storage_slot = first_normal_slot + partition_from_pubkey + slots_per_epoch;
let epoch = epoch_schedule.get_epoch(storage_slot);
assert_eq!(
(epoch, partition_from_pubkey),
epoch_schedule.get_epoch_and_slot_index(storage_slot)
);
let genesis_config = GenesisConfig::default();
let mut rent_collector = RentCollector::new(
epoch,
epoch_schedule,
genesis_config.slots_per_year(),
genesis_config.rent,
);
rent_collector.rent.lamports_per_byte_year = 0;
assert_eq!(
slots_per_epoch,
epoch_schedule.get_slots_in_epoch(epoch_schedule.get_epoch(storage_slot))
);
account.set_rent_epoch(1);
for skipped_slot in [false, true] {
let find_unskipped_slot = if skipped_slot {
|slot| Some(slot + 1)
} else {
Some
};
for epoch in 1..4 {
for partition_index_from_max_slot in
partition_from_pubkey - 1..=partition_from_pubkey + 2
{
let max_slot_in_storages_inclusive =
slots_per_epoch * epoch + first_normal_slot + partition_index_from_max_slot;
if storage_slot > max_slot_in_storages_inclusive {
continue; }
rent_collector.epoch = epoch_schedule.get_epoch(max_slot_in_storages_inclusive);
let first_slot_in_max_epoch = max_slot_in_storages_inclusive
- max_slot_in_storages_inclusive % slots_per_epoch;
let skip_offset = if skipped_slot { 1 } else { 0 };
let mut expected_rent_collection_slot_max_epoch =
first_slot_in_max_epoch + partition_from_pubkey + skip_offset;
let hit_this_epoch =
expected_rent_collection_slot_max_epoch <= max_slot_in_storages_inclusive;
if !hit_this_epoch {
expected_rent_collection_slot_max_epoch -= slots_per_epoch;
}
assert_eq!(
(epoch, partition_index_from_max_slot),
epoch_schedule.get_epoch_and_slot_index(max_slot_in_storages_inclusive)
);
assert_eq!(
(epoch, 0),
epoch_schedule.get_epoch_and_slot_index(first_slot_in_max_epoch)
);
account.set_rent_epoch(1);
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
let some_expected = if epoch == 1 {
skipped_slot && partition_index_from_max_slot > partition_from_pubkey
} else if epoch == 2 {
partition_index_from_max_slot >= partition_from_pubkey - skip_offset
} else {
true
};
assert_eq!(
result,
some_expected.then(|| ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: rent_collector.epoch - if hit_this_epoch { 0 } else {1},
}),
"partition_index_from_max_slot: {}, epoch: {}, hit_this_epoch: {}, skipped_slot: {}",
partition_index_from_max_slot,
epoch,
hit_this_epoch,
skipped_slot,
);
{
let result = ExpectedRentCollection::new(
&pubkey,
&account,
storage_slot,
&epoch_schedule,
&rent_collector,
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
Some(&pubkey),
);
assert_eq!(
result,
some_expected.then(|| ExpectedRentCollection {
partition_from_pubkey,
epoch_of_max_storage_slot: rent_collector.epoch,
partition_index_from_max_slot,
first_slot_in_max_epoch,
expected_rent_collection_slot_max_epoch,
rent_epoch: account.rent_epoch(),
}),
"partition_index_from_max_slot: {}, epoch: {}",
partition_index_from_max_slot,
epoch,
);
}
let hash = AccountsDb::hash_account(storage_slot, &account, &pubkey);
let maybe_rehash = ExpectedRentCollection::maybe_rehash_skipped_rewrite(
&account,
&hash,
&pubkey,
storage_slot,
&epoch_schedule,
&rent_collector,
&HashStats::default(),
&SlotInfoInEpoch::new(max_slot_in_storages_inclusive, &epoch_schedule),
find_unskipped_slot,
None,
);
assert_eq!(
maybe_rehash,
some_expected.then(|| {
AccountsDb::hash_account_with_rent_epoch(
result
.as_ref()
.unwrap()
.expected_rent_collection_slot_max_epoch,
&account,
&pubkey,
result.as_ref().unwrap().rent_epoch,
)
})
);
}
}
}
}
#[test]
fn test_get_corrected_rent_epoch_on_load() {
solana_logger::setup();
let pubkey = Pubkey::new(&[5; 32]);
let owner = solana_sdk::pubkey::new_rand();
let mut account = AccountSharedData::new(1, 0, &owner);
let mut epoch_schedule = EpochSchedule {
first_normal_epoch: 0,
..EpochSchedule::default()
};
epoch_schedule.first_normal_slot = 0;
let first_normal_slot = epoch_schedule.first_normal_slot;
let slots_per_epoch = 432_000;
let partition_from_pubkey = 8470; let storage_slot = first_normal_slot + partition_from_pubkey + slots_per_epoch;
let epoch = epoch_schedule.get_epoch(storage_slot);
assert_eq!(
(epoch, partition_from_pubkey),
epoch_schedule.get_epoch_and_slot_index(storage_slot)
);
let genesis_config = GenesisConfig::default();
let mut rent_collector = RentCollector::new(
epoch,
epoch_schedule,
genesis_config.slots_per_year(),
genesis_config.rent,
);
rent_collector.rent.lamports_per_byte_year = 0;
assert_eq!(
slots_per_epoch,
epoch_schedule.get_slots_in_epoch(epoch_schedule.get_epoch(storage_slot))
);
account.set_rent_epoch(1);
for new_small in [false, true] {
for rewrite_already in [false, true] {
for epoch in 1..4 {
for partition_index_bank_slot in
partition_from_pubkey - 1..=partition_from_pubkey + 2
{
let bank_slot =
slots_per_epoch * epoch + first_normal_slot + partition_index_bank_slot;
if storage_slot > bank_slot {
continue; }
rent_collector.epoch = epoch_schedule.get_epoch(bank_slot);
let first_slot_in_max_epoch = bank_slot - bank_slot % slots_per_epoch;
assert_eq!(
(epoch, partition_index_bank_slot),
epoch_schedule.get_epoch_and_slot_index(bank_slot)
);
assert_eq!(
(epoch, 0),
epoch_schedule.get_epoch_and_slot_index(first_slot_in_max_epoch)
);
account.set_rent_epoch(1);
let rewrites = Rewrites::default();
if rewrite_already {
if partition_index_bank_slot != partition_from_pubkey {
continue;
}
rewrites.write().unwrap().insert(pubkey, Hash::default());
}
let expected_new_rent_epoch =
if partition_index_bank_slot > partition_from_pubkey {
if epoch > account.rent_epoch() {
Some(rent_collector.epoch)
} else {
None
}
} else if partition_index_bank_slot == partition_from_pubkey
&& rewrite_already
{
let expected_rent_epoch = rent_collector.epoch;
if expected_rent_epoch == account.rent_epoch() {
None
} else {
Some(expected_rent_epoch)
}
} else if partition_index_bank_slot <= partition_from_pubkey
&& epoch > account.rent_epoch()
{
let expected_rent_epoch = rent_collector.epoch.saturating_sub(1);
if expected_rent_epoch == account.rent_epoch() {
None
} else {
Some(expected_rent_epoch)
}
} else {
None
};
let get_slot_info = |slot| {
if new_small {
SlotInfoInEpoch::new_small(slot)
} else {
SlotInfoInEpoch::new(slot, &epoch_schedule)
}
};
let new_rent_epoch =
ExpectedRentCollection::get_corrected_rent_epoch_on_load(
&account,
&get_slot_info(storage_slot),
&get_slot_info(bank_slot),
&epoch_schedule,
&rent_collector,
&pubkey,
&rewrites,
);
assert_eq!(new_rent_epoch, expected_new_rent_epoch);
}
}
}
}
}
}