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

Commit cb4b35b

Browse files
committed
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
1 parent 5b006eb commit cb4b35b

File tree

4 files changed

+269
-33
lines changed

4 files changed

+269
-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: 33 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,6 +13,7 @@ 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+
use solana_sdk::hash::Hash;
1517
use solana_sdk::message::Message;
1618
use solana_sdk::native_loader;
1719
use solana_sdk::nonce_state::NonceState;
@@ -248,7 +250,7 @@ impl Accounts {
248250
.zip(lock_results.into_iter())
249251
.map(|etx| match etx {
250252
(tx, (Ok(()), hash_age_kind)) => {
251-
let fee_hash = if let Some(HashAgeKind::DurableNonce) = hash_age_kind {
253+
let fee_hash = if let Some(HashAgeKind::DurableNonce(_, _)) = hash_age_kind {
252254
hash_queue.last_hash()
253255
} else {
254256
tx.message().recent_blockhash
@@ -551,9 +553,16 @@ impl Accounts {
551553
res: &[TransactionProcessResult],
552554
loaded: &mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
553555
rent_collector: &RentCollector,
556+
last_blockhash: &Hash,
554557
) {
555-
let accounts_to_store =
556-
self.collect_accounts_to_store(txs, txs_iteration_order, res, loaded, rent_collector);
558+
let accounts_to_store = self.collect_accounts_to_store(
559+
txs,
560+
txs_iteration_order,
561+
res,
562+
loaded,
563+
rent_collector,
564+
last_blockhash,
565+
);
557566
self.accounts_db.store(slot, &accounts_to_store);
558567
}
559568

@@ -574,17 +583,27 @@ impl Accounts {
574583
res: &'a [TransactionProcessResult],
575584
loaded: &'a mut [(Result<TransactionLoadResult>, Option<HashAgeKind>)],
576585
rent_collector: &RentCollector,
586+
last_blockhash: &Hash,
577587
) -> Vec<(&'a Pubkey, &'a Account)> {
578588
let mut accounts = Vec::with_capacity(loaded.len());
579589
for (i, ((raccs, _hash_age_kind), tx)) in loaded
580590
.iter_mut()
581591
.zip(OrderedIterator::new(txs, txs_iteration_order))
582592
.enumerate()
583593
{
584-
let (res, _hash_age_kind) = &res[i];
585-
if res.is_err() || raccs.is_err() {
594+
if raccs.is_err() {
586595
continue;
587596
}
597+
let (res, hash_age_kind) = &res[i];
598+
let maybe_nonce = match (res, hash_age_kind) {
599+
(Ok(_), Some(HashAgeKind::DurableNonce(pubkey, acc))) => Some((pubkey, acc)),
600+
(
601+
Err(TransactionError::InstructionError(_, _)),
602+
Some(HashAgeKind::DurableNonce(pubkey, acc)),
603+
) => Some((pubkey, acc)),
604+
(Ok(_), _hash_age_kind) => None,
605+
(Err(_), _hash_age_kind) => continue,
606+
};
588607

589608
let message = &tx.message();
590609
let acc = raccs.as_mut().unwrap();
@@ -594,6 +613,7 @@ impl Accounts {
594613
.enumerate()
595614
.zip(acc.0.iter_mut())
596615
{
616+
prepare_if_nonce_account(account, key, res, maybe_nonce, last_blockhash);
597617
if message.is_writable(i) {
598618
if account.rent_epoch == 0 {
599619
account.rent_epoch = rent_collector.epoch;
@@ -1593,8 +1613,14 @@ mod tests {
15931613
},
15941614
);
15951615
}
1596-
let collected_accounts =
1597-
accounts.collect_accounts_to_store(&txs, None, &loaders, &mut loaded, &rent_collector);
1616+
let collected_accounts = accounts.collect_accounts_to_store(
1617+
&txs,
1618+
None,
1619+
&loaders,
1620+
&mut loaded,
1621+
&rent_collector,
1622+
&Hash::default(),
1623+
);
15981624
assert_eq!(collected_accounts.len(), 2);
15991625
assert!(collected_accounts
16001626
.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)