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, hash::Hash,
message::SanitizedMessage, message::SanitizedMessage,
native_loader, 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, pubkey::Pubkey,
system_program, sysvar, system_program, sysvar,
sysvar::instructions::construct_instructions_data, sysvar::instructions::construct_instructions_data,
@ -1033,8 +1033,8 @@ impl Accounts {
blockhash: &Hash, blockhash: &Hash,
lamports_per_signature: u64, lamports_per_signature: u64,
rent_for_sysvars: bool, rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
demote_program_write_locks: bool, demote_program_write_locks: bool,
leave_nonce_on_success: bool,
) { ) {
let accounts_to_store = self.collect_accounts_to_store( let accounts_to_store = self.collect_accounts_to_store(
txs, txs,
@ -1044,8 +1044,8 @@ impl Accounts {
blockhash, blockhash,
lamports_per_signature, lamports_per_signature,
rent_for_sysvars, rent_for_sysvars,
merge_nonce_error_into_system_error,
demote_program_write_locks, demote_program_write_locks,
leave_nonce_on_success,
); );
self.accounts_db.store_cached(slot, &accounts_to_store); self.accounts_db.store_cached(slot, &accounts_to_store);
} }
@ -1072,8 +1072,8 @@ impl Accounts {
blockhash: &Hash, blockhash: &Hash,
lamports_per_signature: u64, lamports_per_signature: u64,
rent_for_sysvars: bool, rent_for_sysvars: bool,
merge_nonce_error_into_system_error: bool,
demote_program_write_locks: bool, demote_program_write_locks: bool,
leave_nonce_on_success: bool,
) -> Vec<(&'a Pubkey, &'a AccountSharedData)> { ) -> Vec<(&'a Pubkey, &'a AccountSharedData)> {
let mut accounts = Vec::with_capacity(load_results.len()); let mut accounts = Vec::with_capacity(load_results.len());
for (i, ((tx_load_result, _), tx)) in load_results.iter_mut().zip(txs).enumerate() { 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 (execution_result, nonce) = &execution_results[i];
let maybe_nonce = match (execution_result, nonce) { let maybe_nonce = match (execution_result, nonce) {
(Ok(_), Some(nonce)) => { (Ok(_), Some(nonce)) => {
let address = nonce.address(); if leave_nonce_on_success {
let account = nonce.account(); None
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
} else { } else {
false Some((nonce, false /* rollback */))
};
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,
))
} }
(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(); let message = tx.message();
@ -1118,35 +1105,22 @@ impl Accounts {
.zip(loaded_transaction.accounts.iter_mut()) .zip(loaded_transaction.accounts.iter_mut())
.filter(|(i, _)| message.is_non_loader_key(*i)) .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() { if fee_payer_index.is_none() {
fee_payer_index = Some(i); fee_payer_index = Some(i);
} }
let is_fee_payer = Some(i) == fee_payer_index; let is_fee_payer = Some(i) == fee_payer_index;
if message.is_writable(i, demote_program_write_locks) if message.is_writable(i, demote_program_write_locks) {
&& (execution_result.is_ok() let is_nonce_account = prepare_if_nonce_account(
|| (maybe_nonce.is_some() && (is_nonce_account || is_fee_payer))) address,
{ account,
if execution_result.is_err() { execution_result,
match (is_nonce_account, is_fee_payer, maybe_nonce) { is_fee_payer,
// nonce is fee-payer, state updated in `prepare_if_nonce_account()` maybe_nonce,
(true, true, Some((_, _, None, _))) => (), blockhash,
// nonce not fee-payer, state updated in `prepare_if_nonce_account()` lamports_per_signature,
(true, false, Some((_, _, Some(_), _))) => (), );
// not nonce, but fee-payer. rollback to cached state
(false, true, Some((_, _, Some(fee_payer_account), _))) => { if execution_result.is_ok() || is_nonce_account || is_fee_payer {
*account = fee_payer_account.clone();
}
_ => panic!("unexpected nonce condition"),
}
}
if account.rent_epoch() == INITIAL_RENT_EPOCH { if account.rent_epoch() == INITIAL_RENT_EPOCH {
let rent = rent_collector.collect_from_created_account( let rent = rent_collector.collect_from_created_account(
address, address,
@ -1154,9 +1128,11 @@ impl Accounts {
rent_for_sysvars, rent_for_sysvars,
); );
loaded_transaction.rent += rent; loaded_transaction.rent += rent;
loaded_transaction loaded_transaction.rent_debits.insert(
.rent_debits address,
.insert(address, rent, account.lamports()); rent,
account.lamports(),
);
} }
// Add to the accounts to store // Add to the accounts to store
@ -1164,40 +1140,37 @@ impl Accounts {
} }
} }
} }
}
accounts accounts
} }
} }
pub fn prepare_if_nonce_account( pub fn prepare_if_nonce_account<'a>(
address: &Pubkey, address: &Pubkey,
account: &mut AccountSharedData, account: &mut AccountSharedData,
execution_result: &Result<()>, execution_result: &Result<()>,
maybe_nonce: Option<( is_fee_payer: bool,
&Pubkey, maybe_nonce: Option<(&'a NonceFull, bool)>,
&AccountSharedData,
Option<&AccountSharedData>,
bool,
)>,
blockhash: &Hash, blockhash: &Hash,
lamports_per_signature: u64, lamports_per_signature: u64,
) -> bool { ) -> bool {
if let Some((nonce_key, nonce_account, _, advance_blockhash)) = maybe_nonce { if let Some((nonce, rollback)) = maybe_nonce {
if address == nonce_key { if address == nonce.address() {
if execution_result.is_err() { if rollback {
// The transaction failed which would normally drop the account // The transaction failed which would normally drop the account
// processing changes, since this account is now being included in // processing changes, since this account is now being included
// the accounts written back to the db, roll it back to // in the accounts written back to the db, roll it back to
// pre-processing state. // pre-processing state.
*account = nonce_account.clone(); *account = nonce.account().clone();
} }
if advance_blockhash {
// Advance the stored blockhash to prevent fee theft by someone // Advance the stored blockhash to prevent fee theft by someone
// replaying nonce transactions that have failed with an // replaying nonce transactions that have failed with an
// `InstructionError`. // `InstructionError`.
// //
// Since we know we are dealing with a valid nonce account, unwrap is safe here // Since we know we are dealing with a valid nonce account,
let state = StateMut::<NonceVersions>::state(nonce_account) // unwrap is safe here
let state = StateMut::<NonceVersions>::state(nonce.account())
.unwrap() .unwrap()
.convert_to_current(); .convert_to_current();
if let NonceState::Initialized(ref data) = state { if let NonceState::Initialized(ref data) = state {
@ -1209,12 +1182,23 @@ pub fn prepare_if_nonce_account(
))) )))
.unwrap(); .unwrap();
} }
} true
return 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();
} }
} }
false false
} }
} else {
false
}
}
pub fn create_test_accounts( pub fn create_test_accounts(
accounts: &Accounts, accounts: &Accounts,
@ -2596,8 +2580,8 @@ mod tests {
&Hash::default(), &Hash::default(),
0, 0,
true, true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks true, // demote_program_write_locks
true, // leave_nonce_on_success
); );
assert_eq!(collected_accounts.len(), 2); assert_eq!(collected_accounts.len(), 2);
assert!(collected_accounts assert!(collected_accounts
@ -2722,39 +2706,32 @@ mod tests {
account_address: &Pubkey, account_address: &Pubkey,
account: &mut AccountSharedData, account: &mut AccountSharedData,
tx_result: &Result<()>, tx_result: &Result<()>,
maybe_nonce: Option<( is_fee_payer: bool,
&Pubkey, maybe_nonce: Option<(&NonceFull, bool)>,
&AccountSharedData,
Option<&AccountSharedData>,
bool,
)>,
blockhash: &Hash, blockhash: &Hash,
lamports_per_signature: u64, lamports_per_signature: u64,
expect_account: &AccountSharedData, expect_account: &AccountSharedData,
) -> bool { ) -> bool {
// Verify expect_account's relationship // Verify expect_account's relationship
if !is_fee_payer {
match maybe_nonce { match maybe_nonce {
Some((nonce_address, _nonce_account, _maybe_fee_payer_account, _)) Some((nonce, _)) if nonce.address() == account_address => {
if nonce_address == account_address && tx_result.is_ok() => assert_ne!(expect_account, nonce.account())
{
assert_eq!(expect_account, account) // Account update occurs in system_instruction_processor
}
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), _ => assert_eq!(expect_account, account),
} }
}
prepare_if_nonce_account( prepare_if_nonce_account(
account_address, account_address,
account, account,
tx_result, tx_result,
is_fee_payer,
maybe_nonce, maybe_nonce,
blockhash, blockhash,
lamports_per_signature, lamports_per_signature,
); );
assert_eq!(expect_account, account);
expect_account == account expect_account == account
} }
@ -2769,22 +2746,25 @@ mod tests {
maybe_fee_payer_account, maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account(); ) = create_accounts_prepare_if_nonce_account();
let post_account_address = pre_account_address; 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 mut expect_account = pre_account;
let data = expect_account
NonceVersions::new_current(NonceState::Initialized(nonce::state::Data::default())); .set_state(&NonceVersions::new_current(NonceState::Initialized(
expect_account.set_state(&data).unwrap(); nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature),
)))
.unwrap();
assert!(run_prepare_if_nonce_account_test( assert!(run_prepare_if_nonce_account_test(
&post_account_address, &post_account_address,
&mut post_account, &mut post_account,
&Ok(()), &Ok(()),
Some((
&pre_account_address,
&pre_account,
maybe_fee_payer_account.as_ref(),
false, false,
)), Some((&nonce, true)),
&blockhash, &blockhash,
lamports_per_signature, lamports_per_signature,
&expect_account, &expect_account,
@ -2809,6 +2789,7 @@ mod tests {
&post_account_address, &post_account_address,
&mut post_account, &mut post_account,
&Ok(()), &Ok(()),
false,
None, None,
&blockhash, &blockhash,
lamports_per_signature, lamports_per_signature,
@ -2827,18 +2808,16 @@ mod tests {
maybe_fee_payer_account, maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_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(); let expect_account = post_account.clone();
// Wrong key // Wrong key
assert!(run_prepare_if_nonce_account_test( assert!(run_prepare_if_nonce_account_test(
&Pubkey::new(&[1u8; 32]), &Pubkey::new(&[1u8; 32]),
&mut post_account, &mut post_account,
&Ok(()), &Ok(()),
Some(( false,
&pre_account_address, Some((&nonce, true)),
&pre_account,
maybe_fee_payer_account.as_ref(),
true,
)),
&blockhash, &blockhash,
lamports_per_signature, lamports_per_signature,
&expect_account, &expect_account,
@ -2856,8 +2835,10 @@ mod tests {
maybe_fee_payer_account, maybe_fee_payer_account,
) = create_accounts_prepare_if_nonce_account(); ) = create_accounts_prepare_if_nonce_account();
let post_account_address = pre_account_address; let post_account_address = pre_account_address;
let mut expect_account = pre_account.clone(); let mut expect_account = pre_account.clone();
let nonce = NonceFull::new(pre_account_address, pre_account, maybe_fee_payer_account);
expect_account expect_account
.set_state(&NonceVersions::new_current(NonceState::Initialized( .set_state(&NonceVersions::new_current(NonceState::Initialized(
nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature), nonce::state::Data::new(Pubkey::default(), blockhash, lamports_per_signature),
@ -2871,18 +2852,81 @@ mod tests {
0, 0,
InstructionError::InvalidArgument, InstructionError::InvalidArgument,
)), )),
Some(( false,
&pre_account_address, Some((&nonce, true)),
&pre_account,
maybe_fee_payer_account.as_ref(),
true,
)),
&blockhash, &blockhash,
lamports_per_signature, lamports_per_signature,
&expect_account, &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] #[test]
fn test_nonced_failure_accounts_rollback_from_pays() { fn test_nonced_failure_accounts_rollback_from_pays() {
let rent_collector = RentCollector::default(); let rent_collector = RentCollector::default();
@ -2966,8 +3010,8 @@ mod tests {
&next_blockhash, &next_blockhash,
0, 0,
true, true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks true, // demote_program_write_locks
true, // leave_nonce_on_success
); );
assert_eq!(collected_accounts.len(), 2); assert_eq!(collected_accounts.len(), 2);
assert_eq!( assert_eq!(
@ -3077,8 +3121,8 @@ mod tests {
&next_blockhash, &next_blockhash,
0, 0,
true, true,
true, // merge_nonce_error_into_system_error
true, // demote_program_write_locks true, // demote_program_write_locks
true, // leave_nonce_on_success
); );
assert_eq!(collected_accounts.len(), 1); assert_eq!(collected_accounts.len(), 1);
let collected_nonce_account = collected_accounts let collected_nonce_account = collected_accounts

View File

@ -669,17 +669,13 @@ impl NonceFull {
let nonce_address = *partial.address(); let nonce_address = *partial.address();
if *fee_payer_address == nonce_address { if *fee_payer_address == nonce_address {
Ok(Self { Ok(Self::new(nonce_address, fee_payer_account, None))
address: nonce_address,
account: fee_payer_account,
fee_payer_account: None,
})
} else { } else {
Ok(Self { Ok(Self::new(
address: nonce_address, nonce_address,
account: partial.account().clone(), partial.account().clone(),
fee_payer_account: Some(fee_payer_account), Some(fee_payer_account),
}) ))
} }
} else { } else {
Err(TransactionError::AccountNotFound) Err(TransactionError::AccountNotFound)
@ -3935,14 +3931,13 @@ impl Bank {
inner_instructions.push(None); inner_instructions.push(None);
} }
let nonce = let nonce = match &process_result {
if let Err(TransactionError::InstructionError(_, _)) = &process_result { Ok(_) => nonce.clone(), // May need to calculate the fee based on the nonce
Err(TransactionError::InstructionError(_, _)) => {
error_counters.instruction_error += 1; error_counters.instruction_error += 1;
nonce.clone() nonce.clone() // May need to advance the nonce
} else if process_result.is_err() { }
None _ => None,
} else {
nonce.clone()
}; };
(process_result, nonce) (process_result, nonce)
@ -4176,8 +4171,8 @@ impl Bank {
&blockhash, &blockhash,
lamports_per_signature, lamports_per_signature,
self.rent_for_sysvars(), self.rent_for_sysvars(),
self.merge_nonce_error_into_system_error(),
self.demote_program_write_locks(), self.demote_program_write_locks(),
self.leave_nonce_on_success(),
); );
let rent_debits = self.collect_rent(executed_results, loaded_txs); 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()) .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 { pub fn versioned_tx_message_enabled(&self) -> bool {
self.feature_set self.feature_set
.is_active(&feature_set::versioned_tx_message_enabled::id()) .is_active(&feature_set::versioned_tx_message_enabled::id())
@ -6058,6 +6048,11 @@ impl Bank {
.is_active(&feature_set::demote_program_write_locks::id()) .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 { pub fn stakes_remove_delegation_if_inactive_enabled(&self) -> bool {
self.feature_set self.feature_set
.is_active(&feature_set::stakes_remove_delegation_if_inactive::id()) .is_active(&feature_set::stakes_remove_delegation_if_inactive::id())
@ -11367,9 +11362,6 @@ pub(crate) mod tests {
debug!("cust: {:?}", custodian_account); debug!("cust: {:?}", custodian_account);
let nonce_hash = get_nonce_blockhash(&bank, &nonce_pubkey).unwrap(); 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 { for _ in 0..MAX_RECENT_BLOCKHASHES + 1 {
goto_end_of_slot(Arc::get_mut(&mut bank).unwrap()); goto_end_of_slot(Arc::get_mut(&mut bank).unwrap());
bank = Arc::new(new_from_parent(&bank)); bank = Arc::new(new_from_parent(&bank));
@ -11403,7 +11395,7 @@ pub(crate) mod tests {
.get_fee_for_message(&recent_message.try_into().unwrap()) .get_fee_for_message(&recent_message.try_into().unwrap())
.unwrap() .unwrap()
); );
assert_eq!( assert_ne!(
nonce_hash, nonce_hash,
get_nonce_blockhash(&bank, &nonce_pubkey).unwrap() 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"); solana_sdk::declare_id!("Ftok2jhqAqxUWEiCVRrfRs9DPppWP8cgTB7NQNKL88mS");
} }
pub mod leave_nonce_on_success {
solana_sdk::declare_id!("E8MkiWZNNPGU6n55jkGzyj8ghUmjCHRmDFdYYFYHxWhQ");
}
lazy_static! { lazy_static! {
/// Map of feature identifiers to user-visible description /// Map of feature identifiers to user-visible description
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [ 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"), (reject_deployment_of_unresolved_syscalls::id(), "Reject deployment of programs with unresolved syscall symbols"),
(nonce_must_be_writable::id(), "nonce must be writable"), (nonce_must_be_writable::id(), "nonce must be writable"),
(spl_token_v3_3_0_release::id(), "spl-token v3.3.0 release"), (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 ***************/ /*************** ADD NEW FEATURES HERE ***************/
] ]
.iter() .iter()