From 9d69f2b32458a510e9ce70c7c2a3ec2cb4fc5f33 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Jan 2022 03:25:10 +0000 Subject: [PATCH] `Bank::get_fee_for_message` is now nonce aware (backport #22494) (#22499) * `Bank::get_fee_for_message` is now nonce aware (cherry picked from commit 4c577d7f8c188b45696df4e72cdbf59c6aa57141) # Conflicts: # runtime/src/bank.rs # sdk/program/src/message/sanitized.rs * Resolve conflicts Co-authored-by: Michael Vines --- runtime/src/bank.rs | 30 +++++++++++++---- sdk/program/src/lib.rs | 1 + sdk/program/src/message/sanitized.rs | 37 ++++++++++++++++++++- sdk/program/src/message/versions/mod.rs | 4 +-- sdk/program/src/message/versions/v0/mod.rs | 6 +--- sdk/program/src/program_utils.rs | 38 ++++++++++++++++++++++ sdk/src/program_utils.rs | 13 +++----- sdk/src/transaction/sanitized.rs | 29 +---------------- 8 files changed, 106 insertions(+), 52 deletions(-) create mode 100644 sdk/program/src/program_utils.rs diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index e22d79ea6d..b049a90d6c 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -3088,9 +3088,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)) } @@ -3453,20 +3461,28 @@ impl Bank { .check_hash_age(hash, max_age) } - pub fn check_transaction_for_nonce( + fn check_message_for_nonce( &self, - tx: &SanitizedTransaction, + message: &SanitizedMessage, ) -> Option<(Pubkey, AccountSharedData)> { - tx.get_durable_nonce(self.feature_set.is_active(&nonce_must_be_writable::id())) + 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<(Pubkey, AccountSharedData)> { + 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 e934d22a2f..5167e9fa5e 100644 --- a/sdk/program/src/message/sanitized.rs +++ b/sdk/program/src/message/sanitized.rs @@ -2,10 +2,17 @@ use { crate::{ hash::Hash, instruction::{CompiledInstruction, Instruction}, - message::{v0::{self, LoadedAddresses}, legacy::Message as LegacyMessage, MessageHeader}, + message::{ + legacy::Message as LegacyMessage, + 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, @@ -288,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/message/versions/mod.rs b/sdk/program/src/message/versions/mod.rs index ffb571579b..ace0a3bf72 100644 --- a/sdk/program/src/message/versions/mod.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -311,9 +311,7 @@ mod tests { num_readonly_unsigned_accounts: 0, }, recent_blockhash: Hash::new_unique(), - account_keys: vec![ - Pubkey::new_unique(), - ], + account_keys: vec![Pubkey::new_unique()], address_table_lookups: vec![ MessageAddressTableLookup { account_key: Pubkey::new_unique(), diff --git a/sdk/program/src/message/versions/v0/mod.rs b/sdk/program/src/message/versions/v0/mod.rs index ac0e599199..1e98d5c3d6 100644 --- a/sdk/program/src/message/versions/v0/mod.rs +++ b/sdk/program/src/message/versions/v0/mod.rs @@ -125,10 +125,7 @@ impl Message { #[cfg(test)] mod tests { - use { - super::*, - crate::message::VersionedMessage, - }; + use {super::*, crate::message::VersionedMessage}; #[test] fn test_sanitize() { @@ -251,7 +248,6 @@ mod tests { .is_err()); } - #[test] fn test_sanitize_with_max_account_keys() { assert!(Message { 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.