diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 2458af54d44..f4c731b960f 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -18,6 +18,7 @@ use solana_sdk::{ account::Account, clock::Slot, fee_calculator::FeeCalculator, + genesis_config::OperatingMode, hash::Hash, message::Message, native_loader, nonce, @@ -650,6 +651,7 @@ impl Accounts { loaded: &mut [(Result, Option)], rent_collector: &RentCollector, last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), + operating_mode: OperatingMode, ) { let accounts_to_store = self.collect_accounts_to_store( txs, @@ -658,6 +660,8 @@ impl Accounts { loaded, rent_collector, last_blockhash_with_fee_calculator, + slot, + operating_mode, ); self.accounts_db.store(slot, &accounts_to_store); } @@ -684,6 +688,8 @@ impl Accounts { loaded: &'a mut [(Result, Option)], rent_collector: &RentCollector, last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), + slot: Slot, + operating_mode: OperatingMode, ) -> Vec<(&'a Pubkey, &'a Account)> { let mut accounts = Vec::with_capacity(loaded.len()); for (i, ((raccs, _hash_age_kind), tx)) in loaded @@ -720,6 +726,8 @@ impl Accounts { res, maybe_nonce, last_blockhash_with_fee_calculator, + slot, + operating_mode, ); if message.is_writable(i) { if account.rent_epoch == 0 { @@ -1675,6 +1683,8 @@ mod tests { &mut loaded, &rent_collector, &(Hash::default(), FeeCalculator::default()), + 1, + OperatingMode::Development, ); assert_eq!(collected_accounts.len(), 2); assert!(collected_accounts diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index ba8cf588666..19955937ba9 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -1767,6 +1767,7 @@ impl Bank { loaded_accounts, &self.rent_collector, &self.last_blockhash_with_fee_calculator(), + self.operating_mode(), ); self.collect_rent(executed, loaded_accounts); @@ -6485,6 +6486,22 @@ mod tests { } } + #[test] + fn test_blockhash_queue_sysvar_consistency() { + // Demonstrates need for feature gating on change to nonce update on success case + let (genesis_config, _mint_keypair) = create_genesis_config(100_000); + let mut bank = Arc::new(Bank::new(&genesis_config)); + goto_end_of_slot(Arc::get_mut(&mut bank).unwrap()); + + let bhq_account = bank.get_account(&sysvar::recent_blockhashes::id()).unwrap(); + let recent_blockhashes = + sysvar::recent_blockhashes::RecentBlockhashes::from_account(&bhq_account).unwrap(); + + let sysvar_recent_blockhash = recent_blockhashes[0].blockhash; + let bank_last_blockhash = bank.last_blockhash(); + assert_ne!(sysvar_recent_blockhash, bank_last_blockhash); + } + #[test] fn test_bank_inherit_last_vote_sync() { let (genesis_config, _) = create_genesis_config(500); @@ -6904,6 +6921,7 @@ mod tests { fn test_nonce_fee_calculator_updates() { let (mut genesis_config, mint_keypair) = create_genesis_config(1_000_000); genesis_config.rent.lamports_per_byte_year = 0; + genesis_config.operating_mode = OperatingMode::Development; let mut bank = Arc::new(Bank::new(&genesis_config)); // Deliberately use bank 0 to initialize nonce account, so that nonce account fee_calculator indicates 0 fees diff --git a/runtime/src/nonce_utils.rs b/runtime/src/nonce_utils.rs index 4fc813e8368..4dc94fabd69 100644 --- a/runtime/src/nonce_utils.rs +++ b/runtime/src/nonce_utils.rs @@ -1,7 +1,9 @@ use solana_sdk::{ account::Account, account_utils::StateMut, + clock::Slot, fee_calculator::FeeCalculator, + genesis_config::OperatingMode, hash::Hash, instruction::CompiledInstruction, nonce::{self, state::Versions, State}, @@ -52,13 +54,35 @@ pub fn prepare_if_nonce_account( tx_result: &transaction::Result<()>, maybe_nonce: Option<(&Pubkey, &Account)>, last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), + slot: Slot, + operating_mode: OperatingMode, ) { if let Some((nonce_key, nonce_acc)) = maybe_nonce { if account_pubkey == nonce_key { // Nonce TX failed with an InstructionError. Roll back // its account state - if tx_result.is_err() { - *account = nonce_acc.clone(); + + if slot > get_fix_nonce_overwrite_slot(operating_mode) { + if tx_result.is_err() { + *account = nonce_acc.clone(); + // Since hash_age_kind is DurableNonce, unwrap is safe here + let state = StateMut::::state(nonce_acc) + .unwrap() + .convert_to_current(); + if let State::Initialized(ref data) = state { + let new_data = + Versions::new_current(State::Initialized(nonce::state::Data { + blockhash: last_blockhash_with_fee_calculator.0, + fee_calculator: last_blockhash_with_fee_calculator.1.clone(), + ..data.clone() + })); + account.set_state(&new_data).unwrap(); + } + } + } else { + if tx_result.is_err() { + *account = nonce_acc.clone() + } // Since hash_age_kind is DurableNonce, unwrap is safe here let state = StateMut::::state(nonce_acc) .unwrap() @@ -66,7 +90,6 @@ pub fn prepare_if_nonce_account( if let State::Initialized(ref data) = state { let new_data = Versions::new_current(State::Initialized(nonce::state::Data { blockhash: last_blockhash_with_fee_calculator.0, - fee_calculator: last_blockhash_with_fee_calculator.1.clone(), ..data.clone() })); account.set_state(&new_data).unwrap(); @@ -86,6 +109,14 @@ pub fn fee_calculator_of(account: &Account) -> Option { } } +fn get_fix_nonce_overwrite_slot(operating_mode: OperatingMode) -> Slot { + match operating_mode { + OperatingMode::Development => 0, + OperatingMode::Stable => std::u64::MAX / 2, + OperatingMode::Preview => std::u64::MAX / 2, + } +} + #[cfg(test)] mod tests { use super::*; @@ -294,6 +325,8 @@ mod tests { tx_result, maybe_nonce, last_blockhash_with_fee_calculator, + 1, + OperatingMode::Development, ); expect_account == account }