Remove sysvar special cases for rent and assign
This commit is contained in:
parent
fd574dcb3b
commit
f029af0fca
|
@ -1138,6 +1138,7 @@ mod tests {
|
|||
programs: vec![],
|
||||
accounts: vec![],
|
||||
sysvars: vec![],
|
||||
feature_active: false,
|
||||
};
|
||||
assert_eq!(
|
||||
Err(InstructionError::ProgramFailedToComplete),
|
||||
|
|
|
@ -210,6 +210,8 @@ impl Accounts {
|
|||
let mut account_deps = Vec::with_capacity(message.account_keys.len());
|
||||
let mut key_check = MessageProgramIdsCache::new(message);
|
||||
let mut rent_debits = RentDebits::default();
|
||||
let rent_for_sysvars = feature_set.is_active(&feature_set::rent_for_sysvars::id());
|
||||
|
||||
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() {
|
||||
|
@ -229,8 +231,11 @@ impl Accounts {
|
|||
.load_with_fixed_root(ancestors, key)
|
||||
.map(|(mut account, _)| {
|
||||
if message.is_writable(i) {
|
||||
let rent_due = rent_collector
|
||||
.collect_from_existing_account(key, &mut account);
|
||||
let rent_due = rent_collector.collect_from_existing_account(
|
||||
key,
|
||||
&mut account,
|
||||
rent_for_sysvars,
|
||||
);
|
||||
(account, rent_due)
|
||||
} else {
|
||||
(account, 0)
|
||||
|
@ -912,6 +917,7 @@ impl Accounts {
|
|||
rent_collector: &RentCollector,
|
||||
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||
fix_recent_blockhashes_sysvar_delay: bool,
|
||||
rent_for_sysvars: bool,
|
||||
) {
|
||||
let accounts_to_store = self.collect_accounts_to_store(
|
||||
txs,
|
||||
|
@ -920,6 +926,7 @@ impl Accounts {
|
|||
rent_collector,
|
||||
last_blockhash_with_fee_calculator,
|
||||
fix_recent_blockhashes_sysvar_delay,
|
||||
rent_for_sysvars,
|
||||
);
|
||||
self.accounts_db.store_cached(slot, &accounts_to_store);
|
||||
}
|
||||
|
@ -944,6 +951,7 @@ impl Accounts {
|
|||
rent_collector: &RentCollector,
|
||||
last_blockhash_with_fee_calculator: &(Hash, FeeCalculator),
|
||||
fix_recent_blockhashes_sysvar_delay: bool,
|
||||
rent_for_sysvars: bool,
|
||||
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
|
||||
let mut accounts = Vec::with_capacity(loaded.len());
|
||||
for (i, ((raccs, _nonce_rollback), tx)) in loaded.iter_mut().zip(txs).enumerate() {
|
||||
|
@ -1005,7 +1013,11 @@ impl Accounts {
|
|||
}
|
||||
}
|
||||
if account.rent_epoch() == INITIAL_RENT_EPOCH {
|
||||
let rent = rent_collector.collect_from_created_account(key, account);
|
||||
let rent = rent_collector.collect_from_created_account(
|
||||
key,
|
||||
account,
|
||||
rent_for_sysvars,
|
||||
);
|
||||
loaded_transaction.rent += rent;
|
||||
loaded_transaction
|
||||
.rent_debits
|
||||
|
@ -2001,6 +2013,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
&(Hash::default(), FeeCalculator::default()),
|
||||
true,
|
||||
true,
|
||||
);
|
||||
assert_eq!(collected_accounts.len(), 2);
|
||||
assert!(collected_accounts
|
||||
|
@ -2377,6 +2390,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
&(next_blockhash, FeeCalculator::default()),
|
||||
true,
|
||||
true,
|
||||
);
|
||||
assert_eq!(collected_accounts.len(), 2);
|
||||
assert_eq!(
|
||||
|
@ -2493,6 +2507,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
&(next_blockhash, FeeCalculator::default()),
|
||||
true,
|
||||
true,
|
||||
);
|
||||
assert_eq!(collected_accounts.len(), 1);
|
||||
let collected_nonce_account = collected_accounts
|
||||
|
|
|
@ -1517,8 +1517,27 @@ impl Bank {
|
|||
where
|
||||
F: Fn(&Option<AccountSharedData>) -> AccountSharedData,
|
||||
{
|
||||
let old_account = self.get_sysvar_account_with_fixed_root(pubkey);
|
||||
let new_account = updater(&old_account);
|
||||
let old_account = if !self.rent_for_sysvars() {
|
||||
// This old behavior is being retired for simpler reasoning for the benefits of all.
|
||||
// Specifically, get_sysvar_account_with_fixed_root() doesn't work nicely with eager
|
||||
// rent collection, which becomes applicable for sysvars after rent_for_sysvars
|
||||
// activation. That's because get_sysvar_account_with_fixed_root() called by both
|
||||
// update_slot_history() and update_recent_blockhashes() ignores any updates
|
||||
// done by eager rent collection in this slot.
|
||||
// Also, it turned out that get_sysvar_account_with_fixed_root()'s special
|
||||
// behavior (idempotent) isn't needed to begin with, because we're fairly certain that
|
||||
// we don't call new_from_parent() with same child slot multiple times in the
|
||||
// production code...
|
||||
self.get_sysvar_account_with_fixed_root(pubkey)
|
||||
} else {
|
||||
self.get_account_with_fixed_root(pubkey)
|
||||
};
|
||||
let mut new_account = updater(&old_account);
|
||||
|
||||
if self.rent_for_sysvars() {
|
||||
// always re-calculate for possible data size change
|
||||
self.adjust_sysvar_balance_for_rent(&mut new_account);
|
||||
}
|
||||
|
||||
self.store_account_and_update_capitalization(pubkey, &new_account);
|
||||
}
|
||||
|
@ -1529,7 +1548,16 @@ impl Bank {
|
|||
) -> InheritableAccountFields {
|
||||
(
|
||||
old_account.as_ref().map(|a| a.lamports()).unwrap_or(1),
|
||||
INITIAL_RENT_EPOCH,
|
||||
if !self.rent_for_sysvars() {
|
||||
INITIAL_RENT_EPOCH
|
||||
} else {
|
||||
// start to inherit rent_epoch updated by rent collection to be consistent with
|
||||
// other normal accounts
|
||||
old_account
|
||||
.as_ref()
|
||||
.map(|a| a.rent_epoch())
|
||||
.unwrap_or(INITIAL_RENT_EPOCH)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3407,6 +3435,7 @@ impl Bank {
|
|||
&self.rent_collector,
|
||||
&self.last_blockhash_with_fee_calculator(),
|
||||
self.fix_recent_blockhashes_sysvar_delay(),
|
||||
self.rent_for_sysvars(),
|
||||
);
|
||||
let rent_debits = self.collect_rent(executed, loaded_txs);
|
||||
|
||||
|
@ -3678,12 +3707,15 @@ impl Bank {
|
|||
let account_count = accounts.len();
|
||||
|
||||
// parallelize?
|
||||
let rent_for_sysvars = self.rent_for_sysvars();
|
||||
let mut total_rent = 0;
|
||||
let mut rent_debits = RentDebits::default();
|
||||
for (pubkey, mut account) in accounts {
|
||||
let rent = self
|
||||
.rent_collector
|
||||
.collect_from_existing_account(&pubkey, &mut account);
|
||||
let rent = self.rent_collector.collect_from_existing_account(
|
||||
&pubkey,
|
||||
&mut account,
|
||||
rent_for_sysvars,
|
||||
);
|
||||
total_rent += rent;
|
||||
// Store all of them unconditionally to purge old AppendVec,
|
||||
// even if collected rent is 0 (= not updated).
|
||||
|
@ -4182,16 +4214,31 @@ impl Bank {
|
|||
if let Some(old_account) = self.get_account_with_fixed_root(pubkey) {
|
||||
match new_account.lamports().cmp(&old_account.lamports()) {
|
||||
std::cmp::Ordering::Greater => {
|
||||
self.capitalization
|
||||
.fetch_add(new_account.lamports() - old_account.lamports(), Relaxed);
|
||||
let increased = new_account.lamports() - old_account.lamports();
|
||||
trace!(
|
||||
"store_account_and_update_capitalization: increased: {} {}",
|
||||
pubkey,
|
||||
increased
|
||||
);
|
||||
self.capitalization.fetch_add(increased, Relaxed);
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
self.capitalization
|
||||
.fetch_sub(old_account.lamports() - new_account.lamports(), Relaxed);
|
||||
let decreased = old_account.lamports() - new_account.lamports();
|
||||
trace!(
|
||||
"store_account_and_update_capitalization: decreased: {} {}",
|
||||
pubkey,
|
||||
decreased
|
||||
);
|
||||
self.capitalization.fetch_sub(decreased, Relaxed);
|
||||
}
|
||||
std::cmp::Ordering::Equal => {}
|
||||
}
|
||||
} else {
|
||||
trace!(
|
||||
"store_account_and_update_capitalization: created: {} {}",
|
||||
pubkey,
|
||||
new_account.lamports()
|
||||
);
|
||||
self.capitalization
|
||||
.fetch_add(new_account.lamports(), Relaxed);
|
||||
}
|
||||
|
@ -4335,6 +4382,8 @@ impl Bank {
|
|||
// multiple times with the same parent_slot in the case of forking.
|
||||
//
|
||||
// Generally, all of sysvar update granularity should be slot boundaries.
|
||||
//
|
||||
// This behavior is deprecated... See comment in update_sysvar_account() for details
|
||||
fn get_sysvar_account_with_fixed_root(&self, pubkey: &Pubkey) -> Option<AccountSharedData> {
|
||||
let mut ancestors = self.ancestors.clone();
|
||||
ancestors.remove(&self.slot());
|
||||
|
@ -5088,6 +5137,14 @@ impl Bank {
|
|||
|
||||
self.rewrite_stakes();
|
||||
}
|
||||
if new_feature_activations.contains(&feature_set::rent_for_sysvars::id()) {
|
||||
// when this feature is activated, immediately all of existing sysvars are susceptible
|
||||
// to rent collection and account data removal due to insufficient balance due to only
|
||||
// having 1 lamport.
|
||||
// so before any is accessed, reset the balance to be rent-exempt here at the same
|
||||
// timing when perpetual balance adjustment is started in update_sysvar_account().
|
||||
self.reset_all_sysvar_balances();
|
||||
}
|
||||
|
||||
if !debug_do_not_add_builtins {
|
||||
self.ensure_feature_builtins(init_finish_or_warp, &new_feature_activations);
|
||||
|
@ -5096,6 +5153,41 @@ impl Bank {
|
|||
self.ensure_no_storage_rewards_pool();
|
||||
}
|
||||
|
||||
fn reset_all_sysvar_balances(&self) {
|
||||
for sysvar_id in &[
|
||||
sysvar::clock::id(),
|
||||
sysvar::epoch_schedule::id(),
|
||||
sysvar::fees::id(),
|
||||
sysvar::recent_blockhashes::id(),
|
||||
sysvar::rent::id(),
|
||||
sysvar::rewards::id(),
|
||||
sysvar::slot_hashes::id(),
|
||||
sysvar::slot_history::id(),
|
||||
sysvar::stake_history::id(),
|
||||
] {
|
||||
if let Some(mut account) = self.get_account(sysvar_id) {
|
||||
let (old_data_len, old_lamports) = (account.data().len(), account.lamports());
|
||||
self.adjust_sysvar_balance_for_rent(&mut account);
|
||||
info!(
|
||||
"reset_all_sysvar_balances (slot: {}): {} ({} bytes) is reset from {} to {}",
|
||||
self.slot(),
|
||||
sysvar_id,
|
||||
old_data_len,
|
||||
old_lamports,
|
||||
account.lamports()
|
||||
);
|
||||
self.store_account_and_update_capitalization(sysvar_id, &account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_sysvar_balance_for_rent(&self, account: &mut AccountSharedData) {
|
||||
account.set_lamports(
|
||||
self.get_minimum_balance_for_rent_exemption(account.data().len())
|
||||
.max(account.lamports()),
|
||||
);
|
||||
}
|
||||
|
||||
// Compute the active feature set based on the current bank state, and return the set of newly activated features
|
||||
fn compute_active_feature_set(&mut self, allow_new_activations: bool) -> HashSet<Pubkey> {
|
||||
let mut active = self.feature_set.active.clone();
|
||||
|
@ -5275,6 +5367,11 @@ impl Bank {
|
|||
.is_active(&feature_set::consistent_recent_blockhashes_sysvar::id()),
|
||||
}
|
||||
}
|
||||
|
||||
fn rent_for_sysvars(&self) -> bool {
|
||||
self.feature_set
|
||||
.is_active(&feature_set::rent_for_sysvars::id())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Bank {
|
||||
|
@ -5887,6 +5984,19 @@ pub(crate) mod tests {
|
|||
assert_eq!(bank.capitalization(), bank.calculate_capitalization(true));
|
||||
}
|
||||
|
||||
fn assert_capitalization_diff_with_new_bank(
|
||||
bank: &Bank,
|
||||
updater: impl Fn() -> Bank,
|
||||
asserter: impl Fn(u64, u64),
|
||||
) -> Bank {
|
||||
let old = bank.capitalization();
|
||||
let bank = updater();
|
||||
let new = bank.capitalization();
|
||||
asserter(old, new);
|
||||
assert_eq!(bank.capitalization(), bank.calculate_capitalization());
|
||||
bank
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_account_and_update_capitalization_missing() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(0);
|
||||
|
@ -6174,7 +6284,9 @@ pub(crate) mod tests {
|
|||
|
||||
let current_capitalization = bank.capitalization.load(Relaxed);
|
||||
|
||||
let sysvar_and_native_proram_delta = 1;
|
||||
// only slot history is newly created
|
||||
let sysvar_and_native_proram_delta =
|
||||
min_rent_excempt_balance_for_sysvars(&bank, &[sysvar::slot_history::id()]);
|
||||
assert_eq!(
|
||||
previous_capitalization - (current_capitalization - sysvar_and_native_proram_delta),
|
||||
burned_portion
|
||||
|
@ -7086,6 +7198,11 @@ pub(crate) mod tests {
|
|||
.map(|(slot, _)| *slot)
|
||||
.collect::<Vec<Slot>>()
|
||||
}
|
||||
|
||||
fn first_slot_in_next_epoch(&self) -> Slot {
|
||||
self.epoch_schedule()
|
||||
.get_first_slot_in_epoch(self.epoch() + 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -8657,7 +8774,7 @@ pub(crate) mod tests {
|
|||
let (mut genesis_config, _mint_keypair) = create_genesis_config(500);
|
||||
|
||||
let expected_previous_slot = 3;
|
||||
let expected_next_slot = expected_previous_slot + 1;
|
||||
let mut expected_next_slot = expected_previous_slot + 1;
|
||||
|
||||
// First, initialize the clock sysvar
|
||||
activate_all_features(&mut genesis_config);
|
||||
|
@ -8688,7 +8805,10 @@ pub(crate) mod tests {
|
|||
assert_eq!(dummy_rent_epoch, current_account.rent_epoch());
|
||||
},
|
||||
|old, new| {
|
||||
assert_eq!(old + 1, new);
|
||||
assert_eq!(
|
||||
old + min_rent_excempt_balance_for_sysvars(&bank1, &[sysvar::clock::id()]),
|
||||
new
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -8696,7 +8816,7 @@ pub(crate) mod tests {
|
|||
&bank1,
|
||||
|| {
|
||||
bank1.update_sysvar_account(&dummy_clock_id, |optional_account| {
|
||||
assert!(optional_account.is_none());
|
||||
assert!(optional_account.is_some());
|
||||
|
||||
create_account(
|
||||
&Clock {
|
||||
|
@ -8737,7 +8857,9 @@ pub(crate) mod tests {
|
|||
expected_next_slot,
|
||||
from_account::<Clock, _>(¤t_account).unwrap().slot
|
||||
);
|
||||
assert_eq!(INITIAL_RENT_EPOCH, current_account.rent_epoch());
|
||||
// inherit_specially_retained_account_fields() now starts to inherit rent_epoch too
|
||||
// with rent_for_sysvars
|
||||
assert_eq!(dummy_rent_epoch, current_account.rent_epoch());
|
||||
},
|
||||
|old, new| {
|
||||
// if existing, capitalization shouldn't change
|
||||
|
@ -8745,8 +8867,9 @@ pub(crate) mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
// Updating again should give bank1's sysvar to the closure not bank2's.
|
||||
// Thus, assert with same expected_next_slot as previously
|
||||
// Updating again should give bank2's sysvar to the closure not bank1's.
|
||||
// Thus, increment expected_next_slot accordingly
|
||||
expected_next_slot += 1;
|
||||
assert_capitalization_diff(
|
||||
&bank2,
|
||||
|| {
|
||||
|
@ -11599,6 +11722,253 @@ pub(crate) mod tests {
|
|||
bank.store_account(vote_pubkey, &vote_account);
|
||||
}
|
||||
|
||||
fn min_rent_excempt_balance_for_sysvars(bank: &Bank, sysvar_ids: &[Pubkey]) -> u64 {
|
||||
sysvar_ids
|
||||
.iter()
|
||||
.map(|sysvar_id| {
|
||||
trace!("min_rent_excempt_balance_for_sysvars: {}", sysvar_id);
|
||||
bank.get_minimum_balance_for_rent_exemption(
|
||||
bank.get_account(sysvar_id).unwrap().data().len(),
|
||||
)
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn expected_cap_delta_after_sysvar_reset(bank: &Bank, sysvar_ids: &[Pubkey]) -> u64 {
|
||||
min_rent_excempt_balance_for_sysvars(bank, sysvar_ids) - sysvar_ids.len() as u64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_adjust_sysvar_balance_for_rent() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(0);
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let mut sample_sysvar = bank.get_account(&sysvar::clock::id()).unwrap();
|
||||
assert_eq!(sample_sysvar.lamports(), 1);
|
||||
bank.adjust_sysvar_balance_for_rent(&mut sample_sysvar);
|
||||
let rent_exempt_balance_for_sysvar_clock = 1169280;
|
||||
assert_eq!(
|
||||
sample_sysvar.lamports(),
|
||||
rent_exempt_balance_for_sysvar_clock
|
||||
);
|
||||
|
||||
// excess lamports shouldn't be reduced by adjust_sysvar_balance_for_rent()
|
||||
let excess_lamports = 9_999_999;
|
||||
sample_sysvar.set_lamports(excess_lamports);
|
||||
bank.adjust_sysvar_balance_for_rent(&mut sample_sysvar);
|
||||
assert_eq!(sample_sysvar.lamports(), excess_lamports);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_deletion_due_to_rent_upon_rent_for_sysvar_activation() {
|
||||
solana_logger::setup();
|
||||
|
||||
let (mut genesis_config, _mint_keypair) = create_genesis_config(0);
|
||||
let feature_balance =
|
||||
std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1);
|
||||
|
||||
// activate all features but rent_for_sysvars
|
||||
activate_all_features(&mut genesis_config);
|
||||
genesis_config
|
||||
.accounts
|
||||
.remove(&feature_set::rent_for_sysvars::id());
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let bank1 = Arc::new(new_from_parent(&Arc::new(bank0)));
|
||||
|
||||
// schedule activation of simple capitalization
|
||||
bank1.store_account_and_update_capitalization(
|
||||
&feature_set::rent_for_sysvars::id(),
|
||||
&feature::create_account(&Feature { activated_at: None }, feature_balance),
|
||||
);
|
||||
|
||||
let bank2 =
|
||||
Bank::new_from_parent(&bank1, &Pubkey::default(), bank1.first_slot_in_next_epoch());
|
||||
assert_eq!(bank2.get_program_accounts(&sysvar::id()).len(), 8);
|
||||
|
||||
// force rent collection for sysvars
|
||||
bank2.collect_rent_in_partition((0, 0, 1)); // all range
|
||||
|
||||
// no sysvar should be deleted due to rent
|
||||
assert_eq!(bank2.get_program_accounts(&sysvar::id()).len(), 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rent_for_sysvars_adjustment_minimum_genesis_set() {
|
||||
solana_logger::setup();
|
||||
|
||||
let (mut genesis_config, _mint_keypair) = create_genesis_config(0);
|
||||
let feature_balance =
|
||||
std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1);
|
||||
|
||||
// inhibit deprecated rewards sysvar creation altogether
|
||||
genesis_config.accounts.insert(
|
||||
feature_set::deprecate_rewards_sysvar::id(),
|
||||
Account::from(feature::create_account(
|
||||
&Feature {
|
||||
activated_at: Some(0),
|
||||
},
|
||||
feature_balance,
|
||||
)),
|
||||
);
|
||||
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let bank1 = Arc::new(new_from_parent(&Arc::new(bank0)));
|
||||
|
||||
// schedule activation of rent_for_sysvars
|
||||
bank1.store_account_and_update_capitalization(
|
||||
&feature_set::rent_for_sysvars::id(),
|
||||
&feature::create_account(&Feature { activated_at: None }, feature_balance),
|
||||
);
|
||||
|
||||
{
|
||||
let sysvars = bank1.get_program_accounts(&sysvar::id());
|
||||
assert_eq!(sysvars.len(), 8);
|
||||
assert!(sysvars
|
||||
.iter()
|
||||
.map(|(_pubkey, account)| account.lamports())
|
||||
.all(|l| l == 1));
|
||||
}
|
||||
|
||||
// 8 sysvars should be reset by reset_all_sysvar_balances()
|
||||
let bank2 = assert_capitalization_diff_with_new_bank(
|
||||
&bank1,
|
||||
|| Bank::new_from_parent(&bank1, &Pubkey::default(), bank1.first_slot_in_next_epoch()),
|
||||
|old, new| {
|
||||
assert_eq!(
|
||||
old + expected_cap_delta_after_sysvar_reset(
|
||||
&bank1,
|
||||
&[
|
||||
sysvar::clock::id(),
|
||||
sysvar::epoch_schedule::id(),
|
||||
sysvar::fees::id(),
|
||||
sysvar::recent_blockhashes::id(),
|
||||
sysvar::rent::id(),
|
||||
sysvar::slot_hashes::id(),
|
||||
sysvar::slot_history::id(),
|
||||
sysvar::stake_history::id(),
|
||||
]
|
||||
),
|
||||
new
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
{
|
||||
let sysvars = bank2.get_program_accounts(&sysvar::id());
|
||||
assert_eq!(sysvars.len(), 8);
|
||||
assert!(sysvars
|
||||
.iter()
|
||||
.map(|(_pubkey, account)| account.lamports())
|
||||
.all(|l| l > 1));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rent_for_sysvars_adjustment_full_set() {
|
||||
solana_logger::setup();
|
||||
|
||||
let (mut genesis_config, _mint_keypair) = create_genesis_config(0);
|
||||
let feature_balance =
|
||||
std::cmp::max(genesis_config.rent.minimum_balance(Feature::size_of()), 1);
|
||||
|
||||
// activate all features but rent_for_sysvars
|
||||
activate_all_features(&mut genesis_config);
|
||||
genesis_config
|
||||
.accounts
|
||||
.remove(&feature_set::rent_for_sysvars::id());
|
||||
// intentionally create deprecated rewards sysvar creation
|
||||
genesis_config
|
||||
.accounts
|
||||
.remove(&feature_set::deprecate_rewards_sysvar::id());
|
||||
|
||||
// intentionally create bogus native programs
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn mock_process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
_data: &[u8],
|
||||
_invoke_context: &mut dyn InvokeContext,
|
||||
) -> std::result::Result<(), solana_sdk::instruction::InstructionError> {
|
||||
Ok(())
|
||||
}
|
||||
let builtins = Builtins {
|
||||
genesis_builtins: vec![
|
||||
Builtin::new(
|
||||
"mock bpf",
|
||||
solana_sdk::bpf_loader::id(),
|
||||
mock_process_instruction,
|
||||
),
|
||||
Builtin::new(
|
||||
"mock bpf",
|
||||
solana_sdk::bpf_loader_deprecated::id(),
|
||||
mock_process_instruction,
|
||||
),
|
||||
],
|
||||
feature_builtins: (vec![]),
|
||||
};
|
||||
|
||||
let bank0 = Arc::new(Bank::new_with_paths(
|
||||
&genesis_config,
|
||||
Vec::new(),
|
||||
&[],
|
||||
None,
|
||||
Some(&builtins),
|
||||
AccountSecondaryIndexes::default(),
|
||||
false,
|
||||
));
|
||||
// move to next epoch to create now deprecated rewards sysvar intentionally
|
||||
let bank1 = Arc::new(Bank::new_from_parent(
|
||||
&bank0,
|
||||
&Pubkey::default(),
|
||||
bank0.first_slot_in_next_epoch(),
|
||||
));
|
||||
|
||||
// schedule activation of simple capitalization
|
||||
bank1.store_account_and_update_capitalization(
|
||||
&feature_set::rent_for_sysvars::id(),
|
||||
&feature::create_account(&Feature { activated_at: None }, feature_balance),
|
||||
);
|
||||
{
|
||||
let sysvars = bank1.get_program_accounts(&sysvar::id());
|
||||
assert_eq!(sysvars.len(), 9);
|
||||
assert!(sysvars
|
||||
.iter()
|
||||
.map(|(_pubkey, account)| account.lamports())
|
||||
.all(|l| l == 1));
|
||||
}
|
||||
|
||||
// 9 sysvars should be reset by reset_all_sysvar_balances()
|
||||
let bank2 = assert_capitalization_diff_with_new_bank(
|
||||
&bank1,
|
||||
|| Bank::new_from_parent(&bank1, &Pubkey::default(), bank1.first_slot_in_next_epoch()),
|
||||
|old, new| {
|
||||
assert_eq!(
|
||||
old + expected_cap_delta_after_sysvar_reset(
|
||||
&bank1,
|
||||
&[
|
||||
sysvar::clock::id(),
|
||||
sysvar::epoch_schedule::id(),
|
||||
sysvar::fees::id(),
|
||||
sysvar::recent_blockhashes::id(),
|
||||
sysvar::rent::id(),
|
||||
sysvar::rewards::id(),
|
||||
sysvar::slot_hashes::id(),
|
||||
sysvar::slot_history::id(),
|
||||
sysvar::stake_history::id(),
|
||||
]
|
||||
),
|
||||
new
|
||||
)
|
||||
},
|
||||
);
|
||||
{
|
||||
let sysvars = bank2.get_program_accounts(&sysvar::id());
|
||||
assert_eq!(sysvars.len(), 9);
|
||||
assert!(sysvars
|
||||
.iter()
|
||||
.map(|(_pubkey, account)| account.lamports())
|
||||
.all(|l| l > 1));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_clock_timestamp() {
|
||||
let leader_pubkey = solana_sdk::pubkey::new_rand();
|
||||
|
|
|
@ -60,10 +60,11 @@ impl RentCollector {
|
|||
&self,
|
||||
address: &Pubkey,
|
||||
account: &mut AccountSharedData,
|
||||
rent_for_sysvars: bool,
|
||||
) -> u64 {
|
||||
if account.executable() // executable accounts must be rent-exempt balance
|
||||
|| account.rent_epoch() > self.epoch
|
||||
|| sysvar::check_id(account.owner())
|
||||
|| (!rent_for_sysvars && sysvar::check_id(&account.owner()))
|
||||
|| *address == incinerator::id()
|
||||
{
|
||||
0
|
||||
|
@ -115,10 +116,11 @@ impl RentCollector {
|
|||
&self,
|
||||
address: &Pubkey,
|
||||
account: &mut AccountSharedData,
|
||||
rent_for_sysvars: bool,
|
||||
) -> u64 {
|
||||
// initialize rent_epoch as created at this epoch
|
||||
account.set_rent_epoch(self.epoch);
|
||||
self.collect_from_existing_account(address, account)
|
||||
self.collect_from_existing_account(address, account, rent_for_sysvars)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,15 +148,21 @@ mod tests {
|
|||
let rent_collector = RentCollector::default().clone_with_epoch(new_epoch);
|
||||
|
||||
// collect rent on a newly-created account
|
||||
let collected = rent_collector
|
||||
.collect_from_created_account(&solana_sdk::pubkey::new_rand(), &mut created_account);
|
||||
let collected = rent_collector.collect_from_created_account(
|
||||
&solana_sdk::pubkey::new_rand(),
|
||||
&mut created_account,
|
||||
true,
|
||||
);
|
||||
assert!(created_account.lamports() < old_lamports);
|
||||
assert_eq!(created_account.lamports() + collected, old_lamports);
|
||||
assert_ne!(created_account.rent_epoch(), old_epoch);
|
||||
|
||||
// collect rent on a already-existing account
|
||||
let collected = rent_collector
|
||||
.collect_from_existing_account(&solana_sdk::pubkey::new_rand(), &mut existing_account);
|
||||
let collected = rent_collector.collect_from_existing_account(
|
||||
&solana_sdk::pubkey::new_rand(),
|
||||
&mut existing_account,
|
||||
true,
|
||||
);
|
||||
assert!(existing_account.lamports() < old_lamports);
|
||||
assert_eq!(existing_account.lamports() + collected, old_lamports);
|
||||
assert_ne!(existing_account.rent_epoch(), old_epoch);
|
||||
|
@ -180,7 +188,7 @@ mod tests {
|
|||
let rent_collector = RentCollector::default().clone_with_epoch(epoch);
|
||||
|
||||
// first mark account as being collected while being rent-exempt
|
||||
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account);
|
||||
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true);
|
||||
assert_eq!(account.lamports(), huge_lamports);
|
||||
assert_eq!(collected, 0);
|
||||
|
||||
|
@ -188,8 +196,33 @@ mod tests {
|
|||
account.set_lamports(tiny_lamports);
|
||||
|
||||
// ... and trigger another rent collection on the same epoch and check that rent is working
|
||||
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account);
|
||||
collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true);
|
||||
assert_eq!(account.lamports(), tiny_lamports - collected);
|
||||
assert_ne!(collected, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rent_exempt_sysvar() {
|
||||
let tiny_lamports = 1;
|
||||
let mut account = AccountSharedData::default();
|
||||
account.set_owner(sysvar::id());
|
||||
account.set_lamports(tiny_lamports);
|
||||
|
||||
let pubkey = solana_sdk::pubkey::new_rand();
|
||||
|
||||
assert_eq!(account.rent_epoch(), 0);
|
||||
|
||||
let epoch = 3;
|
||||
let rent_collector = RentCollector::default().clone_with_epoch(epoch);
|
||||
|
||||
// old behavior: sysvars are special-cased
|
||||
let collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, false);
|
||||
assert_eq!(account.lamports(), tiny_lamports);
|
||||
assert_eq!(collected, 0);
|
||||
|
||||
// new behavior: sysvars are NOT special-cased
|
||||
let collected = rent_collector.collect_from_existing_account(&pubkey, &mut account, true);
|
||||
assert_eq!(account.lamports(), 0);
|
||||
assert_eq!(collected, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,8 +120,13 @@ fn assign(
|
|||
return Err(InstructionError::MissingRequiredSignature);
|
||||
}
|
||||
|
||||
// guard against sysvars being made
|
||||
if sysvar::check_id(owner) {
|
||||
// bpf programs are allowed to do this; so this is inconsistent...
|
||||
// Thus, we're starting to remove this restricition from system instruction
|
||||
// processor for consistency and fewer special casing by piggybacking onto
|
||||
// the related feature gate..
|
||||
let rent_for_sysvars = invoke_context.is_feature_active(&feature_set::rent_for_sysvars::id());
|
||||
if !rent_for_sysvars && sysvar::check_id(owner) {
|
||||
// guard against sysvars being made
|
||||
ic_msg!(invoke_context, "Assign: cannot assign to sysvar, {}", owner);
|
||||
return Err(SystemError::InvalidProgramId.into());
|
||||
}
|
||||
|
@ -890,7 +895,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_sysvar_invalid_id() {
|
||||
fn test_create_sysvar_invalid_id_with_feature() {
|
||||
// Attempt to create system account in account already owned by another program
|
||||
let from = solana_sdk::pubkey::new_rand();
|
||||
let from_account = AccountSharedData::new_ref(100, 0, &system_program::id());
|
||||
|
@ -912,7 +917,34 @@ mod tests {
|
|||
&signers,
|
||||
&MockInvokeContext::new(vec![]),
|
||||
);
|
||||
assert_eq!(result, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_sysvar_invalid_id_without_feature() {
|
||||
// Attempt to create system account in account already owned by another program
|
||||
let from = solana_sdk::pubkey::new_rand();
|
||||
let from_account = AccountSharedData::new_ref(100, 0, &system_program::id());
|
||||
|
||||
let to = solana_sdk::pubkey::new_rand();
|
||||
let to_account = AccountSharedData::new_ref(0, 0, &system_program::id());
|
||||
|
||||
let signers = [from, to].iter().cloned().collect::<HashSet<_>>();
|
||||
let to_address = to.into();
|
||||
|
||||
let result = create_account(
|
||||
&KeyedAccount::new(&from, true, &from_account),
|
||||
&KeyedAccount::new(&to, false, &to_account),
|
||||
&to_address,
|
||||
50,
|
||||
2,
|
||||
&sysvar::id(),
|
||||
&signers,
|
||||
&MockInvokeContext {
|
||||
feature_active: false,
|
||||
..MockInvokeContext::new(vec![])
|
||||
},
|
||||
);
|
||||
assert_eq!(result, Err(SystemError::InvalidProgramId.into()));
|
||||
}
|
||||
|
||||
|
@ -1025,7 +1057,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_assign_to_sysvar() {
|
||||
fn test_assign_to_sysvar_with_feature() {
|
||||
let new_owner = sysvar::id();
|
||||
|
||||
let from = solana_sdk::pubkey::new_rand();
|
||||
|
@ -1039,6 +1071,28 @@ mod tests {
|
|||
&[from].iter().cloned().collect::<HashSet<_>>(),
|
||||
&MockInvokeContext::new(vec![]),
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assign_to_sysvar_without_feature() {
|
||||
let new_owner = sysvar::id();
|
||||
|
||||
let from = solana_sdk::pubkey::new_rand();
|
||||
let mut from_account = AccountSharedData::new(100, 0, &system_program::id());
|
||||
|
||||
assert_eq!(
|
||||
assign(
|
||||
&mut from_account,
|
||||
&from.into(),
|
||||
&new_owner,
|
||||
&[from].iter().cloned().collect::<HashSet<_>>(),
|
||||
&MockInvokeContext {
|
||||
feature_active: false,
|
||||
..MockInvokeContext::new(vec![])
|
||||
},
|
||||
),
|
||||
Err(SystemError::InvalidProgramId.into())
|
||||
);
|
||||
}
|
||||
|
|
|
@ -163,6 +163,10 @@ pub mod neon_evm_compute_budget {
|
|||
solana_sdk::declare_id!("GLrVvDPkQi5PMYUrsYWT9doZhSHr1BVZXqj5DbFps3rS");
|
||||
}
|
||||
|
||||
pub mod rent_for_sysvars {
|
||||
solana_sdk::declare_id!("BKCPBQQBZqggVnFso5nQ8rQ4RwwogYwjuUt9biBjxwNF");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
|
@ -203,6 +207,7 @@ lazy_static! {
|
|||
(vote_stake_checked_instructions::id(), "vote/state program checked instructions #18345"),
|
||||
(updated_verify_policy::id(), "Update verify policy"),
|
||||
(neon_evm_compute_budget::id(), "bump neon_evm's compute budget"),
|
||||
(rent_for_sysvars::id(), "collect rent from accounts owned by sysvars"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
|
|
@ -334,6 +334,7 @@ pub struct MockInvokeContext<'a> {
|
|||
pub programs: Vec<(Pubkey, ProcessInstructionWithContext)>,
|
||||
pub accounts: Vec<(Pubkey, Rc<RefCell<AccountSharedData>>)>,
|
||||
pub sysvars: Vec<(Pubkey, Option<Rc<Vec<u8>>>)>,
|
||||
pub feature_active: bool,
|
||||
}
|
||||
impl<'a> MockInvokeContext<'a> {
|
||||
pub fn new(keyed_accounts: Vec<KeyedAccount<'a>>) -> Self {
|
||||
|
@ -348,6 +349,7 @@ impl<'a> MockInvokeContext<'a> {
|
|||
programs: vec![],
|
||||
accounts: vec![],
|
||||
sysvars: vec![],
|
||||
feature_active: true,
|
||||
};
|
||||
invoke_context
|
||||
.invoke_stack
|
||||
|
@ -442,7 +444,7 @@ impl<'a> InvokeContext for MockInvokeContext<'a> {
|
|||
}
|
||||
fn record_instruction(&self, _instruction: &Instruction) {}
|
||||
fn is_feature_active(&self, _feature_id: &Pubkey) -> bool {
|
||||
true
|
||||
self.feature_active
|
||||
}
|
||||
fn get_account(&self, pubkey: &Pubkey) -> Option<Rc<RefCell<AccountSharedData>>> {
|
||||
for (key, account) in self.accounts.iter() {
|
||||
|
|
Loading…
Reference in New Issue