From 4c577d7f8c188b45696df4e72cdbf59c6aa57141 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Thu, 13 Jan 2022 13:06:03 -0800 Subject: [PATCH] `Bank::get_fee_for_message` is now nonce aware --- runtime/src/bank.rs | 31 ++++++++++++++++------- sdk/program/src/lib.rs | 1 + sdk/program/src/message/sanitized.rs | 31 +++++++++++++++++++++++ sdk/program/src/program_utils.rs | 38 ++++++++++++++++++++++++++++ sdk/src/program_utils.rs | 13 ++++------ sdk/src/transaction/sanitized.rs | 29 +-------------------- 6 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 sdk/program/src/program_utils.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index b5d5a71140..6da1d8ab5a 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3073,9 +3073,17 @@ impl Bank { } pub fn get_fee_for_message(&self, message: &SanitizedMessage) -> Option { - let blockhash_queue = self.blockhash_queue.read().unwrap(); - let lamports_per_signature = - blockhash_queue.get_lamports_per_signature(message.recent_blockhash())?; + let lamports_per_signature = { + let blockhash_queue = self.blockhash_queue.read().unwrap(); + blockhash_queue.get_lamports_per_signature(message.recent_blockhash()) + } + .or_else(|| { + self.check_message_for_nonce(message) + .and_then(|(address, account)| { + NoncePartial::new(address, account).lamports_per_signature() + }) + })?; + Some(Self::calculate_fee(message, lamports_per_signature)) } @@ -3438,20 +3446,25 @@ impl Bank { .check_hash_age(hash, max_age) } - pub fn check_transaction_for_nonce( - &self, - tx: &SanitizedTransaction, - ) -> Option { - tx.get_durable_nonce(self.feature_set.is_active(&nonce_must_be_writable::id())) + 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, tx.message().recent_blockhash()) + nonce_account::verify_nonce_account(nonce_account, message.recent_blockhash()) }) } + pub fn check_transaction_for_nonce( + &self, + tx: &SanitizedTransaction, + ) -> Option { + self.check_message_for_nonce(tx.message()) + } + pub fn check_transactions( &self, sanitized_txs: &[SanitizedTransaction], diff --git a/sdk/program/src/lib.rs b/sdk/program/src/lib.rs index 8ac5140ad8..d4d6ffc780 100644 --- a/sdk/program/src/lib.rs +++ b/sdk/program/src/lib.rs @@ -38,6 +38,7 @@ pub mod program_memory; pub mod program_option; pub mod program_pack; pub mod program_stubs; +pub mod program_utils; pub mod pubkey; pub mod rent; pub mod sanitize; diff --git a/sdk/program/src/message/sanitized.rs b/sdk/program/src/message/sanitized.rs index fa27d5b881..307aef9dd6 100644 --- a/sdk/program/src/message/sanitized.rs +++ b/sdk/program/src/message/sanitized.rs @@ -7,9 +7,12 @@ use { v0::{self, LoadedAddresses}, MessageHeader, }, + nonce::NONCED_TX_MARKER_IX_INDEX, + program_utils::limited_deserialize, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, serialize_utils::{append_slice, append_u16, append_u8}, + solana_program::{system_instruction::SystemInstruction, system_program}, }, bitflags::bitflags, std::convert::TryFrom, @@ -292,6 +295,34 @@ impl SanitizedMessage { Self::V0(message) => message.is_upgradeable_loader_present(), } } + + /// If the message uses a durable nonce, return the pubkey of the nonce account + pub fn get_durable_nonce(&self, nonce_must_be_writable: bool) -> Option<&Pubkey> { + self.instructions() + .get(NONCED_TX_MARKER_IX_INDEX as usize) + .filter( + |ix| match self.get_account_key(ix.program_id_index as usize) { + Some(program_id) => system_program::check_id(program_id), + _ => false, + }, + ) + .filter(|ix| { + matches!( + limited_deserialize(&ix.data, 4 /* serialized size of AdvanceNonceAccount */), + Ok(SystemInstruction::AdvanceNonceAccount) + ) + }) + .and_then(|ix| { + ix.accounts.get(0).and_then(|idx| { + let idx = *idx as usize; + if nonce_must_be_writable && !self.is_writable(idx) { + None + } else { + self.get_account_key(idx) + } + }) + }) + } } #[cfg(test)] diff --git a/sdk/program/src/program_utils.rs b/sdk/program/src/program_utils.rs new file mode 100644 index 0000000000..78ad6393d3 --- /dev/null +++ b/sdk/program/src/program_utils.rs @@ -0,0 +1,38 @@ +use {crate::instruction::InstructionError, bincode::config::Options}; + +/// Deserialize with a limit based the maximum amount of data a program can expect to get. +/// This function should be used in place of direct deserialization to help prevent OOM errors +pub fn limited_deserialize(instruction_data: &[u8], limit: u64) -> Result +where + T: serde::de::DeserializeOwned, +{ + bincode::options() + .with_limit(limit) + .with_fixint_encoding() // As per https://github.com/servo/bincode/issues/333, these two options are needed + .allow_trailing_bytes() // to retain the behavior of bincode::deserialize with the new `options()` method + .deserialize_from(instruction_data) + .map_err(|_| InstructionError::InvalidInstructionData) +} + +#[cfg(test)] +pub mod tests { + use {super::*, solana_program::system_instruction::SystemInstruction}; + + #[test] + fn test_limited_deserialize_advance_nonce_account() { + let item = SystemInstruction::AdvanceNonceAccount; + let serialized = bincode::serialize(&item).unwrap(); + + assert_eq!( + serialized.len(), + 4, + "`SanitizedMessage::get_durable_nonce()` may need a change" + ); + + assert_eq!( + limited_deserialize::(&serialized, 4).as_ref(), + Ok(&item) + ); + assert!(limited_deserialize::(&serialized, 3).is_err()); + } +} diff --git a/sdk/src/program_utils.rs b/sdk/src/program_utils.rs index 590e81ae39..e0ca9975da 100644 --- a/sdk/src/program_utils.rs +++ b/sdk/src/program_utils.rs @@ -1,4 +1,4 @@ -use {crate::instruction::InstructionError, bincode::config::Options}; +use crate::instruction::InstructionError; /// Deserialize with a limit based the maximum amount of data a program can expect to get. /// This function should be used in place of direct deserialization to help prevent OOM errors @@ -6,13 +6,10 @@ pub fn limited_deserialize(instruction_data: &[u8]) -> Result Option<&Pubkey> { - self.message - .instructions() - .get(NONCED_TX_MARKER_IX_INDEX as usize) - .filter( - |ix| match self.message.get_account_key(ix.program_id_index as usize) { - Some(program_id) => system_program::check_id(program_id), - _ => false, - }, - ) - .filter(|ix| { - matches!( - limited_deserialize(&ix.data), - Ok(SystemInstruction::AdvanceNonceAccount) - ) - }) - .and_then(|ix| { - ix.accounts.get(0).and_then(|idx| { - let idx = *idx as usize; - if nonce_must_be_writable && !self.message.is_writable(idx) { - None - } else { - self.message.get_account_key(idx) - } - }) - }) + self.message.get_durable_nonce(nonce_must_be_writable) } /// Return the serialized message data to sign.