Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 7bda879

Browse files
t-nelsonmergify-bot
authored andcommitted
Manage durable nonce stored value in runtime (#7684)
* Bank: Return nonce pubkey/account from `check_tx_durable_nonce` * Forward account with HashAgeKind::DurableNonce * Add durable nonce helper for HashAgeKind * Add nonce util for advancing stored nonce in runtime * Advance nonce in runtime * Store rolled back nonce account on TX InstructionError * nonce: Add test for replayed InstErr fee theft (cherry picked from commit 9754fc7) # Conflicts: # runtime/src/accounts.rs
1 parent 5b006eb commit 7bda879

File tree

4 files changed

+272
-33
lines changed

4 files changed

+272
-33
lines changed

core/src/transaction_status_service.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl TransactionStatusService {
6464
.zip(balances.post_balances)
6565
{
6666
if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
67-
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
67+
let fee_hash = if let Some(HashAgeKind::DurableNonce(_, _)) = hash_age_kind {
6868
bank.last_blockhash()
6969
} else {
7070
transaction.message().recent_blockhash

runtime/src/accounts.rs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::append_vec::StoredAccount;
44
use crate::bank::{HashAgeKind, TransactionProcessResult};
55
use crate::blockhash_queue::BlockhashQueue;
66
use crate::message_processor::has_duplicates;
7+
use crate::nonce_utils::prepare_if_nonce_account;
78
use crate::rent_collector::RentCollector;
89
use crate::system_instruction_processor::{get_system_account_kind, SystemAccountKind};
910
use log::*;
@@ -12,7 +13,11 @@ use solana_metrics::inc_new_counter_error;
1213
use solana_sdk::account::Account;
1314
use solana_sdk::bank_hash::BankHash;
1415
use solana_sdk::clock::Slot;
16+
<<<<<<< HEAD
1517
use solana_sdk::message::Message;
18+
=======
19+
use solana_sdk::hash::Hash;
20+
>>>>>>> 9754fc789... Manage durable nonce stored value in runtime (#7684)
1621
use solana_sdk::native_loader;
1722
use solana_sdk::nonce_state::NonceState;
1823
use solana_sdk::pubkey::Pubkey;
@@ -248,7 +253,7 @@ impl Accounts {
248253
.zip(lock_results.into_iter())
249254
.map(|etx| match etx {
250255
(tx, (Ok(()), hash_age_kind)) => {
251-
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
256+
let fee_hash = if let Some(HashAgeKind::DurableNonce(_, _)) = hash_age_kind {
252257
hash_queue.last_hash()
253258
} else {
254259
tx.message().recent_blockhash
@@ -551,9 +556,16 @@ impl Accounts {
551556
res: &[TransactionProcessResult],
552557
loaded: &mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
553558
rent_collector: &RentCollector,
559+
last_blockhash: &Hash,
554560
) {
555-
let accounts_to_store =
556-
self.collect_accounts_to_store(txs, txs_iteration_order, res, loaded, rent_collector);
561+
let accounts_to_store = self.collect_accounts_to_store(
562+
txs,
563+
txs_iteration_order,
564+
res,
565+
loaded,
566+
rent_collector,
567+
last_blockhash,
568+
);
557569
self.accounts_db.store(slot, &accounts_to_store);
558570
}
559571

@@ -574,17 +586,27 @@ impl Accounts {
574586
res: &'a [TransactionProcessResult],
575587
loaded: &'a mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
576588
rent_collector: &RentCollector,
589+
last_blockhash: &Hash,
577590
) -> Vec<(&'a Pubkey, &'a Account)> {
578591
let mut accounts = Vec::with_capacity(loaded.len());
579592
for (i, ((raccs, _hash_age_kind), tx)) in loaded
580593
.iter_mut()
581594
.zip(OrderedIterator::new(txs, txs_iteration_order))
582595
.enumerate()
583596
{
584-
let (res, _hash_age_kind) = &res[i];
585-
if res.is_err() || raccs.is_err() {
597+
if raccs.is_err() {
586598
continue;
587599
}
600+
let (res, hash_age_kind) = &res[i];
601+
let maybe_nonce = match (res, hash_age_kind) {
602+
(Ok(_), Some(HashAgeKind::DurableNonce(pubkey, acc))) => Some((pubkey, acc)),
603+
(
604+
Err(TransactionError::InstructionError(_, _)),
605+
Some(HashAgeKind::DurableNonce(pubkey, acc)),
606+
) => Some((pubkey, acc)),
607+
(Ok(_), _hash_age_kind) => None,
608+
(Err(_), _hash_age_kind) => continue,
609+
};
588610

589611
let message = &tx.message();
590612
let acc = raccs.as_mut().unwrap();
@@ -594,6 +616,7 @@ impl Accounts {
594616
.enumerate()
595617
.zip(acc.0.iter_mut())
596618
{
619+
prepare_if_nonce_account(account, key, res, maybe_nonce, last_blockhash);
597620
if message.is_writable(i) {
598621
if account.rent_epoch == 0 {
599622
account.rent_epoch = rent_collector.epoch;
@@ -1593,8 +1616,14 @@ mod tests {
15931616
},
15941617
);
15951618
}
1596-
let collected_accounts =
1597-
accounts.collect_accounts_to_store(&txs, None, &loaders, &mut loaded, &rent_collector);
1619+
let collected_accounts = accounts.collect_accounts_to_store(
1620+
&txs,
1621+
None,
1622+
&loaders,
1623+
&mut loaded,
1624+
&rent_collector,
1625+
&Hash::default(),
1626+
);
15981627
assert_eq!(collected_accounts.len(), 2);
15991628
assert!(collected_accounts
16001629
.iter()

runtime/src/bank.rs

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,16 @@ pub type TransactionBalances = Vec<Vec<u64>>;
183183
#[derive(Clone, Debug, Eq, PartialEq)]
184184
pub enum HashAgeKind {
185185
Extant,
186-
DurableNonce,
186+
DurableNonce(Pubkey, Account),
187+
}
188+
189+
impl HashAgeKind {
190+
pub fn is_durable_nonce(&self) -> bool {
191+
match self {
192+
HashAgeKind::DurableNonce(_, _) => true,
193+
_ => false,
194+
}
195+
}
187196
}
188197

189198
/// Manager for the state of all accounts and programs after processing its entries.
@@ -969,8 +978,8 @@ impl Bank {
969978
let message = tx.message();
970979
if hash_queue.check_hash_age(&message.recent_blockhash, max_age) {
971980
(Ok(()), Some(HashAgeKind::Extant))
972-
} else if self.check_tx_durable_nonce(&tx) {
973-
(Ok(()), Some(HashAgeKind::DurableNonce))
981+
} else if let Some((pubkey, acc)) = self.check_tx_durable_nonce(&tx) {
982+
(Ok(()), Some(HashAgeKind::DurableNonce(pubkey, acc)))
974983
} else {
975984
error_counters.reserve_blockhash += 1;
976985
(Err(TransactionError::BlockhashNotFound), None)
@@ -1024,16 +1033,16 @@ impl Bank {
10241033
.check_hash_age(hash, max_age)
10251034
}
10261035

1027-
pub fn check_tx_durable_nonce(&self, tx: &Transaction) -> bool {
1036+
pub fn check_tx_durable_nonce(&self, tx: &Transaction) -> Option<(Pubkey, Account)> {
10281037
nonce_utils::transaction_uses_durable_nonce(&tx)
10291038
.and_then(|nonce_ix| nonce_utils::get_nonce_pubkey_from_instruction(&nonce_ix, &tx))
1030-
.and_then(|nonce_pubkey| self.get_account(&nonce_pubkey))
1031-
.map_or_else(
1032-
|| false,
1033-
|nonce_account| {
1034-
nonce_utils::verify_nonce(&nonce_account, &tx.message().recent_blockhash)
1035-
},
1036-
)
1039+
.and_then(|nonce_pubkey| {
1040+
self.get_account(&nonce_pubkey)
1041+
.map(|acc| (*nonce_pubkey, acc))
1042+
})
1043+
.filter(|(_pubkey, nonce_account)| {
1044+
nonce_utils::verify_nonce(nonce_account, &tx.message().recent_blockhash)
1045+
})
10371046
}
10381047

10391048
pub fn check_transactions(
@@ -1223,7 +1232,11 @@ impl Bank {
12231232
let results = OrderedIterator::new(txs, iteration_order)
12241233
.zip(executed.iter())
12251234
.map(|(tx, (res, hash_age_kind))| {
1226-
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
1235+
let is_durable_nonce = hash_age_kind
1236+
.as_ref()
1237+
.map(|hash_age_kind| hash_age_kind.is_durable_nonce())
1238+
.unwrap_or(false);
1239+
let fee_hash = if is_durable_nonce {
12271240
self.last_blockhash()
12281241
} else {
12291242
tx.message().recent_blockhash
@@ -1239,7 +1252,12 @@ impl Bank {
12391252
// credit the transaction fee even in case of InstructionError
12401253
// necessary to withdraw from account[0] here because previous
12411254
// work of doing so (in accounts.load()) is ignored by store_account()
1242-
self.withdraw(&message.account_keys[0], fee)?;
1255+
//
1256+
// ...except nonce accounts, which will have their post-load,
1257+
// pre-execute account state stored
1258+
if !is_durable_nonce {
1259+
self.withdraw(&message.account_keys[0], fee)?;
1260+
}
12431261
fees += fee;
12441262
Ok(())
12451263
}
@@ -1291,6 +1309,7 @@ impl Bank {
12911309
executed,
12921310
loaded_accounts,
12931311
&self.rent_collector,
1312+
&self.last_blockhash(),
12941313
);
12951314
self.collect_rent(executed, loaded_accounts);
12961315

@@ -1905,6 +1924,14 @@ mod tests {
19051924
use std::{io::Cursor, result, time::Duration};
19061925
use tempfile::TempDir;
19071926

1927+
#[test]
1928+
fn test_hash_age_kind_is_durable_nonce() {
1929+
assert!(
1930+
HashAgeKind::DurableNonce(Pubkey::default(), Account::default()).is_durable_nonce()
1931+
);
1932+
assert!(!HashAgeKind::Extant.is_durable_nonce());
1933+
}
1934+
19081935
#[test]
19091936
fn test_bank_unix_timestamp() {
19101937
let (genesis_config, _mint_keypair) = create_genesis_config(1);
@@ -4713,7 +4740,11 @@ mod tests {
47134740
&[&custodian_keypair, &nonce_keypair],
47144741
nonce_hash,
47154742
);
4716-
assert!(bank.check_tx_durable_nonce(&tx));
4743+
let nonce_account = bank.get_account(&nonce_pubkey).unwrap();
4744+
assert_eq!(
4745+
bank.check_tx_durable_nonce(&tx),
4746+
Some((nonce_pubkey, nonce_account))
4747+
);
47174748
}
47184749

47194750
#[test]
@@ -4733,7 +4764,7 @@ mod tests {
47334764
&[&custodian_keypair, &nonce_keypair],
47344765
nonce_hash,
47354766
);
4736-
assert!(!bank.check_tx_durable_nonce(&tx));
4767+
assert!(bank.check_tx_durable_nonce(&tx).is_none());
47374768
}
47384769

47394770
#[test]
@@ -4754,7 +4785,7 @@ mod tests {
47544785
nonce_hash,
47554786
);
47564787
tx.message.instructions[0].accounts.clear();
4757-
assert!(!bank.check_tx_durable_nonce(&tx));
4788+
assert!(bank.check_tx_durable_nonce(&tx).is_none());
47584789
}
47594790

47604791
#[test]
@@ -4776,7 +4807,7 @@ mod tests {
47764807
&[&custodian_keypair, &nonce_keypair],
47774808
nonce_hash,
47784809
);
4779-
assert!(!bank.check_tx_durable_nonce(&tx));
4810+
assert!(bank.check_tx_durable_nonce(&tx).is_none());
47804811
}
47814812

47824813
#[test]
@@ -4795,7 +4826,7 @@ mod tests {
47954826
&[&custodian_keypair, &nonce_keypair],
47964827
Hash::default(),
47974828
);
4798-
assert!(!bank.check_tx_durable_nonce(&tx));
4829+
assert!(bank.check_tx_durable_nonce(&tx).is_none());
47994830
}
48004831

48014832
#[test]
@@ -4908,10 +4939,11 @@ mod tests {
49084939
bank.process_transaction(&durable_tx),
49094940
Err(TransactionError::BlockhashNotFound)
49104941
);
4911-
/* Check fee not charged */
4942+
/* Check fee not charged and nonce not advanced */
49124943
assert_eq!(bank.get_balance(&custodian_pubkey), 4_640_000);
4944+
assert_eq!(new_nonce, get_nonce(&bank, &nonce_pubkey).unwrap());
49134945

4914-
let nonce_hash = get_nonce(&bank, &nonce_pubkey).unwrap();
4946+
let nonce_hash = new_nonce;
49154947

49164948
/* Kick nonce hash off the blockhash_queue */
49174949
for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {
@@ -4935,8 +4967,16 @@ mod tests {
49354967
system_instruction::SystemError::ResultWithNegativeLamports.into()
49364968
))
49374969
);
4938-
/* Check fee charged */
4970+
/* Check fee charged and nonce has advanced */
49394971
assert_eq!(bank.get_balance(&custodian_pubkey), 4_630_000);
4972+
assert_ne!(nonce_hash, get_nonce(&bank, &nonce_pubkey).unwrap());
4973+
/* Confirm replaying a TX that failed with InstructionError::* now
4974+
* fails with TransactionError::BlockhashNotFound
4975+
*/
4976+
assert_eq!(
4977+
bank.process_transaction(&durable_tx),
4978+
Err(TransactionError::BlockhashNotFound),
4979+
);
49404980
}
49414981

49424982
#[test]

0 commit comments

Comments
 (0)