From 17de653ce00da17f51d19ad8eb42763148d8b0ee Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2020 06:25:25 +0000 Subject: [PATCH] Move nonce utils from runtime to sdk (bp #12577) (#12583) * runtime: Move prepare_if_nonce_account into accounts (cherry picked from commit caec631344a6e7a37646b7fda79d75f0a4dc4d6d) * Move nonced tx helpers to SDK (cherry picked from commit 65b868f4eb372c3a9b291dbf39d4f070d0c535a0) * Move remaining nonce utils from runtime to SDK (cherry picked from commit 3c7b9c29387df4a7c90aec5377836ef9d169d608) # Conflicts: # runtime/src/bank.rs * Fix conflict Co-authored-by: Trent Nelson Co-authored-by: Tyera Eulberg --- core/src/transaction_status_service.rs | 4 +- runtime/src/accounts.rs | 204 ++++++++++++- runtime/src/bank.rs | 11 +- runtime/src/lib.rs | 1 - runtime/src/nonce_utils.rs | 407 ------------------------- sdk/src/nonce/mod.rs | 1 + sdk/src/nonce/utils.rs | 86 ++++++ sdk/src/transaction.rs | 123 ++++++++ 8 files changed, 416 insertions(+), 421 deletions(-) delete mode 100644 runtime/src/nonce_utils.rs create mode 100644 sdk/src/nonce/utils.rs diff --git a/core/src/transaction_status_service.rs b/core/src/transaction_status_service.rs index b1ce0b38b8..39f7b94180 100644 --- a/core/src/transaction_status_service.rs +++ b/core/src/transaction_status_service.rs @@ -3,9 +3,9 @@ use itertools::izip; use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch}; use solana_runtime::{ bank::{Bank, HashAgeKind}, - nonce_utils, transaction_utils::OrderedIterator, }; +use solana_sdk::nonce; use solana_transaction_status::{InnerInstructions, TransactionStatusMeta}; use std::{ sync::{ @@ -75,7 +75,7 @@ impl TransactionStatusService { if Bank::can_commit(&status) && !transaction.signatures.is_empty() { let fee_calculator = match hash_age_kind { Some(HashAgeKind::DurableNonce(_, account)) => { - nonce_utils::fee_calculator_of(&account) + nonce::utils::fee_calculator_of(&account) } _ => bank.get_fee_calculator(&transaction.message().recent_blockhash), } diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index 191fe78ed9..6561b7e16d 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -7,7 +7,6 @@ use crate::{ bank::{HashAgeKind, TransactionProcessResult}, blockhash_queue::BlockhashQueue, feature_set::{self, FeatureSet}, - nonce_utils, rent_collector::RentCollector, system_instruction_processor::{get_system_account_kind, SystemAccountKind}, transaction_utils::OrderedIterator, @@ -17,6 +16,7 @@ use rand::{thread_rng, Rng}; use rayon::slice::ParallelSliceMut; use solana_sdk::{ account::Account, + account_utils::StateMut, clock::{Epoch, Slot}, fee_calculator::{FeeCalculator, FeeConfig}, genesis_config::ClusterType, @@ -315,7 +315,7 @@ impl Accounts { ((_, tx), (Ok(()), hash_age_kind)) => { let fee_calculator = match hash_age_kind.as_ref() { Some(HashAgeKind::DurableNonce(_, account)) => { - nonce_utils::fee_calculator_of(account) + nonce::utils::fee_calculator_of(account) } _ => hash_queue .get_fee_calculator(&tx.message().recent_blockhash) @@ -797,7 +797,7 @@ impl Accounts { .zip(acc.0.iter_mut()) .filter(|((i, key), _account)| Self::is_non_loader_key(message, key, *i)) { - nonce_utils::prepare_if_nonce_account( + prepare_if_nonce_account( account, key, res, @@ -817,6 +817,46 @@ impl Accounts { } } +pub fn prepare_if_nonce_account( + account: &mut Account, + account_pubkey: &Pubkey, + tx_result: &Result<()>, + maybe_nonce: Option<(&Pubkey, &Account)>, + last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), + fix_recent_blockhashes_sysvar_delay: bool, +) { + if let Some((nonce_key, nonce_acc)) = maybe_nonce { + if account_pubkey == nonce_key { + let overwrite = if tx_result.is_err() { + // Nonce TX failed with an InstructionError. Roll back + // its account state + *account = nonce_acc.clone(); + true + } else { + // Retain overwrite on successful transactions until + // recent_blockhashes_sysvar_delay fix is activated + !fix_recent_blockhashes_sysvar_delay + }; + if overwrite { + // Since hash_age_kind is DurableNonce, unwrap is safe here + let state = StateMut::::state(nonce_acc) + .unwrap() + .convert_to_current(); + if let nonce::State::Initialized(ref data) = state { + let new_data = nonce::state::Versions::new_current(nonce::State::Initialized( + nonce::state::Data { + blockhash: last_blockhash_with_fee_calculator.0, + fee_calculator: last_blockhash_with_fee_calculator.1.clone(), + ..data.clone() + }, + )); + account.set_state(&new_data).unwrap(); + } + } + } + } +} + pub fn create_test_accounts( accounts: &Accounts, pubkeys: &mut Vec, @@ -851,13 +891,12 @@ mod tests { fee_calculator::FeeCalculator, genesis_config::ClusterType, hash::Hash, - instruction::CompiledInstruction, + instruction::{CompiledInstruction, InstructionError}, message::Message, nonce, rent::Rent, signature::{Keypair, Signer}, system_program, - transaction::Transaction, }; use std::{ sync::atomic::{AtomicBool, AtomicU64, Ordering}, @@ -1856,4 +1895,159 @@ mod tests { assert_eq!(loaded_accounts.len(), 1); assert!(loaded_accounts[0].0.is_err()); } + + fn create_accounts_prepare_if_nonce_account() -> (Pubkey, Account, Account, Hash, FeeCalculator) + { + let data = nonce::state::Versions::new_current(nonce::State::Initialized( + nonce::state::Data::default(), + )); + let account = Account::new_data(42, &data, &system_program::id()).unwrap(); + let pre_account = Account { + lamports: 43, + ..account.clone() + }; + ( + Pubkey::default(), + pre_account, + account, + Hash::new(&[1u8; 32]), + FeeCalculator { + lamports_per_signature: 1234, + }, + ) + } + + fn run_prepare_if_nonce_account_test( + account: &mut Account, + account_pubkey: &Pubkey, + tx_result: &Result<()>, + maybe_nonce: Option<(&Pubkey, &Account)>, + last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), + expect_account: &Account, + ) -> bool { + // Verify expect_account's relationship + match maybe_nonce { + Some((nonce_pubkey, _nonce_account)) + if nonce_pubkey == account_pubkey && tx_result.is_ok() => + { + assert_eq!(expect_account, account) // Account update occurs in system_instruction_processor + } + Some((nonce_pubkey, nonce_account)) if nonce_pubkey == account_pubkey => { + assert_ne!(expect_account, nonce_account) + } + _ => assert_eq!(expect_account, account), + } + + prepare_if_nonce_account( + account, + account_pubkey, + tx_result, + maybe_nonce, + last_blockhash_with_fee_calculator, + true, + ); + expect_account == account + } + + #[test] + fn test_prepare_if_nonce_account_expected() { + let ( + pre_account_pubkey, + pre_account, + mut post_account, + last_blockhash, + last_fee_calculator, + ) = create_accounts_prepare_if_nonce_account(); + let post_account_pubkey = pre_account_pubkey; + + let mut expect_account = post_account.clone(); + let data = nonce::state::Versions::new_current(nonce::State::Initialized( + nonce::state::Data::default(), + )); + expect_account.set_state(&data).unwrap(); + + assert!(run_prepare_if_nonce_account_test( + &mut post_account, + &post_account_pubkey, + &Ok(()), + Some((&pre_account_pubkey, &pre_account)), + &(last_blockhash, last_fee_calculator), + &expect_account, + )); + } + + #[test] + fn test_prepare_if_nonce_account_not_nonce_tx() { + let (pre_account_pubkey, _pre_account, _post_account, last_blockhash, last_fee_calculator) = + create_accounts_prepare_if_nonce_account(); + let post_account_pubkey = pre_account_pubkey; + + let mut post_account = Account::default(); + let expect_account = post_account.clone(); + assert!(run_prepare_if_nonce_account_test( + &mut post_account, + &post_account_pubkey, + &Ok(()), + None, + &(last_blockhash, last_fee_calculator), + &expect_account, + )); + } + + #[test] + fn test_prepare_if_nonce_account_not_nonce_pubkey() { + let ( + pre_account_pubkey, + pre_account, + mut post_account, + last_blockhash, + last_fee_calculator, + ) = create_accounts_prepare_if_nonce_account(); + + let expect_account = post_account.clone(); + // Wrong key + assert!(run_prepare_if_nonce_account_test( + &mut post_account, + &Pubkey::new(&[1u8; 32]), + &Ok(()), + Some((&pre_account_pubkey, &pre_account)), + &(last_blockhash, last_fee_calculator), + &expect_account, + )); + } + + #[test] + fn test_prepare_if_nonce_account_tx_error() { + let ( + pre_account_pubkey, + pre_account, + mut post_account, + last_blockhash, + last_fee_calculator, + ) = create_accounts_prepare_if_nonce_account(); + let post_account_pubkey = pre_account_pubkey; + + let mut expect_account = pre_account.clone(); + expect_account + .set_state(&nonce::state::Versions::new_current( + nonce::State::Initialized(nonce::state::Data { + blockhash: last_blockhash, + fee_calculator: last_fee_calculator.clone(), + ..nonce::state::Data::default() + }), + )) + .unwrap(); + + assert!(run_prepare_if_nonce_account_test( + &mut post_account, + &post_account_pubkey, + &Err(TransactionError::InstructionError( + 0, + InstructionError::InvalidArgument, + )), + Some((&pre_account_pubkey, &pre_account)), + &(last_blockhash, last_fee_calculator), + &expect_account, + )); + } } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index fa5cb9cd08..c7ca6b56c5 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -17,7 +17,6 @@ use crate::{ instruction_recorder::InstructionRecorder, log_collector::LogCollector, message_processor::{Executors, MessageProcessor}, - nonce_utils, process_instruction::{ ComputeBudget, Executor, ProcessInstruction, ProcessInstructionWithContext, }, @@ -63,7 +62,7 @@ use solana_sdk::{ system_transaction, sysvar::{self, Sysvar}, timing::years_as_slots, - transaction::{Result, Transaction, TransactionError}, + transaction::{self, Result, Transaction, TransactionError}, }; use solana_stake_program::stake_state::{self, Delegation, PointValue}; use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::VoteState}; @@ -1852,14 +1851,14 @@ impl Bank { } pub fn check_tx_durable_nonce(&self, tx: &Transaction) -> Option<(Pubkey, Account)> { - nonce_utils::transaction_uses_durable_nonce(&tx) - .and_then(|nonce_ix| nonce_utils::get_nonce_pubkey_from_instruction(&nonce_ix, &tx)) + transaction::uses_durable_nonce(&tx) + .and_then(|nonce_ix| transaction::get_nonce_pubkey_from_instruction(&nonce_ix, &tx)) .and_then(|nonce_pubkey| { self.get_account(&nonce_pubkey) .map(|acc| (*nonce_pubkey, acc)) }) .filter(|(_pubkey, nonce_account)| { - nonce_utils::verify_nonce_account(nonce_account, &tx.message().recent_blockhash) + nonce::utils::verify_nonce_account(nonce_account, &tx.message().recent_blockhash) }) } @@ -2275,7 +2274,7 @@ impl Bank { .map(|((_, tx), (res, hash_age_kind))| { let (fee_calculator, is_durable_nonce) = match hash_age_kind { Some(HashAgeKind::DurableNonce(_, account)) => { - (nonce_utils::fee_calculator_of(account), true) + (nonce::utils::fee_calculator_of(account), true) } _ => ( hash_queue diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4f4c01ec0a..8266f5ad93 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -21,7 +21,6 @@ pub mod loader_utils; pub mod log_collector; pub mod message_processor; mod native_loader; -pub mod nonce_utils; pub mod process_instruction; pub mod rent_collector; pub mod serde_snapshot; diff --git a/runtime/src/nonce_utils.rs b/runtime/src/nonce_utils.rs deleted file mode 100644 index 9fe3fd9d29..0000000000 --- a/runtime/src/nonce_utils.rs +++ /dev/null @@ -1,407 +0,0 @@ -use solana_sdk::{ - account::Account, - account_utils::StateMut, - fee_calculator::FeeCalculator, - hash::Hash, - instruction::CompiledInstruction, - nonce::{self, state::Versions, State}, - program_utils::limited_deserialize, - pubkey::Pubkey, - system_instruction::SystemInstruction, - system_program, - transaction::{self, Transaction}, -}; - -pub fn transaction_uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> { - let message = tx.message(); - message - .instructions - .get(0) - .filter(|maybe_ix| { - let prog_id_idx = maybe_ix.program_id_index as usize; - match message.account_keys.get(prog_id_idx) { - Some(program_id) => system_program::check_id(&program_id), - _ => false, - } - } && matches!(limited_deserialize(&maybe_ix.data), Ok(SystemInstruction::AdvanceNonceAccount)) - ) -} - -pub fn get_nonce_pubkey_from_instruction<'a>( - ix: &CompiledInstruction, - tx: &'a Transaction, -) -> Option<&'a Pubkey> { - ix.accounts.get(0).and_then(|idx| { - let idx = *idx as usize; - tx.message().account_keys.get(idx) - }) -} - -pub fn verify_nonce_account(acc: &Account, hash: &Hash) -> bool { - match StateMut::::state(acc).map(|v| v.convert_to_current()) { - Ok(State::Initialized(ref data)) => *hash == data.blockhash, - _ => false, - } -} - -pub fn prepare_if_nonce_account( - account: &mut Account, - account_pubkey: &Pubkey, - tx_result: &transaction::Result<()>, - maybe_nonce: Option<(&Pubkey, &Account)>, - last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), - fix_recent_blockhashes_sysvar_delay: bool, -) { - if let Some((nonce_key, nonce_acc)) = maybe_nonce { - if account_pubkey == nonce_key { - let overwrite = if tx_result.is_err() { - // Nonce TX failed with an InstructionError. Roll back - // its account state - *account = nonce_acc.clone(); - true - } else { - // Retain overwrite on successful transactions until - // recent_blockhashes_sysvar_delay fix is activated - !fix_recent_blockhashes_sysvar_delay - }; - if overwrite { - // Since hash_age_kind is DurableNonce, unwrap is safe here - let state = StateMut::::state(nonce_acc) - .unwrap() - .convert_to_current(); - if let State::Initialized(ref data) = state { - let new_data = Versions::new_current(State::Initialized(nonce::state::Data { - blockhash: last_blockhash_with_fee_calculator.0, - fee_calculator: last_blockhash_with_fee_calculator.1.clone(), - ..data.clone() - })); - account.set_state(&new_data).unwrap(); - } - } - } - } -} - -pub fn fee_calculator_of(account: &Account) -> Option { - let state = StateMut::::state(account) - .ok()? - .convert_to_current(); - match state { - State::Initialized(data) => Some(data.fee_calculator), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use solana_sdk::{ - account::Account, - account_utils::State as AccountUtilsState, - hash::Hash, - instruction::InstructionError, - message::Message, - nonce::{self, account::with_test_keyed_account, Account as NonceAccount, State}, - pubkey::Pubkey, - signature::{Keypair, Signer}, - system_instruction, - sysvar::{recent_blockhashes::create_test_recent_blockhashes, rent::Rent}, - }; - use std::collections::HashSet; - - fn nonced_transfer_tx() -> (Pubkey, Pubkey, Transaction) { - let from_keypair = Keypair::new(); - let from_pubkey = from_keypair.pubkey(); - let nonce_keypair = Keypair::new(); - let nonce_pubkey = nonce_keypair.pubkey(); - let instructions = [ - system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey), - system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), - ]; - let message = Message::new(&instructions, Some(&nonce_pubkey)); - let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); - (from_pubkey, nonce_pubkey, tx) - } - - #[test] - fn tx_uses_nonce_ok() { - let (_, _, tx) = nonced_transfer_tx(); - assert!(transaction_uses_durable_nonce(&tx).is_some()); - } - - #[test] - fn tx_uses_nonce_empty_ix_fail() { - assert!(transaction_uses_durable_nonce(&Transaction::default()).is_none()); - } - - #[test] - fn tx_uses_nonce_bad_prog_id_idx_fail() { - let (_, _, mut tx) = nonced_transfer_tx(); - tx.message.instructions.get_mut(0).unwrap().program_id_index = 255u8; - assert!(transaction_uses_durable_nonce(&tx).is_none()); - } - - #[test] - fn tx_uses_nonce_first_prog_id_not_nonce_fail() { - let from_keypair = Keypair::new(); - let from_pubkey = from_keypair.pubkey(); - let nonce_keypair = Keypair::new(); - let nonce_pubkey = nonce_keypair.pubkey(); - let instructions = [ - system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), - system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey), - ]; - let message = Message::new(&instructions, Some(&from_pubkey)); - let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); - assert!(transaction_uses_durable_nonce(&tx).is_none()); - } - - #[test] - fn tx_uses_nonce_wrong_first_nonce_ix_fail() { - let from_keypair = Keypair::new(); - let from_pubkey = from_keypair.pubkey(); - let nonce_keypair = Keypair::new(); - let nonce_pubkey = nonce_keypair.pubkey(); - let instructions = [ - system_instruction::withdraw_nonce_account( - &nonce_pubkey, - &nonce_pubkey, - &from_pubkey, - 42, - ), - system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), - ]; - let message = Message::new(&instructions, Some(&nonce_pubkey)); - let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); - assert!(transaction_uses_durable_nonce(&tx).is_none()); - } - - #[test] - fn get_nonce_pub_from_ix_ok() { - let (_, nonce_pubkey, tx) = nonced_transfer_tx(); - let nonce_ix = transaction_uses_durable_nonce(&tx).unwrap(); - assert_eq!( - get_nonce_pubkey_from_instruction(&nonce_ix, &tx), - Some(&nonce_pubkey), - ); - } - - #[test] - fn get_nonce_pub_from_ix_no_accounts_fail() { - let (_, _, tx) = nonced_transfer_tx(); - let nonce_ix = transaction_uses_durable_nonce(&tx).unwrap(); - let mut nonce_ix = nonce_ix.clone(); - nonce_ix.accounts.clear(); - assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,); - } - - #[test] - fn get_nonce_pub_from_ix_bad_acc_idx_fail() { - let (_, _, tx) = nonced_transfer_tx(); - let nonce_ix = transaction_uses_durable_nonce(&tx).unwrap(); - let mut nonce_ix = nonce_ix.clone(); - nonce_ix.accounts[0] = 255u8; - assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,); - } - - #[test] - fn verify_nonce_ok() { - with_test_keyed_account(42, true, |nonce_account| { - let mut signers = HashSet::new(); - signers.insert(nonce_account.signer_key().unwrap()); - let state: State = nonce_account.state().unwrap(); - // New is in Uninitialzed state - assert_eq!(state, State::Uninitialized); - let recent_blockhashes = create_test_recent_blockhashes(0); - let authorized = nonce_account.unsigned_key(); - nonce_account - .initialize_nonce_account(&authorized, &recent_blockhashes, &Rent::free()) - .unwrap(); - assert!(verify_nonce_account( - &nonce_account.account.borrow(), - &recent_blockhashes[0].blockhash, - )); - }); - } - - #[test] - fn verify_nonce_bad_acc_state_fail() { - with_test_keyed_account(42, true, |nonce_account| { - assert!(!verify_nonce_account( - &nonce_account.account.borrow(), - &Hash::default() - )); - }); - } - - #[test] - fn verify_nonce_bad_query_hash_fail() { - with_test_keyed_account(42, true, |nonce_account| { - let mut signers = HashSet::new(); - signers.insert(nonce_account.signer_key().unwrap()); - let state: State = nonce_account.state().unwrap(); - // New is in Uninitialzed state - assert_eq!(state, State::Uninitialized); - let recent_blockhashes = create_test_recent_blockhashes(0); - let authorized = nonce_account.unsigned_key(); - nonce_account - .initialize_nonce_account(&authorized, &recent_blockhashes, &Rent::free()) - .unwrap(); - assert!(!verify_nonce_account( - &nonce_account.account.borrow(), - &recent_blockhashes[1].blockhash, - )); - }); - } - - fn create_accounts_prepare_if_nonce_account() -> (Pubkey, Account, Account, Hash, FeeCalculator) - { - let data = Versions::new_current(State::Initialized(nonce::state::Data::default())); - let account = Account::new_data(42, &data, &system_program::id()).unwrap(); - let pre_account = Account { - lamports: 43, - ..account.clone() - }; - ( - Pubkey::default(), - pre_account, - account, - Hash::new(&[1u8; 32]), - FeeCalculator { - lamports_per_signature: 1234, - }, - ) - } - - fn run_prepare_if_nonce_account_test( - account: &mut Account, - account_pubkey: &Pubkey, - tx_result: &transaction::Result<()>, - maybe_nonce: Option<(&Pubkey, &Account)>, - last_blockhash_with_fee_calculator: &(Hash, FeeCalculator), - expect_account: &Account, - ) -> bool { - // Verify expect_account's relationship - match maybe_nonce { - Some((nonce_pubkey, _nonce_account)) - if nonce_pubkey == account_pubkey && tx_result.is_ok() => - { - assert_eq!(expect_account, account) // Account update occurs in system_instruction_processor - } - Some((nonce_pubkey, nonce_account)) if nonce_pubkey == account_pubkey => { - assert_ne!(expect_account, nonce_account) - } - _ => assert_eq!(expect_account, account), - } - - prepare_if_nonce_account( - account, - account_pubkey, - tx_result, - maybe_nonce, - last_blockhash_with_fee_calculator, - true, - ); - expect_account == account - } - - #[test] - fn test_prepare_if_nonce_account_expected() { - let ( - pre_account_pubkey, - pre_account, - mut post_account, - last_blockhash, - last_fee_calculator, - ) = create_accounts_prepare_if_nonce_account(); - let post_account_pubkey = pre_account_pubkey; - - let mut expect_account = post_account.clone(); - let data = Versions::new_current(State::Initialized(nonce::state::Data::default())); - expect_account.set_state(&data).unwrap(); - - assert!(run_prepare_if_nonce_account_test( - &mut post_account, - &post_account_pubkey, - &Ok(()), - Some((&pre_account_pubkey, &pre_account)), - &(last_blockhash, last_fee_calculator), - &expect_account, - )); - } - - #[test] - fn test_prepare_if_nonce_account_not_nonce_tx() { - let (pre_account_pubkey, _pre_account, _post_account, last_blockhash, last_fee_calculator) = - create_accounts_prepare_if_nonce_account(); - let post_account_pubkey = pre_account_pubkey; - - let mut post_account = Account::default(); - let expect_account = post_account.clone(); - assert!(run_prepare_if_nonce_account_test( - &mut post_account, - &post_account_pubkey, - &Ok(()), - None, - &(last_blockhash, last_fee_calculator), - &expect_account, - )); - } - - #[test] - fn test_prepare_if_nonce_account_not_nonce_pubkey() { - let ( - pre_account_pubkey, - pre_account, - mut post_account, - last_blockhash, - last_fee_calculator, - ) = create_accounts_prepare_if_nonce_account(); - - let expect_account = post_account.clone(); - // Wrong key - assert!(run_prepare_if_nonce_account_test( - &mut post_account, - &Pubkey::new(&[1u8; 32]), - &Ok(()), - Some((&pre_account_pubkey, &pre_account)), - &(last_blockhash, last_fee_calculator), - &expect_account, - )); - } - - #[test] - fn test_prepare_if_nonce_account_tx_error() { - let ( - pre_account_pubkey, - pre_account, - mut post_account, - last_blockhash, - last_fee_calculator, - ) = create_accounts_prepare_if_nonce_account(); - let post_account_pubkey = pre_account_pubkey; - - let mut expect_account = pre_account.clone(); - expect_account - .set_state(&Versions::new_current(State::Initialized( - nonce::state::Data { - blockhash: last_blockhash, - fee_calculator: last_fee_calculator.clone(), - ..nonce::state::Data::default() - }, - ))) - .unwrap(); - - assert!(run_prepare_if_nonce_account_test( - &mut post_account, - &post_account_pubkey, - &Err(transaction::TransactionError::InstructionError( - 0, - InstructionError::InvalidArgument, - )), - Some((&pre_account_pubkey, &pre_account)), - &(last_blockhash, last_fee_calculator), - &expect_account, - )); - } -} diff --git a/sdk/src/nonce/mod.rs b/sdk/src/nonce/mod.rs index c38595384a..f9a34f27cd 100644 --- a/sdk/src/nonce/mod.rs +++ b/sdk/src/nonce/mod.rs @@ -2,3 +2,4 @@ pub mod account; pub use account::{create_account, Account}; pub mod state; pub use state::State; +pub mod utils; diff --git a/sdk/src/nonce/utils.rs b/sdk/src/nonce/utils.rs new file mode 100644 index 0000000000..9c6ccfc2d3 --- /dev/null +++ b/sdk/src/nonce/utils.rs @@ -0,0 +1,86 @@ +use solana_sdk::{ + account::Account, + account_utils::StateMut, + fee_calculator::FeeCalculator, + hash::Hash, + nonce::{state::Versions, State}, +}; + +pub fn verify_nonce_account(acc: &Account, hash: &Hash) -> bool { + match StateMut::::state(acc).map(|v| v.convert_to_current()) { + Ok(State::Initialized(ref data)) => *hash == data.blockhash, + _ => false, + } +} + +pub fn fee_calculator_of(account: &Account) -> Option { + let state = StateMut::::state(account) + .ok()? + .convert_to_current(); + match state { + State::Initialized(data) => Some(data.fee_calculator), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use solana_sdk::{ + account_utils::State as AccountUtilsState, + hash::Hash, + nonce::{account::with_test_keyed_account, Account as NonceAccount, State}, + sysvar::{recent_blockhashes::create_test_recent_blockhashes, rent::Rent}, + }; + use std::collections::HashSet; + + #[test] + fn verify_nonce_ok() { + with_test_keyed_account(42, true, |nonce_account| { + let mut signers = HashSet::new(); + signers.insert(nonce_account.signer_key().unwrap()); + let state: State = nonce_account.state().unwrap(); + // New is in Uninitialzed state + assert_eq!(state, State::Uninitialized); + let recent_blockhashes = create_test_recent_blockhashes(0); + let authorized = nonce_account.unsigned_key(); + nonce_account + .initialize_nonce_account(&authorized, &recent_blockhashes, &Rent::free()) + .unwrap(); + assert!(verify_nonce_account( + &nonce_account.account.borrow(), + &recent_blockhashes[0].blockhash, + )); + }); + } + + #[test] + fn verify_nonce_bad_acc_state_fail() { + with_test_keyed_account(42, true, |nonce_account| { + assert!(!verify_nonce_account( + &nonce_account.account.borrow(), + &Hash::default() + )); + }); + } + + #[test] + fn verify_nonce_bad_query_hash_fail() { + with_test_keyed_account(42, true, |nonce_account| { + let mut signers = HashSet::new(); + signers.insert(nonce_account.signer_key().unwrap()); + let state: State = nonce_account.state().unwrap(); + // New is in Uninitialzed state + assert_eq!(state, State::Uninitialized); + let recent_blockhashes = create_test_recent_blockhashes(0); + let authorized = nonce_account.unsigned_key(); + nonce_account + .initialize_nonce_account(&authorized, &recent_blockhashes, &Rent::free()) + .unwrap(); + assert!(!verify_nonce_account( + &nonce_account.account.borrow(), + &recent_blockhashes[1].blockhash, + )); + }); + } +} diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs index e9e61a8531..69c0c09dae 100644 --- a/sdk/src/transaction.rs +++ b/sdk/src/transaction.rs @@ -6,10 +6,13 @@ use crate::{ hash::Hash, instruction::{CompiledInstruction, Instruction, InstructionError}, message::Message, + program_utils::limited_deserialize, pubkey::Pubkey, short_vec, signature::{Signature, SignerError}, signers::Signers, + system_instruction::SystemInstruction, + system_program, }; use std::result; use thiserror::Error; @@ -395,6 +398,31 @@ impl Transaction { } } +pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> { + let message = tx.message(); + message + .instructions + .get(0) + .filter(|maybe_ix| { + let prog_id_idx = maybe_ix.program_id_index as usize; + match message.account_keys.get(prog_id_idx) { + Some(program_id) => system_program::check_id(&program_id), + _ => false, + } + } && matches!(limited_deserialize(&maybe_ix.data), Ok(SystemInstruction::AdvanceNonceAccount)) + ) +} + +pub fn get_nonce_pubkey_from_instruction<'a>( + ix: &CompiledInstruction, + tx: &'a Transaction, +) -> Option<&'a Pubkey> { + ix.accounts.get(0).and_then(|idx| { + let idx = *idx as usize; + tx.message().account_keys.get(idx) + }) +} + #[cfg(test)] mod tests { use super::*; @@ -812,4 +840,99 @@ mod tests { vec![Signature::default(), Signature::default()] ); } + + fn nonced_transfer_tx() -> (Pubkey, Pubkey, Transaction) { + let from_keypair = Keypair::new(); + let from_pubkey = from_keypair.pubkey(); + let nonce_keypair = Keypair::new(); + let nonce_pubkey = nonce_keypair.pubkey(); + let instructions = [ + system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey), + system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), + ]; + let message = Message::new(&instructions, Some(&nonce_pubkey)); + let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); + (from_pubkey, nonce_pubkey, tx) + } + + #[test] + fn tx_uses_nonce_ok() { + let (_, _, tx) = nonced_transfer_tx(); + assert!(uses_durable_nonce(&tx).is_some()); + } + + #[test] + fn tx_uses_nonce_empty_ix_fail() { + assert!(uses_durable_nonce(&Transaction::default()).is_none()); + } + + #[test] + fn tx_uses_nonce_bad_prog_id_idx_fail() { + let (_, _, mut tx) = nonced_transfer_tx(); + tx.message.instructions.get_mut(0).unwrap().program_id_index = 255u8; + assert!(uses_durable_nonce(&tx).is_none()); + } + + #[test] + fn tx_uses_nonce_first_prog_id_not_nonce_fail() { + let from_keypair = Keypair::new(); + let from_pubkey = from_keypair.pubkey(); + let nonce_keypair = Keypair::new(); + let nonce_pubkey = nonce_keypair.pubkey(); + let instructions = [ + system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), + system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey), + ]; + let message = Message::new(&instructions, Some(&from_pubkey)); + let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); + assert!(uses_durable_nonce(&tx).is_none()); + } + + #[test] + fn tx_uses_nonce_wrong_first_nonce_ix_fail() { + let from_keypair = Keypair::new(); + let from_pubkey = from_keypair.pubkey(); + let nonce_keypair = Keypair::new(); + let nonce_pubkey = nonce_keypair.pubkey(); + let instructions = [ + system_instruction::withdraw_nonce_account( + &nonce_pubkey, + &nonce_pubkey, + &from_pubkey, + 42, + ), + system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42), + ]; + let message = Message::new(&instructions, Some(&nonce_pubkey)); + let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default()); + assert!(uses_durable_nonce(&tx).is_none()); + } + + #[test] + fn get_nonce_pub_from_ix_ok() { + let (_, nonce_pubkey, tx) = nonced_transfer_tx(); + let nonce_ix = uses_durable_nonce(&tx).unwrap(); + assert_eq!( + get_nonce_pubkey_from_instruction(&nonce_ix, &tx), + Some(&nonce_pubkey), + ); + } + + #[test] + fn get_nonce_pub_from_ix_no_accounts_fail() { + let (_, _, tx) = nonced_transfer_tx(); + let nonce_ix = uses_durable_nonce(&tx).unwrap(); + let mut nonce_ix = nonce_ix.clone(); + nonce_ix.accounts.clear(); + assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,); + } + + #[test] + fn get_nonce_pub_from_ix_bad_acc_idx_fail() { + let (_, _, tx) = nonced_transfer_tx(); + let nonce_ix = uses_durable_nonce(&tx).unwrap(); + let mut nonce_ix = nonce_ix.clone(); + nonce_ix.accounts[0] = 255u8; + assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,); + } }