From 799a60eff0e9bc4f0d9e561c7f18f7e5786eff80 Mon Sep 17 00:00:00 2001 From: behzad nouri Date: Sun, 5 Jun 2022 12:58:52 -0400 Subject: [PATCH] permanently disables durable transactions with chain blockhash domain https://github.com/solana-labs/solana/pull/25744 separated durable nonce and blockhash domains, which will stop double execution going forward. However it is possible that a durable transaction has *already* been executed once as a normal transaction and it is now a valid durable transaction. #25744 cannot stop such transactions to be re-executed until the nonce accounts are advanced. This commit adds a new nonce version indicating that the nonce is moved out of blockhash domain, and permanently disables durable transactions for legacy nonces which are in blockhash domain. --- account-decoder/src/parse_account_data.rs | 5 +- account-decoder/src/parse_nonce.rs | 10 +- cli/src/nonce.rs | 56 +++-- client/src/blockhash_query.rs | 5 +- client/src/nonce_utils.rs | 5 +- rpc/src/rpc.rs | 13 +- rpc/src/transaction_status_service.rs | 6 +- runtime/src/accounts.rs | 126 +++++++---- runtime/src/bank.rs | 102 ++++----- runtime/src/nonce_keyed_account.rs | 204 ++++++++---------- runtime/src/system_instruction_processor.rs | 66 +++--- sdk/program/src/nonce/state/current.rs | 5 +- sdk/program/src/nonce/state/mod.rs | 112 +++++++++- sdk/src/nonce_account.rs | 161 ++++++++++++-- .../src/send_transaction_service.rs | 32 ++- 15 files changed, 600 insertions(+), 308 deletions(-) diff --git a/account-decoder/src/parse_account_data.rs b/account-decoder/src/parse_account_data.rs index 4b7768dc615513..417a1e30604705 100644 --- a/account-decoder/src/parse_account_data.rs +++ b/account-decoder/src/parse_account_data.rs @@ -145,7 +145,10 @@ mod test { assert_eq!(parsed.program, "vote".to_string()); assert_eq!(parsed.space, VoteState::size_of() as u64); - let nonce_data = Versions::new_current(State::Initialized(Data::default())); + let nonce_data = Versions::new( + State::Initialized(Data::default()), + true, // separate_domains + ); let nonce_account_data = bincode::serialize(&nonce_data).unwrap(); let parsed = parse_account_data( &account_pubkey, diff --git a/account-decoder/src/parse_nonce.rs b/account-decoder/src/parse_nonce.rs index 9b33f67295785d..5e541bd6db6938 100644 --- a/account-decoder/src/parse_nonce.rs +++ b/account-decoder/src/parse_nonce.rs @@ -7,10 +7,9 @@ use { }; pub fn parse_nonce(data: &[u8]) -> Result { - let nonce_state: Versions = bincode::deserialize(data) + let nonce_versions: Versions = bincode::deserialize(data) .map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?; - let nonce_state = nonce_state.convert_to_current(); - match nonce_state { + match nonce_versions.state() { // This prevents parsing an allocated System-owned account with empty data of any non-zero // length as `uninitialized` nonce. An empty account of the wrong length can never be // initialized as a nonce account, and an empty account of the correct length may not be an @@ -58,7 +57,10 @@ mod test { #[test] fn test_parse_nonce() { - let nonce_data = Versions::new_current(State::Initialized(Data::default())); + let nonce_data = Versions::new( + State::Initialized(Data::default()), + true, // separate_domains + ); let nonce_account_data = bincode::serialize(&nonce_data).unwrap(); assert_eq!( parse_nonce(&nonce_account_data).unwrap(), diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index fdf253943331d8..d94468c770f72e 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -933,11 +933,10 @@ mod tests { DurableNonce::from_blockhash(&Hash::default(), /*separate_domains:*/ true); let blockhash = *durable_nonce.as_hash(); let nonce_pubkey = solana_sdk::pubkey::new_rand(); - let data = Versions::new_current(State::Initialized(nonce::state::Data::new( - nonce_pubkey, - durable_nonce, - 0, - ))); + let data = Versions::new( + State::Initialized(nonce::state::Data::new(nonce_pubkey, durable_nonce, 0)), + true, // separate_domains + ); let valid = Account::new_data(1, &data, &system_program::ID); assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok()); @@ -957,11 +956,14 @@ mod tests { let invalid_durable_nonce = DurableNonce::from_blockhash(&hash(b"invalid"), /*separate_domains:*/ true); - let data = Versions::new_current(State::Initialized(nonce::state::Data::new( - nonce_pubkey, - invalid_durable_nonce, - 0, - ))); + let data = Versions::new( + State::Initialized(nonce::state::Data::new( + nonce_pubkey, + invalid_durable_nonce, + 0, + )), + true, // separate_domains + ); let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap(); if let CliError::InvalidNonce(err) = check_nonce_account(&invalid_hash, &nonce_pubkey, &blockhash).unwrap_err() @@ -976,11 +978,14 @@ mod tests { } let new_nonce_authority = solana_sdk::pubkey::new_rand(); - let data = Versions::new_current(State::Initialized(nonce::state::Data::new( - new_nonce_authority, - durable_nonce, - 0, - ))); + let data = Versions::new( + State::Initialized(nonce::state::Data::new( + new_nonce_authority, + durable_nonce, + 0, + )), + true, // separate_domains + ); let invalid_authority = Account::new_data(1, &data, &system_program::ID); if let CliError::InvalidNonce(err) = check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err() @@ -994,7 +999,7 @@ mod tests { ); } - let data = Versions::new_current(State::Uninitialized); + let data = Versions::new(State::Uninitialized, /*separate_domains:*/ true); let invalid_state = Account::new_data(1, &data, &system_program::ID); if let CliError::InvalidNonce(err) = check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err() @@ -1005,7 +1010,8 @@ mod tests { #[test] fn test_account_identity_ok() { - let nonce_account = nonce_account::create_account(1).into_inner(); + let nonce_account = + nonce_account::create_account(1, /*separate_domains:*/ true).into_inner(); assert_eq!(account_identity_ok(&nonce_account), Ok(())); let system_account = Account::new(1, 0, &system_program::id()); @@ -1024,14 +1030,18 @@ mod tests { #[test] fn test_state_from_account() { - let mut nonce_account = nonce_account::create_account(1).into_inner(); + let mut nonce_account = + nonce_account::create_account(1, /*separate_domains:*/ true).into_inner(); assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized)); let durable_nonce = DurableNonce::from_blockhash(&Hash::new(&[42u8; 32]), /*separate_domains:*/ true); let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), durable_nonce, 42); nonce_account - .set_state(&Versions::new_current(State::Initialized(data.clone()))) + .set_state(&Versions::new( + State::Initialized(data.clone()), + true, // separate_domains + )) .unwrap(); assert_eq!( state_from_account(&nonce_account), @@ -1047,7 +1057,8 @@ mod tests { #[test] fn test_data_from_helpers() { - let mut nonce_account = nonce_account::create_account(1).into_inner(); + let mut nonce_account = + nonce_account::create_account(1, /*separate_domains:*/ true).into_inner(); let state = state_from_account(&nonce_account).unwrap(); assert_eq!( data_from_state(&state), @@ -1062,7 +1073,10 @@ mod tests { DurableNonce::from_blockhash(&Hash::new(&[42u8; 32]), /*separate_domains:*/ true); let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), durable_nonce, 42); nonce_account - .set_state(&Versions::new_current(State::Initialized(data.clone()))) + .set_state(&Versions::new( + State::Initialized(data.clone()), + true, // separate_domains + )) .unwrap(); let state = state_from_account(&nonce_account).unwrap(); assert_eq!(data_from_state(&state), Ok(&data)); diff --git a/client/src/blockhash_query.rs b/client/src/blockhash_query.rs index 02558a8c0560fc..638c51910314ed 100644 --- a/client/src/blockhash_query.rs +++ b/client/src/blockhash_query.rs @@ -427,7 +427,10 @@ mod tests { }; let nonce_account = Account::new_data_with_space( 42, - &nonce::state::Versions::new_current(nonce::State::Initialized(data)), + &nonce::state::Versions::new( + nonce::State::Initialized(data), + true, // separate_domains + ), nonce::State::size(), &system_program::id(), ) diff --git a/client/src/nonce_utils.rs b/client/src/nonce_utils.rs index e9de5142db1fda..9d797771559cd8 100644 --- a/client/src/nonce_utils.rs +++ b/client/src/nonce_utils.rs @@ -129,9 +129,8 @@ pub fn state_from_account>( account: &T, ) -> Result { account_identity_ok(account)?; - StateMut::::state(account) - .map_err(|_| Error::InvalidAccountData) - .map(|v| v.convert_to_current()) + let versions = StateMut::::state(account).map_err(|_| Error::InvalidAccountData)?; + Ok(State::from(versions)) } /// Deserialize the state data of a durable transaction nonce account. diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 3d55c695f8e11a..226675569a09d4 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -5549,11 +5549,10 @@ pub mod tests { let authority = Pubkey::new_unique(); let account = AccountSharedData::new_data( 42, - &nonce::state::Versions::new_current(nonce::State::new_initialized( - &authority, - DurableNonce::default(), - 1000, - )), + &nonce::state::Versions::new( + nonce::State::new_initialized(&authority, DurableNonce::default(), 1000), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -5584,8 +5583,8 @@ pub mod tests { system_program::id().to_string(), {"filters": [{ "memcmp": { - "offset": 0, - "bytes": bs58::encode(vec![1, 0, 0, 0]).into_string(), + "offset": 4, + "bytes": bs58::encode(vec![0, 0, 0, 0]).into_string(), }, }]}, ])), diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index 181b6669ae6a34..73382de62958ce 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -323,13 +323,15 @@ pub(crate) mod tests { let expected_transaction = transaction.clone(); let pubkey = Pubkey::new_unique(); - let mut nonce_account = nonce_account::create_account(1).into_inner(); + let mut nonce_account = + nonce_account::create_account(1, /*separate_domains:*/ true).into_inner(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new(&[42u8; 32]), /*separate_domains:*/ true); let data = nonce::state::Data::new(Pubkey::new(&[1u8; 32]), durable_nonce, 42); nonce_account - .set_state(&nonce::state::Versions::new_current( + .set_state(&nonce::state::Versions::new( nonce::State::Initialized(data), + true, // separate_domains )) .unwrap(); diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 193c7352b6d05e..e2bbdd9320938b 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -1196,7 +1196,7 @@ impl Accounts { res: &'a [TransactionExecutionResult], loaded: &'a mut [TransactionLoadResult], rent_collector: &RentCollector, - durable_nonce: &DurableNonce, + durable_nonce: &(DurableNonce, /*separate_domains:*/ bool), lamports_per_signature: u64, leave_nonce_on_success: bool, ) { @@ -1228,7 +1228,7 @@ impl Accounts { execution_results: &'a [TransactionExecutionResult], load_results: &'a mut [TransactionLoadResult], rent_collector: &RentCollector, - durable_nonce: &DurableNonce, + durable_nonce: &(DurableNonce, /*separate_domains:*/ bool), lamports_per_signature: u64, leave_nonce_on_success: bool, ) -> Vec<(&'a Pubkey, &'a AccountSharedData)> { @@ -1315,7 +1315,7 @@ fn prepare_if_nonce_account<'a>( execution_result: &Result<()>, is_fee_payer: bool, maybe_nonce: Option<(&'a NonceFull, bool)>, - durable_nonce: &DurableNonce, + &(durable_nonce, separate_domains): &(DurableNonce, bool), lamports_per_signature: u64, ) -> bool { if let Some((nonce, rollback)) = maybe_nonce { @@ -1334,17 +1334,16 @@ fn prepare_if_nonce_account<'a>( // // Since we know we are dealing with a valid nonce account, // unwrap is safe here - let state = StateMut::::state(nonce.account()) - .unwrap() - .convert_to_current(); + let nonce_versions = StateMut::::state(nonce.account()).unwrap(); + let state = nonce_versions.state(); if let NonceState::Initialized(ref data) = state { - account - .set_state(&NonceVersions::new_current(NonceState::new_initialized( - &data.authority, - *durable_nonce, - lamports_per_signature, - ))) - .unwrap(); + let nonce_state = NonceState::new_initialized( + &data.authority, + durable_nonce, + lamports_per_signature, + ); + let nonce_versions = NonceVersions::new(nonce_state, separate_domains); + account.set_state(&nonce_versions).unwrap(); } true } else { @@ -1725,7 +1724,10 @@ mod tests { nonce.pubkey(), AccountSharedData::new_data( min_balance * 2, - &NonceVersions::new_current(NonceState::Initialized(nonce::state::Data::default())), + &NonceVersions::new( + NonceState::Initialized(nonce::state::Data::default()), + true, // separate_domains + ), &system_program::id(), ) .unwrap(), @@ -3028,7 +3030,7 @@ mod tests { &execution_results, loaded.as_mut_slice(), &rent_collector, - &DurableNonce::default(), + &(DurableNonce::default(), /*separate_domains:*/ true), 0, true, // leave_nonce_on_success ); @@ -3174,20 +3176,24 @@ mod tests { Pubkey, AccountSharedData, AccountSharedData, - DurableNonce, + (DurableNonce, /*separate_domains:*/ bool), u64, Option, ) { - let data = - NonceVersions::new_current(NonceState::Initialized(nonce::state::Data::default())); + let data = NonceVersions::new( + NonceState::Initialized(nonce::state::Data::default()), + true, // separate_domains + ); let account = AccountSharedData::new_data(42, &data, &system_program::id()).unwrap(); let mut pre_account = account.clone(); pre_account.set_lamports(43); + let durable_nonce = + DurableNonce::from_blockhash(&Hash::new(&[1u8; 32]), /*separate_domains:*/ true); ( Pubkey::default(), pre_account, account, - DurableNonce::from_blockhash(&Hash::new(&[1u8; 32]), /*separate_domains:*/ true), + (durable_nonce, /*separate_domains:*/ true), 1234, None, ) @@ -3199,7 +3205,7 @@ mod tests { tx_result: &Result<()>, is_fee_payer: bool, maybe_nonce: Option<(&NonceFull, bool)>, - durable_nonce: &DurableNonce, + durable_nonce: &(DurableNonce, /*separate_domains:*/ bool), lamports_per_signature: u64, expect_account: &AccountSharedData, ) -> bool { @@ -3245,9 +3251,14 @@ mod tests { 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), - ))) + .set_state(&NonceVersions::new( + NonceState::Initialized(nonce::state::Data::new( + Pubkey::default(), + blockhash.0, + lamports_per_signature, + )), + true, // separate_domains + )) .unwrap(); assert!(run_prepare_if_nonce_account_test( @@ -3331,9 +3342,14 @@ mod tests { 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), - ))) + .set_state(&NonceVersions::new( + NonceState::Initialized(nonce::state::Data::new( + Pubkey::default(), + blockhash.0, + lamports_per_signature, + )), + true, // separate_domains + )) .unwrap(); assert!(run_prepare_if_nonce_account_test( @@ -3373,7 +3389,7 @@ mod tests { )), false, Some((&nonce, true)), - &DurableNonce::default(), + &(DurableNonce::default(), /*separate_domains:*/ true), 1, &post_fee_payer_account.clone(), )); @@ -3384,7 +3400,7 @@ mod tests { &Ok(()), true, Some((&nonce, true)), - &DurableNonce::default(), + &(DurableNonce::default(), /*separate_domains:*/ true), 1, &post_fee_payer_account.clone(), )); @@ -3398,7 +3414,7 @@ mod tests { )), true, None, - &DurableNonce::default(), + &(DurableNonce::default(), /*separate_domains:*/ true), 1, &post_fee_payer_account.clone(), )); @@ -3412,7 +3428,7 @@ mod tests { )), true, Some((&nonce, true)), - &DurableNonce::default(), + &(DurableNonce::default(), /*separate_domains:*/ true), 1, &pre_fee_payer_account, )); @@ -3429,9 +3445,14 @@ mod tests { let to_address = Pubkey::new_unique(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); - let nonce_state = NonceVersions::new_current(NonceState::Initialized( - nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0), - )); + let nonce_state = NonceVersions::new( + NonceState::Initialized(nonce::state::Data::new( + nonce_authority.pubkey(), + durable_nonce, + 0, + )), + true, // separate_domains + ); let nonce_account_post = AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap(); let from_account_post = AccountSharedData::new(4199, 0, &Pubkey::default()); @@ -3456,9 +3477,14 @@ mod tests { let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); - let nonce_state = NonceVersions::new_current(NonceState::Initialized( - nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0), - )); + let nonce_state = NonceVersions::new( + NonceState::Initialized(nonce::state::Data::new( + nonce_authority.pubkey(), + durable_nonce, + 0, + )), + true, // separate_domains + ); let nonce_account_pre = AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap(); let from_account_pre = AccountSharedData::new(4242, 0, &Pubkey::default()); @@ -3503,7 +3529,7 @@ mod tests { &execution_results, loaded.as_mut_slice(), &rent_collector, - &durable_nonce, + &(durable_nonce, /*separate_domains:*/ true), 0, true, // leave_nonce_on_success ); @@ -3530,6 +3556,7 @@ mod tests { assert!(nonce_account::verify_nonce_account( &collected_nonce_account, durable_nonce.as_hash(), + true, // separate_domins )); } @@ -3544,9 +3571,14 @@ mod tests { let to_address = Pubkey::new_unique(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); - let nonce_state = NonceVersions::new_current(NonceState::Initialized( - nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0), - )); + let nonce_state = NonceVersions::new( + NonceState::Initialized(nonce::state::Data::new( + nonce_authority.pubkey(), + durable_nonce, + 0, + )), + true, // separate_domains + ); let nonce_account_post = AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap(); let from_account_post = AccountSharedData::new(4200, 0, &Pubkey::default()); @@ -3571,9 +3603,14 @@ mod tests { let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); - let nonce_state = NonceVersions::new_current(NonceState::Initialized( - nonce::state::Data::new(nonce_authority.pubkey(), durable_nonce, 0), - )); + let nonce_state = NonceVersions::new( + NonceState::Initialized(nonce::state::Data::new( + nonce_authority.pubkey(), + durable_nonce, + 0, + )), + true, // separate_domains + ); let nonce_account_pre = AccountSharedData::new_data(42, &nonce_state, &system_program::id()).unwrap(); @@ -3617,7 +3654,7 @@ mod tests { &execution_results, loaded.as_mut_slice(), &rent_collector, - &durable_nonce, + &(durable_nonce, /*separate_domains:*/ true), 0, true, // leave_nonce_on_success ); @@ -3635,6 +3672,7 @@ mod tests { assert!(nonce_account::verify_nonce_account( &collected_nonce_account, durable_nonce.as_hash(), + true, // separate_domins )); } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 8d7b7cd6fbf5ec..5d8c6cb54819b7 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -4045,6 +4045,11 @@ impl Bank { self.rc.accounts.accounts_db.set_shrink_paths(paths); } + pub fn separate_nonce_from_blockhash(&self) -> bool { + self.feature_set + .is_active(&feature_set::separate_nonce_from_blockhash::id()) + } + fn check_age<'a>( &self, txs: impl Iterator, @@ -4052,9 +4057,7 @@ impl Bank { max_age: usize, error_counters: &mut TransactionErrorMetrics, ) -> Vec { - let separate_nonce_from_blockhash = self - .feature_set - .is_active(&feature_set::separate_nonce_from_blockhash::id()); + let separate_nonce_from_blockhash = self.separate_nonce_from_blockhash(); let enable_durable_nonce = separate_nonce_from_blockhash && self .feature_set @@ -4127,15 +4130,15 @@ impl Bank { } fn check_message_for_nonce(&self, message: &SanitizedMessage) -> Option { - message - .get_durable_nonce(self.feature_set.is_active(&nonce_must_be_writable::id())) - .and_then(|nonce_address| { - self.get_account_with_fixed_root(nonce_address) - .map(|nonce_account| (*nonce_address, nonce_account)) - }) - .filter(|(_, nonce_account)| { - nonce_account::verify_nonce_account(nonce_account, message.recent_blockhash()) - }) + let nonce_address = + message.get_durable_nonce(self.feature_set.is_active(&nonce_must_be_writable::id()))?; + let nonce_account = self.get_account_with_fixed_root(nonce_address)?; + nonce_account::verify_nonce_account( + &nonce_account, + message.recent_blockhash(), + self.separate_nonce_from_blockhash(), + ) + .then(|| (*nonce_address, nonce_account)) } fn check_transaction_for_nonce( @@ -4913,10 +4916,10 @@ impl Bank { let mut write_time = Measure::start("write_time"); let durable_nonce = { - let separate_nonce_from_blockhash = self - .feature_set - .is_active(&feature_set::separate_nonce_from_blockhash::id()); - DurableNonce::from_blockhash(&last_blockhash, separate_nonce_from_blockhash) + let separate_nonce_from_blockhash = self.separate_nonce_from_blockhash(); + let durable_nonce = + DurableNonce::from_blockhash(&last_blockhash, separate_nonce_from_blockhash); + (durable_nonce, separate_nonce_from_blockhash) }; self.rc.accounts.store_cached( self.slot(), @@ -7635,9 +7638,14 @@ pub(crate) mod tests { DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); let nonce_account = AccountSharedData::new_data( 43, - &nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::new(Pubkey::default(), durable_nonce, lamports_per_signature), - )), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::new( + Pubkey::default(), + durable_nonce, + lamports_per_signature, + )), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -10160,9 +10168,10 @@ pub(crate) mod tests { let nonce = Keypair::new(); let nonce_account = AccountSharedData::new_data( min_balance + 42, - &nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::default(), - )), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::default()), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -12350,14 +12359,12 @@ pub(crate) mod tests { } fn get_nonce_blockhash(bank: &Bank, nonce_pubkey: &Pubkey) -> Option { - bank.get_account(nonce_pubkey).and_then(|acc| { - let state = - StateMut::::state(&acc).map(|v| v.convert_to_current()); - match state { - Ok(nonce::State::Initialized(ref data)) => Some(data.blockhash()), - _ => None, - } - }) + let account = bank.get_account(nonce_pubkey)?; + let nonce_versions = StateMut::::state(&account); + match nonce_versions.ok()?.state() { + nonce::State::Initialized(ref data) => Some(data.blockhash()), + _ => None, + } } fn nonce_setup( @@ -12578,9 +12585,10 @@ pub(crate) mod tests { let nonce = Keypair::new(); let nonce_account = AccountSharedData::new_data( 42_424_242, - &nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::default(), - )), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::default()), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -13067,10 +13075,9 @@ pub(crate) mod tests { let (stored_nonce_hash, stored_fee_calculator) = bank .get_account(&nonce_pubkey) .and_then(|acc| { - let state = - StateMut::::state(&acc).map(|v| v.convert_to_current()); - match state { - Ok(nonce::State::Initialized(ref data)) => { + let nonce_versions = StateMut::::state(&acc); + match nonce_versions.ok()?.state() { + nonce::State::Initialized(ref data) => { Some((data.blockhash(), data.fee_calculator)) } _ => None, @@ -13104,10 +13111,9 @@ pub(crate) mod tests { let (nonce_hash, fee_calculator) = bank .get_account(&nonce_pubkey) .and_then(|acc| { - let state = - StateMut::::state(&acc).map(|v| v.convert_to_current()); - match state { - Ok(nonce::State::Initialized(ref data)) => { + let nonce_versions = StateMut::::state(&acc); + match nonce_versions.ok()?.state() { + nonce::State::Initialized(ref data) => { Some((data.blockhash(), data.fee_calculator)) } _ => None, @@ -13137,10 +13143,9 @@ pub(crate) mod tests { let (stored_nonce_hash, stored_fee_calculator) = bank .get_account(&nonce_pubkey) .and_then(|acc| { - let state = - StateMut::::state(&acc).map(|v| v.convert_to_current()); - match state { - Ok(nonce::State::Initialized(ref data)) => { + let nonce_versions = StateMut::::state(&acc); + match nonce_versions.ok()?.state() { + nonce::State::Initialized(ref data) => { Some((data.blockhash(), data.fee_calculator)) } _ => None, @@ -13174,10 +13179,9 @@ pub(crate) mod tests { let (nonce_hash, fee_calculator) = bank .get_account(&nonce_pubkey) .and_then(|acc| { - let state = - StateMut::::state(&acc).map(|v| v.convert_to_current()); - match state { - Ok(nonce::State::Initialized(ref data)) => { + let nonce_versions = StateMut::::state(&acc); + match nonce_versions.ok()?.state() { + nonce::State::Initialized(ref data) => { Some((data.blockhash(), data.fee_calculator)) } _ => None, diff --git a/runtime/src/nonce_keyed_account.rs b/runtime/src/nonce_keyed_account.rs index 3ffd3541ecc2f6..55e85d21e4a8f7 100644 --- a/runtime/src/nonce_keyed_account.rs +++ b/runtime/src/nonce_keyed_account.rs @@ -16,11 +16,13 @@ use { std::collections::HashSet, }; -fn get_durable_nonce(invoke_context: &InvokeContext) -> DurableNonce { +fn get_durable_nonce(invoke_context: &InvokeContext) -> (DurableNonce, /*separate_domains:*/ bool) { let separate_nonce_from_blockhash = invoke_context .feature_set .is_active(&feature_set::separate_nonce_from_blockhash::id()); - DurableNonce::from_blockhash(&invoke_context.blockhash, separate_nonce_from_blockhash) + let durable_nonce = + DurableNonce::from_blockhash(&invoke_context.blockhash, separate_nonce_from_blockhash); + (durable_nonce, separate_nonce_from_blockhash) } pub fn advance_nonce_account( @@ -46,7 +48,7 @@ pub fn advance_nonce_account( } let state: Versions = account.get_state()?; - match state.convert_to_current() { + match state.state() { State::Initialized(data) => { if !signers.contains(&data.authority) { ic_msg!( @@ -56,7 +58,7 @@ pub fn advance_nonce_account( ); return Err(InstructionError::MissingRequiredSignature); } - let next_durable_nonce = get_durable_nonce(invoke_context); + let (next_durable_nonce, separate_domains) = get_durable_nonce(invoke_context); if data.durable_nonce == next_durable_nonce { ic_msg!( invoke_context, @@ -73,7 +75,10 @@ pub fn advance_nonce_account( next_durable_nonce, invoke_context.lamports_per_signature, ); - account.set_state(&Versions::new_current(State::Initialized(new_data))) + account.set_state(&Versions::new( + State::Initialized(new_data), + separate_domains, + )) } _ => { ic_msg!( @@ -119,7 +124,8 @@ pub fn withdraw_nonce_account( } let state: Versions = from.get_state()?; - let signer = match state.convert_to_current() { + let separate_domains = state.separate_domains(); + let signer = match state.state() { State::Uninitialized => { if lamports > from.get_lamports() { ic_msg!( @@ -134,7 +140,7 @@ pub fn withdraw_nonce_account( } State::Initialized(ref data) => { if lamports == from.get_lamports() { - if data.durable_nonce == get_durable_nonce(invoke_context) { + if data.durable_nonce == get_durable_nonce(invoke_context).0 { ic_msg!( invoke_context, "Withdraw nonce account: nonce can only advance once per slot" @@ -144,7 +150,7 @@ pub fn withdraw_nonce_account( merge_nonce_error_into_system_error, )); } - from.set_state(&Versions::new_current(State::Uninitialized))?; + from.set_state(&Versions::new(State::Uninitialized, separate_domains))?; } else { let min_balance = rent.minimum_balance(from.get_data().len()); let amount = checked_add(lamports, min_balance)?; @@ -205,7 +211,7 @@ pub fn initialize_nonce_account( } let state: Versions = account.get_state()?; - match state.convert_to_current() { + match state.state() { State::Uninitialized => { let min_balance = rent.minimum_balance(account.get_data().len()); if account.get_lamports() < min_balance { @@ -217,12 +223,14 @@ pub fn initialize_nonce_account( ); return Err(InstructionError::InsufficientFunds); } + let (durable_nonce, separate_domains) = get_durable_nonce(invoke_context); let data = nonce::state::Data::new( *nonce_authority, - get_durable_nonce(invoke_context), + durable_nonce, invoke_context.lamports_per_signature, ); - account.set_state(&Versions::new_current(State::Initialized(data))) + let state = State::Initialized(data); + account.set_state(&Versions::new(state, separate_domains)) } _ => { ic_msg!( @@ -262,7 +270,8 @@ pub fn authorize_nonce_account( } let state: Versions = account.get_state()?; - match state.convert_to_current() { + let separate_domains = state.separate_domains(); + match state.state() { State::Initialized(data) => { if !signers.contains(&data.authority) { ic_msg!( @@ -277,7 +286,10 @@ pub fn authorize_nonce_account( data.durable_nonce, data.get_lamports_per_signature(), ); - account.set_state(&Versions::new_current(State::Initialized(new_data))) + account.set_state(&Versions::new( + State::Initialized(new_data), + separate_domains, + )) } _ => { ic_msg!( @@ -334,9 +346,12 @@ mod test { let accounts = vec![ ( Pubkey::new_unique(), - create_account(from_lamports).into_inner(), + create_account(from_lamports, /*separate_domains:*/ true).into_inner(), + ), + ( + Pubkey::new_unique(), + create_account(42, /*separate_domains:*/ true).into_inner(), ), - (Pubkey::new_unique(), create_account(42).into_inner()), (system_program::id(), AccountSharedData::default()), ]; let $instruction_accounts = vec![ @@ -390,52 +405,40 @@ mod test { }; let mut signers = HashSet::new(); signers.insert(*nonce_account.get_key()); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); // New is in Uninitialzed state - assert_eq!(state, State::Uninitialized); + assert_eq!(versions.state(), &State::Uninitialized); set_invoke_context_blockhash!(invoke_context, 95); let authorized = *nonce_account.get_key(); initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); let data = nonce::state::Data::new( data.authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); // First nonce instruction drives state from Uninitialized to Initialized - assert_eq!(state, State::Initialized(data.clone())); + assert_eq!(versions.state(), &State::Initialized(data.clone())); set_invoke_context_blockhash!(invoke_context, 63); advance_nonce_account(&mut nonce_account, &signers, &invoke_context).unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); let data = nonce::state::Data::new( data.authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); // Second nonce instruction consumes and replaces stored nonce - assert_eq!(state, State::Initialized(data.clone())); + assert_eq!(versions.state(), &State::Initialized(data.clone())); set_invoke_context_blockhash!(invoke_context, 31); advance_nonce_account(&mut nonce_account, &signers, &invoke_context).unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); let data = nonce::state::Data::new( data.authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); // Third nonce instruction for fun and profit - assert_eq!(state, State::Initialized(data)); + assert_eq!(versions.state(), &State::Initialized(data)); set_invoke_context_blockhash!(invoke_context, 0); let to_account = instruction_context @@ -467,12 +470,9 @@ mod test { assert_eq!(nonce_account.get_lamports(), expect_nonce_lamports); // Account balance goes to `to` assert_eq!(to_account.get_lamports(), expect_to_lamports); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); // Empty balance deinitializes data - assert_eq!(state, State::Uninitialized); + assert_eq!(versions.state(), &State::Uninitialized); } #[test] @@ -490,16 +490,13 @@ mod test { set_invoke_context_blockhash!(invoke_context, 31); let authority = *nonce_account.get_key(); initialize_nonce_account(&mut nonce_account, &authority, &rent, &invoke_context).unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); let data = nonce::state::Data::new( authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); - assert_eq!(state, State::Initialized(data)); + assert_eq!(versions.state(), &State::Initialized(data)); // Nonce account did not sign let signers = HashSet::new(); set_invoke_context_blockhash!(invoke_context, 0); @@ -613,11 +610,8 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); let mut signers = HashSet::new(); signers.insert(*nonce_account.get_key()); set_invoke_context_blockhash!(invoke_context, 0); @@ -643,11 +637,8 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); assert_eq!(nonce_account.get_lamports(), expect_from_lamports); assert_eq!(to_account.get_lamports(), expect_to_lamports); } @@ -667,11 +658,8 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); let signers = HashSet::new(); set_invoke_context_blockhash!(invoke_context, 0); let withdraw_lamports = nonce_account.get_lamports(); @@ -702,11 +690,8 @@ mod test { let nonce_account = instruction_context .try_borrow_instruction_account(transaction_context, NONCE_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); let mut signers = HashSet::new(); signers.insert(*nonce_account.get_key()); set_invoke_context_blockhash!(invoke_context, 0); @@ -765,11 +750,8 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); assert_eq!(nonce_account.get_lamports(), from_expect_lamports); assert_eq!(to_account.get_lamports(), to_expect_lamports); let withdraw_lamports = nonce_account.get_lamports(); @@ -794,11 +776,8 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); assert_eq!(nonce_account.get_lamports(), from_expect_lamports); assert_eq!(to_account.get_lamports(), to_expect_lamports); } @@ -823,16 +802,13 @@ mod test { set_invoke_context_blockhash!(invoke_context, 31); let authority = *nonce_account.get_key(); initialize_nonce_account(&mut nonce_account, &authority, &rent, &invoke_context).unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); let data = nonce::state::Data::new( authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); - assert_eq!(state, State::Initialized(data.clone())); + assert_eq!(versions.state(), &State::Initialized(data.clone())); let withdraw_lamports = 42; let from_expect_lamports = nonce_account.get_lamports() - withdraw_lamports; let to_expect_lamports = to_account.get_lamports() + withdraw_lamports; @@ -855,16 +831,13 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); + let versions = nonce_account.get_state::().unwrap(); let data = nonce::state::Data::new( data.authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); - assert_eq!(state, State::Initialized(data)); + assert_eq!(versions.state(), &State::Initialized(data)); assert_eq!(nonce_account.get_lamports(), from_expect_lamports); assert_eq!(to_account.get_lamports(), to_expect_lamports); set_invoke_context_blockhash!(invoke_context, 0); @@ -890,11 +863,8 @@ mod test { let to_account = instruction_context .try_borrow_instruction_account(transaction_context, WITHDRAW_TO_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); assert_eq!(nonce_account.get_lamports(), from_expect_lamports); assert_eq!(to_account.get_lamports(), to_expect_lamports); } @@ -1046,11 +1016,8 @@ mod test { let mut nonce_account = instruction_context .try_borrow_instruction_account(transaction_context, NONCE_ACCOUNT_INDEX) .unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Uninitialized); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Uninitialized); let mut signers = HashSet::new(); signers.insert(*nonce_account.get_key()); set_invoke_context_blockhash!(invoke_context, 0); @@ -1059,15 +1026,12 @@ mod test { initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context); let data = nonce::state::Data::new( authorized, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); assert_eq!(result, Ok(())); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Initialized(data)); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Initialized(data)); } #[test] @@ -1131,15 +1095,12 @@ mod test { let authority = Pubkey::default(); let data = nonce::state::Data::new( authority, - get_durable_nonce(&invoke_context), + get_durable_nonce(&invoke_context).0, invoke_context.lamports_per_signature, ); authorize_nonce_account(&mut nonce_account, &authority, &signers, &invoke_context).unwrap(); - let state = nonce_account - .get_state::() - .unwrap() - .convert_to_current(); - assert_eq!(state, State::Initialized(data)); + let versions = nonce_account.get_state::().unwrap(); + assert_eq!(versions.state(), &State::Initialized(data)); } #[test] @@ -1201,9 +1162,9 @@ mod test { .unwrap(); let mut signers = HashSet::new(); signers.insert(nonce_account.get_key()); - let state: State = nonce_account.get_state().unwrap(); + let versions: Versions = nonce_account.get_state().unwrap(); // New is in Uninitialzed state - assert_eq!(state, State::Uninitialized); + assert_eq!(versions.state(), &State::Uninitialized); set_invoke_context_blockhash!(invoke_context, 0); let authorized = *nonce_account.get_key(); initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap(); @@ -1213,7 +1174,8 @@ mod test { .get_account_at_index(NONCE_ACCOUNT_INDEX) .unwrap() .borrow(), - get_durable_nonce(&invoke_context).as_hash(), + get_durable_nonce(&invoke_context).0.as_hash(), + true, // separate_domins )); } @@ -1231,7 +1193,8 @@ mod test { .get_account_at_index(NONCE_ACCOUNT_INDEX) .unwrap() .borrow(), - &Hash::default() + &Hash::default(), + true, // separate_domins )); } @@ -1249,9 +1212,9 @@ mod test { .unwrap(); let mut signers = HashSet::new(); signers.insert(nonce_account.get_key()); - let state: State = nonce_account.get_state().unwrap(); + let versions: Versions = nonce_account.get_state().unwrap(); // New is in Uninitialzed state - assert_eq!(state, State::Uninitialized); + assert_eq!(versions.state(), &State::Uninitialized); set_invoke_context_blockhash!(invoke_context, 0); let authorized = *nonce_account.get_key(); initialize_nonce_account( @@ -1268,7 +1231,8 @@ mod test { .get_account_at_index(NONCE_ACCOUNT_INDEX) .unwrap() .borrow(), - get_durable_nonce(&invoke_context).as_hash(), + get_durable_nonce(&invoke_context).0.as_hash(), + true, // separate_domins )); } } diff --git a/runtime/src/system_instruction_processor.rs b/runtime/src/system_instruction_processor.rs index 6042d3b3aa5f65..d8991ce38cb0ad 100644 --- a/runtime/src/system_instruction_processor.rs +++ b/runtime/src/system_instruction_processor.rs @@ -549,11 +549,10 @@ pub fn get_system_account_kind(account: &AccountSharedData) -> Option match *state { - nonce::State::Initialized(_) => Some(SystemAccountKind::Nonce), - _ => None, - }, + let nonce_versions: nonce::state::Versions = account.state().ok()?; + match nonce_versions.state() { + nonce::State::Uninitialized => None, + nonce::State::Initialized(_) => Some(SystemAccountKind::Nonce), } } else { None @@ -1191,9 +1190,10 @@ mod tests { let nonce = Pubkey::new_unique(); let nonce_account = AccountSharedData::new_data( 42, - &nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::default(), - )), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::default()), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -1482,10 +1482,13 @@ mod tests { let from = Pubkey::new_unique(); let from_account = AccountSharedData::new_data( 100, - &nonce::state::Versions::new_current(nonce::State::Initialized(nonce::state::Data { - authority: from, - ..nonce::state::Data::default() - })), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data { + authority: from, + ..nonce::state::Data::default() + }), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -1764,7 +1767,8 @@ mod tests { #[test] fn test_process_nonce_ix_ok() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); let accounts = process_instruction( @@ -1871,7 +1875,8 @@ mod tests { #[test] fn test_process_withdraw_ix_ok() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); let pubkey = Pubkey::new_unique(); #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); @@ -1924,7 +1929,8 @@ mod tests { #[test] fn test_process_initialize_ix_only_nonce_acc_fail() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); process_instruction( &serialize(&SystemInstruction::InitializeNonceAccount(nonce_address)).unwrap(), vec![(nonce_address, nonce_account)], @@ -1941,7 +1947,8 @@ mod tests { #[test] fn test_process_initialize_ix_ok() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); process_instruction( @@ -1976,7 +1983,8 @@ mod tests { #[test] fn test_process_authorize_ix_ok() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); let accounts = process_instruction( @@ -2045,9 +2053,10 @@ mod tests { fn test_get_system_account_kind_nonce_ok() { let nonce_account = AccountSharedData::new_data( 42, - &nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::default(), - )), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::default()), + true, // separate_domains + ), &system_program::id(), ) .unwrap(); @@ -2060,7 +2069,9 @@ mod tests { #[test] fn test_get_system_account_kind_uninitialized_nonce_account_fail() { assert_eq!( - get_system_account_kind(&nonce_account::create_account(42).borrow()), + get_system_account_kind( + &nonce_account::create_account(42, /*separate_domains:*/ true).borrow() + ), None ); } @@ -2076,9 +2087,10 @@ mod tests { fn test_get_system_account_kind_nonsystem_owner_with_nonce_data_fail() { let nonce_account = AccountSharedData::new_data( 42, - &nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::default(), - )), + &nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::default()), + true, // separate_domains + ), &Pubkey::new_unique(), ) .unwrap(); @@ -2088,7 +2100,8 @@ mod tests { #[test] fn test_nonce_initialize_with_empty_recent_blockhashes_fail() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); #[allow(deprecated)] @@ -2128,7 +2141,8 @@ mod tests { #[test] fn test_nonce_advance_with_empty_recent_blockhashes_fail() { let nonce_address = Pubkey::new_unique(); - let nonce_account = nonce_account::create_account(1_000_000).into_inner(); + let nonce_account = + nonce_account::create_account(1_000_000, /*separate_domains:*/ true).into_inner(); #[allow(deprecated)] let blockhash_id = sysvar::recent_blockhashes::id(); let accounts = process_instruction( diff --git a/sdk/program/src/nonce/state/current.rs b/sdk/program/src/nonce/state/current.rs index 7465c74d2f91b6..0839ff9fa298a1 100644 --- a/sdk/program/src/nonce/state/current.rs +++ b/sdk/program/src/nonce/state/current.rs @@ -110,7 +110,10 @@ mod test { #[test] fn test_nonce_state_size() { - let data = Versions::new_current(State::Initialized(Data::default())); + let data = Versions::new( + State::Initialized(Data::default()), + true, // separate_domains + ); let size = bincode::serialized_size(&data).unwrap(); assert_eq!(State::size() as u64, size); } diff --git a/sdk/program/src/nonce/state/mod.rs b/sdk/program/src/nonce/state/mod.rs index 58d798976b4a49..fdeef759d371d6 100644 --- a/sdk/program/src/nonce/state/mod.rs +++ b/sdk/program/src/nonce/state/mod.rs @@ -2,21 +2,123 @@ mod current; pub use current::{Data, DurableNonce, State}; -use serde_derive::{Deserialize, Serialize}; +use { + crate::hash::Hash, + serde_derive::{Deserialize, Serialize}, +}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub enum Versions { + Legacy(Box), Current(Box), } impl Versions { - pub fn new_current(state: State) -> Self { - Self::Current(Box::new(state)) + pub fn new(state: State, separate_domains: bool) -> Self { + if separate_domains { + Self::Current(Box::new(state)) + } else { + Self::Legacy(Box::new(state)) + } + } + + pub fn state(&self) -> &State { + match self { + Self::Legacy(state) => state, + Self::Current(state) => state, + } } - pub fn convert_to_current(self) -> State { + pub fn separate_domains(&self) -> bool { match self { - Self::Current(state) => *state, + Self::Legacy(_) => false, + Self::Current(_) => true, + } + } + + // Returns true if the recent_blockhash field in Transaction verifies. + pub fn verify_recent_blockhash(&self, hash: &Hash, separate_domains: bool) -> bool { + let state = match self { + Self::Legacy(state) => { + if separate_domains { + // Legacy nonce accounts are invalid and should not + // allow durable transactions. + return false; + } else { + state + } + } + Self::Current(state) => state, + }; + match **state { + State::Uninitialized => false, + State::Initialized(ref data) => hash == &data.blockhash(), + } + } +} + +impl From for State { + fn from(versions: Versions) -> Self { + match versions { + Versions::Legacy(state) => *state, + Versions::Current(state) => *state, + } + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{fee_calculator::FeeCalculator, pubkey::Pubkey}, + }; + + #[test] + fn test_verify_recent_blockhash() { + let blockhash = Hash::from([171; 32]); + let versions = Versions::Legacy(Box::new(State::Uninitialized)); + for separate_domains in [false, true] { + assert!(!versions.verify_recent_blockhash(&blockhash, separate_domains)); + assert!(!versions.verify_recent_blockhash(&Hash::default(), separate_domains)); + } + let versions = Versions::Current(Box::new(State::Uninitialized)); + for separate_domains in [false, true] { + assert!(!versions.verify_recent_blockhash(&blockhash, separate_domains)); + assert!(!versions.verify_recent_blockhash(&Hash::default(), separate_domains)); + } + let durable_nonce = + DurableNonce::from_blockhash(&blockhash, /*separate_domains:*/ false); + let data = Data { + authority: Pubkey::new_unique(), + durable_nonce, + fee_calculator: FeeCalculator { + lamports_per_signature: 2718, + }, + }; + let versions = Versions::Legacy(Box::new(State::Initialized(data.clone()))); + let separate_domains = false; + assert!(!versions.verify_recent_blockhash(&Hash::default(), separate_domains)); + assert!(versions.verify_recent_blockhash(&blockhash, separate_domains)); + assert!(versions.verify_recent_blockhash(&data.blockhash(), separate_domains)); + assert!(versions.verify_recent_blockhash(durable_nonce.as_hash(), separate_domains)); + let separate_domains = true; + assert!(!versions.verify_recent_blockhash(&Hash::default(), separate_domains)); + assert!(!versions.verify_recent_blockhash(&blockhash, separate_domains)); + assert!(!versions.verify_recent_blockhash(&data.blockhash(), separate_domains)); + assert!(!versions.verify_recent_blockhash(durable_nonce.as_hash(), separate_domains)); + let durable_nonce = + DurableNonce::from_blockhash(&blockhash, /*separate_domains:*/ true); + assert_ne!(data.durable_nonce, durable_nonce); + let data = Data { + durable_nonce, + ..data + }; + let versions = Versions::Current(Box::new(State::Initialized(data.clone()))); + for separate_domains in [false, true] { + assert!(!versions.verify_recent_blockhash(&blockhash, separate_domains)); + assert!(!versions.verify_recent_blockhash(&Hash::default(), separate_domains)); + assert!(versions.verify_recent_blockhash(&data.blockhash(), separate_domains)); + assert!(versions.verify_recent_blockhash(durable_nonce.as_hash(), separate_domains)); } } } diff --git a/sdk/src/nonce_account.rs b/sdk/src/nonce_account.rs index 7dd642d96d15f7..3c250c35a0b92c 100644 --- a/sdk/src/nonce_account.rs +++ b/sdk/src/nonce_account.rs @@ -8,11 +8,11 @@ use { std::cell::RefCell, }; -pub fn create_account(lamports: u64) -> RefCell { +pub fn create_account(lamports: u64, separate_domains: bool) -> RefCell { RefCell::new( AccountSharedData::new_data_with_space( lamports, - &Versions::new_current(State::Uninitialized), + &Versions::new(State::Uninitialized, separate_domains), State::size(), &crate::system_program::id(), ) @@ -20,22 +20,19 @@ pub fn create_account(lamports: u64) -> RefCell { ) } -// TODO: Consider changing argument from Hash to DurableNonce. -pub fn verify_nonce_account(acc: &AccountSharedData, hash: &Hash) -> bool { +pub fn verify_nonce_account(acc: &AccountSharedData, hash: &Hash, separate_domains: bool) -> bool { if acc.owner() != &crate::system_program::id() { return false; } - match StateMut::::state(acc).map(|v| v.convert_to_current()) { - Ok(State::Initialized(ref data)) => hash == &data.blockhash(), - _ => false, + match StateMut::::state(acc) { + Ok(versions) => versions.verify_recent_blockhash(hash, separate_domains), + Err(_) => false, } } pub fn lamports_per_signature_of(account: &AccountSharedData) -> Option { - let state = StateMut::::state(account) - .ok()? - .convert_to_current(); - match state { + let versions = StateMut::::state(account).ok()?; + match versions.state() { State::Initialized(data) => Some(data.fee_calculator.lamports_per_signature), _ => None, } @@ -43,7 +40,15 @@ pub fn lamports_per_signature_of(account: &AccountSharedData) -> Option { #[cfg(test)] mod tests { - use {super::*, crate::pubkey::Pubkey}; + use { + super::*, + crate::{ + fee_calculator::FeeCalculator, + nonce::state::{Data, DurableNonce}, + pubkey::Pubkey, + system_program, + }, + }; #[test] fn test_verify_bad_account_owner_fails() { @@ -51,11 +56,139 @@ mod tests { assert_ne!(program_id, crate::system_program::id()); let account = AccountSharedData::new_data_with_space( 42, - &Versions::new_current(State::Uninitialized), + &Versions::new(State::Uninitialized, /*separate_domains:*/ true), State::size(), &program_id, ) .expect("nonce_account"); - assert!(!verify_nonce_account(&account, &Hash::default())); + for separate_domains in [false, true] { + assert!(!verify_nonce_account( + &account, + &Hash::default(), + separate_domains + )); + } + } + + fn new_nonce_account(versions: Versions) -> AccountSharedData { + AccountSharedData::new_data( + 1_000_000, // lamports + &versions, // state + &system_program::id(), // owner + ) + .unwrap() + } + + #[test] + fn test_verify_nonce_account() { + let blockhash = Hash::from([171; 32]); + let versions = Versions::Legacy(Box::new(State::Uninitialized)); + let account = new_nonce_account(versions); + for separate_domains in [false, true] { + assert!(!verify_nonce_account( + &account, + &blockhash, + separate_domains + )); + assert!(!verify_nonce_account( + &account, + &Hash::default(), + separate_domains + )); + } + let versions = Versions::Current(Box::new(State::Uninitialized)); + let account = new_nonce_account(versions); + for separate_domains in [false, true] { + assert!(!verify_nonce_account( + &account, + &blockhash, + separate_domains + )); + assert!(!verify_nonce_account( + &account, + &Hash::default(), + separate_domains + )); + } + let durable_nonce = + DurableNonce::from_blockhash(&blockhash, /*separate_domains:*/ false); + let data = Data { + authority: Pubkey::new_unique(), + durable_nonce, + fee_calculator: FeeCalculator { + lamports_per_signature: 2718, + }, + }; + let versions = Versions::Legacy(Box::new(State::Initialized(data.clone()))); + let account = new_nonce_account(versions); + let separate_domains = false; + assert!(verify_nonce_account(&account, &blockhash, separate_domains)); + assert!(!verify_nonce_account( + &account, + &Hash::default(), + separate_domains + )); + assert!(verify_nonce_account( + &account, + &data.blockhash(), + separate_domains + )); + assert!(verify_nonce_account( + &account, + durable_nonce.as_hash(), + separate_domains + )); + let separate_domains = true; + assert!(!verify_nonce_account( + &account, + &blockhash, + separate_domains + )); + assert!(!verify_nonce_account( + &account, + &Hash::default(), + separate_domains + )); + assert!(!verify_nonce_account( + &account, + &data.blockhash(), + separate_domains + )); + assert!(!verify_nonce_account( + &account, + durable_nonce.as_hash(), + separate_domains + )); + let durable_nonce = + DurableNonce::from_blockhash(&blockhash, /*separate_domains:*/ true); + assert_ne!(data.durable_nonce, durable_nonce); + let data = Data { + durable_nonce, + ..data + }; + let versions = Versions::Current(Box::new(State::Initialized(data.clone()))); + let account = new_nonce_account(versions); + for separate_domains in [false, true] { + assert!(!verify_nonce_account( + &account, + &blockhash, + separate_domains + )); + assert!(!verify_nonce_account( + &account, + &Hash::default(), + separate_domains + )); + assert!(verify_nonce_account( + &account, + &data.blockhash(), + separate_domains + )); + assert!(verify_nonce_account( + &account, + durable_nonce.as_hash(), + separate_domains + )); + } } } diff --git a/send-transaction-service/src/send_transaction_service.rs b/send-transaction-service/src/send_transaction_service.rs index 3b674020b1136c..3014692d28b3fe 100644 --- a/send-transaction-service/src/send_transaction_service.rs +++ b/send-transaction-service/src/send_transaction_service.rs @@ -597,10 +597,12 @@ impl SendTransactionService { .last_sent_time .map(|last| now.duration_since(last) >= retry_rate) .unwrap_or(false); - if !nonce_account::verify_nonce_account(&nonce_account, &durable_nonce) - && signature_status.is_none() - && expired - { + let verify_nonce_account = nonce_account::verify_nonce_account( + &nonce_account, + &durable_nonce, + working_bank.separate_nonce_from_blockhash(), + ); + if !verify_nonce_account && signature_status.is_none() && expired { info!("Dropping expired durable-nonce transaction: {}", signature); result.expired += 1; stats.expired_transactions.fetch_add(1, Ordering::Relaxed); @@ -1074,9 +1076,14 @@ mod test { let nonce_address = Pubkey::new_unique(); let durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); - let nonce_state = nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::new(Pubkey::default(), durable_nonce, 42), - )); + let nonce_state = nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::new( + Pubkey::default(), + durable_nonce, + 42, + )), + true, // separate_domains + ); let nonce_account = AccountSharedData::new_data(43, &nonce_state, &system_program::id()).unwrap(); root_bank.store_account(&nonce_address, &nonce_account); @@ -1318,9 +1325,14 @@ mod test { } let new_durable_nonce = DurableNonce::from_blockhash(&Hash::new_unique(), /*separate_domains:*/ true); - let new_nonce_state = nonce::state::Versions::new_current(nonce::State::Initialized( - nonce::state::Data::new(Pubkey::default(), new_durable_nonce, 42), - )); + let new_nonce_state = nonce::state::Versions::new( + nonce::State::Initialized(nonce::state::Data::new( + Pubkey::default(), + new_durable_nonce, + 42, + )), + true, // separate_domains + ); let nonce_account = AccountSharedData::new_data(43, &new_nonce_state, &system_program::id()).unwrap(); working_bank.store_account(&nonce_address, &nonce_account);