Cleanup the bank's use of nonces (#21246)

This commit is contained in:
Jack May
2021-12-02 09:57:05 -08:00
committed by GitHub
parent bfdb775ffc
commit 976eb81d4f
3 changed files with 208 additions and 167 deletions

View File

@@ -30,7 +30,7 @@ use solana_sdk::{
hash::Hash,
message::SanitizedMessage,
native_loader,
nonce::{state::Versions as NonceVersions, State as NonceState, NONCED_TX_MARKER_IX_INDEX},
nonce::{state::Versions as NonceVersions, State as NonceState},
pubkey::Pubkey,
system_program, sysvar,
sysvar::instructions::construct_instructions_data,
@@ -1033,8 +1033,8 @@ impl Accounts {
blockhash: &Hash,
lamports_per_signature: u64,
rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
demote_program_write_locks: bool,
leave_nonce_on_success: bool,
) {
let accounts_to_store = self.collect_accounts_to_store(
txs,
@@ -1044,8 +1044,8 @@ impl Accounts {
blockhash,
lamports_per_signature,
rent_for_sysvars,
merge_nonce_error_into_system_error,
demote_program_write_locks,
leave_nonce_on_success,
);
self.accounts_db.store_cached(slot, &accounts_to_store);
}
@@ -1072,8 +1072,8 @@ impl Accounts {
blockhash: &Hash,
lamports_per_signature: u64,
rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
demote_program_write_locks: bool,
leave_nonce_on_success: bool,
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
let mut accounts = Vec::with_capacity(load_results.len());
for (i, ((tx_load_result, _), tx)) in load_results.iter_mut().zip(txs).enumerate() {
@@ -1085,30 +1085,17 @@ impl Accounts {
let (execution_result, nonce) = &execution_results[i];
let maybe_nonce = match (execution_result, nonce) {
(Ok(_), Some(nonce)) => {
let address = nonce.address();
let account = nonce.account();
let maybe_fee_payer_account = nonce.fee_payer_account();
Some((address, account, maybe_fee_payer_account, true))
}
(Err(TransactionError::InstructionError(index, _)), Some(nonce)) => {
let nonce_marker_ix_failed = if merge_nonce_error_into_system_error {
// Don't advance stored blockhash when the nonce marker ix fails
*index == NONCED_TX_MARKER_IX_INDEX
if leave_nonce_on_success {
None
} else {
false
};
let address = nonce.address();
let account = nonce.account();
let maybe_fee_payer_account = nonce.fee_payer_account();
Some((
address,
account,
maybe_fee_payer_account,
!nonce_marker_ix_failed,
))
Some((nonce, false /* rollback */))
}
}
(Ok(_), _nonce) => None,
(Err(_), _nonce) => continue,
(Err(TransactionError::InstructionError(_, _)), Some(nonce)) => {
Some((nonce, true /* rollback */))
}
(Ok(_), _) => None, // Success, don't do any additional nonce processing
(Err(_), _) => continue, // Not nonce, don't store any accounts
};
let message = tx.message();
@@ -1118,49 +1105,39 @@ impl Accounts {
.zip(loaded_transaction.accounts.iter_mut())
.filter(|(i, _)| message.is_non_loader_key(*i))
{
let is_nonce_account = prepare_if_nonce_account(
address,
account,
execution_result,
maybe_nonce,
blockhash,
lamports_per_signature,
);
if fee_payer_index.is_none() {
fee_payer_index = Some(i);
}
let is_fee_payer = Some(i) == fee_payer_index;
if message.is_writable(i, demote_program_write_locks)
&& (execution_result.is_ok()
|| (maybe_nonce.is_some() && (is_nonce_account || is_fee_payer)))
{
if execution_result.is_err() {
match (is_nonce_account, is_fee_payer, maybe_nonce) {
// nonce is fee-payer, state updated in `prepare_if_nonce_account()`
(true, true, Some((_, _, None, _))) => (),
// nonce not fee-payer, state updated in `prepare_if_nonce_account()`
(true, false, Some((_, _, Some(_), _))) => (),
// not nonce, but fee-payer. rollback to cached state
(false, true, Some((_, _, Some(fee_payer_account), _))) => {
*account = fee_payer_account.clone();
}
_ => panic!("unexpected nonce condition"),
}
}
if account.rent_epoch() == INITIAL_RENT_EPOCH {
let rent = rent_collector.collect_from_created_account(
address,
account,
rent_for_sysvars,
);
loaded_transaction.rent += rent;
loaded_transaction
.rent_debits
.insert(address, rent, account.lamports());
}
if message.is_writable(i, demote_program_write_locks) {
let is_nonce_account = prepare_if_nonce_account(
address,
account,
execution_result,
is_fee_payer,
maybe_nonce,
blockhash,
lamports_per_signature,
);
// Add to the accounts to store
accounts.push((&*address, &*account));
if execution_result.is_ok() || is_nonce_account || is_fee_payer {
if account.rent_epoch() == INITIAL_RENT_EPOCH {
let rent = rent_collector.collect_from_created_account(
address,
account,
rent_for_sysvars,
);
loaded_transaction.rent += rent;
loaded_transaction.rent_debits.insert(
address,
rent,
account.lamports(),
);
}
// Add to the accounts to store
accounts.push((&*address, &*account));
}
}
}
}
@@ -1168,52 +1145,59 @@ impl Accounts {
}
}
pub fn prepare_if_nonce_account(
pub fn prepare_if_nonce_account<'a>(
address: &Pubkey,
account: &mut AccountSharedData,
execution_result: &Result<()>,
maybe_nonce: Option<(
&Pubkey,
&AccountSharedData,
Option<&AccountSharedData>,
bool,
)>,
is_fee_payer: bool,
maybe_nonce: Option<(&'a NonceFull, bool)>,
blockhash: &Hash,
lamports_per_signature: u64,
) -> bool {
if let Some((nonce_key, nonce_account, _, advance_blockhash)) = maybe_nonce {
if address == nonce_key {
if execution_result.is_err() {
if let Some((nonce, rollback)) = maybe_nonce {
if address == nonce.address() {
if rollback {
// The transaction failed which would normally drop the account
// processing changes, since this account is now being included in
// the accounts written back to the db, roll it back to
// processing changes, since this account is now being included
// in the accounts written back to the db, roll it back to
// pre-processing state.
*account = nonce_account.clone();
*account = nonce.account().clone();
}
if advance_blockhash {
// Advance the stored blockhash to prevent fee theft by someone
// replaying nonce transactions that have failed with an
// `InstructionError`.
//
// Since we know we are dealing with a valid nonce account, unwrap is safe here
let state = StateMut::<NonceVersions>::state(nonce_account)
.unwrap()
.convert_to_current();
if let NonceState::Initialized(ref data) = state {
account
.set_state(&NonceVersions::new_current(NonceState::new_initialized(
&data.authority,
blockhash,
lamports_per_signature,
)))
.unwrap();
// Advance the stored blockhash to prevent fee theft by someone
// replaying nonce transactions that have failed with an
// `InstructionError`.
//
// Since we know we are dealing with a valid nonce account,
// unwrap is safe here
let state = StateMut::<NonceVersions>::state(nonce.account())
.unwrap()
.convert_to_current();
if let NonceState::Initialized(ref data) = state {
account
.set_state(&NonceVersions::new_current(NonceState::new_initialized(
&data.authority,
blockhash,
lamports_per_signature,
)))
.unwrap();
}
true
} else {
if execution_result.is_err() && is_fee_payer {
if let Some(fee_payer_account) = nonce.fee_payer_account() {
// Instruction error and fee-payer for this nonce tx is not
// the nonce account itself, rollback the fee payer to the
// fee-paid original state.
*account = fee_payer_account.clone();
}
}
return true;
false
}
} else {
false
}
false
}
pub fn create_test_accounts(
@@ -2596,8 +2580,8 @@ mod tests {
&Hash::default(),
0,
true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks
true, // leave_nonce_on_success
);
assert_eq!(collected_accounts.len(), 2);
assert!(collected_accounts
@@ -2722,39 +2706,32 @@ mod tests {
account_address: &Pubkey,
account: &mut AccountSharedData,
tx_result: &Result<()>,
maybe_nonce: Option<(
&Pubkey,
&AccountSharedData,
Option<&AccountSharedData>,
bool,
)>,
is_fee_payer: bool,
maybe_nonce: Option<(&NonceFull, bool)>,
blockhash: &Hash,
lamports_per_signature: u64,
expect_account: &AccountSharedData,
) -> bool {
// Verify expect_account's relationship
match maybe_nonce {
Some((nonce_address, _nonce_account, _maybe_fee_payer_account, _))
if nonce_address == account_address && tx_result.is_ok() =>
{
assert_eq!(expect_account, account) // Account update occurs in system_instruction_processor
if !is_fee_payer {
match maybe_nonce {
Some((nonce, _)) if nonce.address() == account_address => {
assert_ne!(expect_account, nonce.account())
}
_ => assert_eq!(expect_account, account),
}
Some((nonce_address, nonce_account, _maybe_fee_payer_account, _))
if nonce_address == account_address =>
{
assert_ne!(expect_account, nonce_account)
}
_ => assert_eq!(expect_account, account),
}
prepare_if_nonce_account(
account_address,
account,
tx_result,
is_fee_payer,
maybe_nonce,
blockhash,
lamports_per_signature,
);
assert_eq!(expect_account, account);
expect_account == account
}
@@ -2769,22 +2746,25 @@ mod tests {
maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
let post_account_address = pre_account_address;
let nonce = NonceFull::new(
pre_account_address,
pre_account.clone(),
maybe_fee_payer_account,
);
let mut expect_account = post_account.clone();
let data =
NonceVersions::new_current(NonceState::Initialized(nonce::state::Data::default()));
expect_account.set_state(&data).unwrap();
let mut expect_account = pre_account;
expect_account
.set_state(&NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature),
)))
.unwrap();
assert!(run_prepare_if_nonce_account_test(
&post_account_address,
&mut post_account,
&Ok(()),
Some((
&pre_account_address,
&pre_account,
maybe_fee_payer_account.as_ref(),
false,
)),
false,
Some((&nonce, true)),
&blockhash,
lamports_per_signature,
&expect_account,
@@ -2809,6 +2789,7 @@ mod tests {
&post_account_address,
&mut post_account,
&Ok(()),
false,
None,
&blockhash,
lamports_per_signature,
@@ -2827,18 +2808,16 @@ mod tests {
maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
let nonce = NonceFull::new(pre_account_address, pre_account, maybe_fee_payer_account);
let expect_account = post_account.clone();
// Wrong key
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new(&[1u8; 32]),
&mut post_account,
&Ok(()),
Some((
&pre_account_address,
&pre_account,
maybe_fee_payer_account.as_ref(),
true,
)),
false,
Some((&nonce, true)),
&blockhash,
lamports_per_signature,
&expect_account,
@@ -2856,8 +2835,10 @@ mod tests {
maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account();
let post_account_address = pre_account_address;
let mut expect_account = pre_account.clone();
let nonce = NonceFull::new(pre_account_address, pre_account, maybe_fee_payer_account);
expect_account
.set_state(&NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature),
@@ -2871,18 +2852,81 @@ mod tests {
0,
InstructionError::InvalidArgument,
)),
Some((
&pre_account_address,
&pre_account,
maybe_fee_payer_account.as_ref(),
true,
)),
false,
Some((&nonce, true)),
&blockhash,
lamports_per_signature,
&expect_account,
));
}
#[test]
fn test_rollback_nonce_fee_payer() {
let nonce_account = AccountSharedData::new_data(1, &(), &system_program::id()).unwrap();
let pre_fee_payer_account =
AccountSharedData::new_data(42, &(), &system_program::id()).unwrap();
let mut post_fee_payer_account =
AccountSharedData::new_data(84, &[1, 2, 3, 4], &system_program::id()).unwrap();
let nonce = NonceFull::new(
Pubkey::new_unique(),
nonce_account,
Some(pre_fee_payer_account.clone()),
);
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account.clone(),
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
false,
Some((&nonce, true)),
&Hash::default(),
1,
&post_fee_payer_account.clone(),
));
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account.clone(),
&Ok(()),
true,
Some((&nonce, true)),
&Hash::default(),
1,
&post_fee_payer_account.clone(),
));
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account.clone(),
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
true,
None,
&Hash::default(),
1,
&post_fee_payer_account.clone(),
));
assert!(run_prepare_if_nonce_account_test(
&Pubkey::new_unique(),
&mut post_fee_payer_account,
&Err(TransactionError::InstructionError(
0,
InstructionError::InvalidArgument,
)),
true,
Some((&nonce, true)),
&Hash::default(),
1,
&pre_fee_payer_account,
));
}
#[test]
fn test_nonced_failure_accounts_rollback_from_pays() {
let rent_collector = RentCollector::default();
@@ -2966,8 +3010,8 @@ mod tests {
&next_blockhash,
0,
true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks
true, // leave_nonce_on_success
);
assert_eq!(collected_accounts.len(), 2);
assert_eq!(
@@ -3077,8 +3121,8 @@ mod tests {
&next_blockhash,
0,
true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks
true, // leave_nonce_on_success
);
assert_eq!(collected_accounts.len(), 1);
let collected_nonce_account = collected_accounts