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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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

View File

@ -669,17 +669,13 @@ impl NonceFull {
let nonce_address = *partial.address();
if *fee_payer_address == nonce_address {
Ok(Self {
address: nonce_address,
account: fee_payer_account,
fee_payer_account: None,
})
Ok(Self::new(nonce_address, fee_payer_account, None))
} else {
Ok(Self {
address: nonce_address,
account: partial.account().clone(),
fee_payer_account: Some(fee_payer_account),
})
Ok(Self::new(
nonce_address,
partial.account().clone(),
Some(fee_payer_account),
))
}
} else {
Err(TransactionError::AccountNotFound)
@ -3935,15 +3931,14 @@ impl Bank {
inner_instructions.push(None);
}
let nonce =
if let Err(TransactionError::InstructionError(_, _)) = &process_result {
let nonce = match &process_result {
Ok(_) => nonce.clone(), // May need to calculate the fee based on the nonce
Err(TransactionError::InstructionError(_, _)) => {
error_counters.instruction_error += 1;
nonce.clone()
} else if process_result.is_err() {
None
} else {
nonce.clone()
};
nonce.clone() // May need to advance the nonce
}
_ => None,
};
(process_result, nonce)
}
@ -4176,8 +4171,8 @@ impl Bank {
&blockhash,
lamports_per_signature,
self.rent_for_sysvars(),
self.merge_nonce_error_into_system_error(),
self.demote_program_write_locks(),
self.leave_nonce_on_success(),
);
let rent_debits = self.collect_rent(executed_results, loaded_txs);
@ -6038,11 +6033,6 @@ impl Bank {
.is_active(&feature_set::verify_tx_signatures_len::id())
}
pub fn merge_nonce_error_into_system_error(&self) -> bool {
self.feature_set
.is_active(&feature_set::merge_nonce_error_into_system_error::id())
}
pub fn versioned_tx_message_enabled(&self) -> bool {
self.feature_set
.is_active(&feature_set::versioned_tx_message_enabled::id())
@ -6058,6 +6048,11 @@ impl Bank {
.is_active(&feature_set::demote_program_write_locks::id())
}
pub fn leave_nonce_on_success(&self) -> bool {
self.feature_set
.is_active(&feature_set::leave_nonce_on_success::id())
}
pub fn stakes_remove_delegation_if_inactive_enabled(&self) -> bool {
self.feature_set
.is_active(&feature_set::stakes_remove_delegation_if_inactive::id())
@ -11367,9 +11362,6 @@ pub(crate) mod tests {
debug!("cust: {:?}", custodian_account);
let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap();
Arc::get_mut(&mut bank)
.unwrap()
.activate_feature(&feature_set::merge_nonce_error_into_system_error::id());
for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {
goto_end_of_slot(Arc::get_mut(&mut bank).unwrap());
bank = Arc::new(new_from_parent(&bank));
@ -11403,7 +11395,7 @@ pub(crate) mod tests {
.get_fee_for_message(&recent_message.try_into().unwrap())
.unwrap()
);
assert_eq!(
assert_ne!(
nonce_hash,
get_nonce_blockhash(&bank, &nonce_pubkey).unwrap()
);

View File

@ -245,6 +245,10 @@ pub mod spl_token_v3_3_0_release {
solana_sdk::declare_id!("Ftok2jhqAqxUWEiCVRrfRs9DPppWP8cgTB7NQNKL88mS");
}
pub mod leave_nonce_on_success {
solana_sdk::declare_id!("E8MkiWZNNPGU6n55jkGzyj8ghUmjCHRmDFdYYFYHxWhQ");
}
lazy_static! {
/// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
@ -301,6 +305,7 @@ lazy_static! {
(reject_deployment_of_unresolved_syscalls::id(), "Reject deployment of programs with unresolved syscall symbols"),
(nonce_must_be_writable::id(), "nonce must be writable"),
(spl_token_v3_3_0_release::id(), "spl-token v3.3.0 release"),
(leave_nonce_on_success::id(), "leave nonce as is on success"),
/*************** ADD NEW FEATURES HERE ***************/
]
.iter()