diff --git a/runtime-transaction/src/runtime_transaction.rs b/runtime-transaction/src/runtime_transaction.rs index b44223b3bcf7cb..07ed60f234e0a5 100644 --- a/runtime-transaction/src/runtime_transaction.rs +++ b/runtime-transaction/src/runtime_transaction.rs @@ -12,7 +12,6 @@ use { crate::{ compute_budget_instruction_details::*, - signature_details::get_precompile_signature_details, transaction_meta::{DynamicMeta, StaticMeta, TransactionMeta}, }, core::ops::Deref, @@ -20,22 +19,20 @@ use { solana_sdk::{ feature_set::FeatureSet, hash::Hash, - message::{AccountKeys, AddressLoader, TransactionSignatureDetails}, + message::{AccountKeys, TransactionSignatureDetails}, pubkey::Pubkey, signature::Signature, - simple_vote_transaction_checker::is_simple_vote_transaction, - transaction::{ - MessageHash, Result, SanitizedTransaction, SanitizedVersionedTransaction, - VersionedTransaction, - }, + transaction::Result, }, solana_svm_transaction::{ instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup, svm_message::SVMMessage, svm_transaction::SVMTransaction, }, - std::collections::HashSet, }; +mod sdk_transactions; +mod transaction_view; + #[cfg_attr(feature = "dev-context-only-utils", derive(Clone))] #[derive(Debug)] pub struct RuntimeTransaction { @@ -72,125 +69,6 @@ impl Deref for RuntimeTransaction { } } -impl RuntimeTransaction { - pub fn try_from( - sanitized_versioned_tx: SanitizedVersionedTransaction, - message_hash: MessageHash, - is_simple_vote_tx: Option, - ) -> Result { - let message_hash = match message_hash { - MessageHash::Precomputed(hash) => hash, - MessageHash::Compute => sanitized_versioned_tx.get_message().message.hash(), - }; - let is_simple_vote_tx = is_simple_vote_tx - .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx)); - - let precompile_signature_details = get_precompile_signature_details( - sanitized_versioned_tx - .get_message() - .program_instructions_iter() - .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))), - ); - let signature_details = TransactionSignatureDetails::new( - u64::from( - sanitized_versioned_tx - .get_message() - .message - .header() - .num_required_signatures, - ), - precompile_signature_details.num_secp256k1_instruction_signatures, - precompile_signature_details.num_ed25519_instruction_signatures, - ); - let compute_budget_instruction_details = ComputeBudgetInstructionDetails::try_from( - sanitized_versioned_tx - .get_message() - .program_instructions_iter() - .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))), - )?; - - Ok(Self { - transaction: sanitized_versioned_tx, - meta: TransactionMeta { - message_hash, - is_simple_vote_transaction: is_simple_vote_tx, - signature_details, - compute_budget_instruction_details, - }, - }) - } -} - -impl RuntimeTransaction { - /// Create a new `RuntimeTransaction` from an - /// unsanitized `VersionedTransaction`. - pub fn try_create( - tx: VersionedTransaction, - message_hash: MessageHash, - is_simple_vote_tx: Option, - address_loader: impl AddressLoader, - reserved_account_keys: &HashSet, - ) -> Result { - let statically_loaded_runtime_tx = - RuntimeTransaction::::try_from( - SanitizedVersionedTransaction::try_from(tx)?, - message_hash, - is_simple_vote_tx, - )?; - Self::try_from( - statically_loaded_runtime_tx, - address_loader, - reserved_account_keys, - ) - } - - /// Create a new `RuntimeTransaction` from a - /// `RuntimeTransaction` that already has - /// static metadata loaded. - pub fn try_from( - statically_loaded_runtime_tx: RuntimeTransaction, - address_loader: impl AddressLoader, - reserved_account_keys: &HashSet, - ) -> Result { - let hash = *statically_loaded_runtime_tx.message_hash(); - let is_simple_vote_tx = statically_loaded_runtime_tx.is_simple_vote_transaction(); - let sanitized_transaction = SanitizedTransaction::try_new( - statically_loaded_runtime_tx.transaction, - hash, - is_simple_vote_tx, - address_loader, - reserved_account_keys, - )?; - - let mut tx = Self { - transaction: sanitized_transaction, - meta: statically_loaded_runtime_tx.meta, - }; - tx.load_dynamic_metadata()?; - - Ok(tx) - } - - fn load_dynamic_metadata(&mut self) -> Result<()> { - Ok(()) - } -} - -#[cfg(feature = "dev-context-only-utils")] -impl RuntimeTransaction { - pub fn from_transaction_for_tests(transaction: solana_sdk::transaction::Transaction) -> Self { - let versioned_transaction = VersionedTransaction::from(transaction); - Self::try_create( - versioned_transaction, - MessageHash::Compute, - None, - solana_sdk::message::SimpleAddressLoader::Disabled, - &HashSet::new(), - ) - .expect("failed to create RuntimeTransaction from Transaction") - } -} - #[cfg(feature = "dev-context-only-utils")] impl RuntimeTransaction { /// Create simply wrapped transaction with a `TransactionMeta` for testing. @@ -272,193 +150,3 @@ impl SVMTransaction for RuntimeTransaction { self.transaction.signatures() } } - -#[cfg(test)] -mod tests { - use { - super::*, - solana_program::{ - system_instruction, - vote::{self, state::Vote}, - }, - solana_sdk::{ - compute_budget::ComputeBudgetInstruction, - instruction::Instruction, - message::Message, - reserved_account_keys::ReservedAccountKeys, - signer::{keypair::Keypair, Signer}, - transaction::{SimpleAddressLoader, Transaction, VersionedTransaction}, - }, - }; - - fn vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction { - let bank_hash = Hash::new_unique(); - let block_hash = Hash::new_unique(); - let vote_keypair = Keypair::new(); - let node_keypair = Keypair::new(); - let auth_keypair = Keypair::new(); - let votes = Vote::new(vec![1, 2, 3], bank_hash); - let vote_ix = - vote::instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes); - let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey())); - vote_tx.partial_sign(&[&node_keypair], block_hash); - vote_tx.partial_sign(&[&auth_keypair], block_hash); - - SanitizedVersionedTransaction::try_from(VersionedTransaction::from(vote_tx)).unwrap() - } - - fn non_vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction { - TestTransaction::new().to_sanitized_versioned_transaction() - } - - // Simple transfer transaction for testing, it does not support vote instruction - // because simple vote transaction will not request limits - struct TestTransaction { - from_keypair: Keypair, - hash: Hash, - instructions: Vec, - } - - impl TestTransaction { - fn new() -> Self { - let from_keypair = Keypair::new(); - let instructions = vec![system_instruction::transfer( - &from_keypair.pubkey(), - &solana_sdk::pubkey::new_rand(), - 1, - )]; - TestTransaction { - from_keypair, - hash: Hash::new_unique(), - instructions, - } - } - - fn add_compute_unit_limit(&mut self, val: u32) -> &mut TestTransaction { - self.instructions - .push(ComputeBudgetInstruction::set_compute_unit_limit(val)); - self - } - - fn add_compute_unit_price(&mut self, val: u64) -> &mut TestTransaction { - self.instructions - .push(ComputeBudgetInstruction::set_compute_unit_price(val)); - self - } - - fn add_loaded_accounts_bytes(&mut self, val: u32) -> &mut TestTransaction { - self.instructions - .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(val)); - self - } - - fn to_sanitized_versioned_transaction(&self) -> SanitizedVersionedTransaction { - let message = Message::new(&self.instructions, Some(&self.from_keypair.pubkey())); - let tx = Transaction::new(&[&self.from_keypair], message, self.hash); - SanitizedVersionedTransaction::try_from(VersionedTransaction::from(tx)).unwrap() - } - } - - #[test] - fn test_runtime_transaction_is_vote_meta() { - fn get_is_simple_vote( - svt: SanitizedVersionedTransaction, - is_simple_vote: Option, - ) -> bool { - RuntimeTransaction::::try_from( - svt, - MessageHash::Compute, - is_simple_vote, - ) - .unwrap() - .meta - .is_simple_vote_transaction - } - - assert!(!get_is_simple_vote( - non_vote_sanitized_versioned_transaction(), - None - )); - - assert!(get_is_simple_vote( - non_vote_sanitized_versioned_transaction(), - Some(true), // override - )); - - assert!(get_is_simple_vote( - vote_sanitized_versioned_transaction(), - None - )); - - assert!(!get_is_simple_vote( - vote_sanitized_versioned_transaction(), - Some(false), // override - )); - } - - #[test] - fn test_advancing_transaction_type() { - let hash = Hash::new_unique(); - - let statically_loaded_transaction = - RuntimeTransaction::::try_from( - non_vote_sanitized_versioned_transaction(), - MessageHash::Precomputed(hash), - None, - ) - .unwrap(); - - assert_eq!(hash, *statically_loaded_transaction.message_hash()); - assert!(!statically_loaded_transaction.is_simple_vote_transaction()); - - let dynamically_loaded_transaction = RuntimeTransaction::::try_from( - statically_loaded_transaction, - SimpleAddressLoader::Disabled, - &ReservedAccountKeys::empty_key_set(), - ); - let dynamically_loaded_transaction = - dynamically_loaded_transaction.expect("created from statically loaded tx"); - - assert_eq!(hash, *dynamically_loaded_transaction.message_hash()); - assert!(!dynamically_loaded_transaction.is_simple_vote_transaction()); - } - - #[test] - fn test_runtime_transaction_static_meta() { - let hash = Hash::new_unique(); - let compute_unit_limit = 250_000; - let compute_unit_price = 1_000; - let loaded_accounts_bytes = 1_024; - let mut test_transaction = TestTransaction::new(); - - let runtime_transaction_static = - RuntimeTransaction::::try_from( - test_transaction - .add_compute_unit_limit(compute_unit_limit) - .add_compute_unit_price(compute_unit_price) - .add_loaded_accounts_bytes(loaded_accounts_bytes) - .to_sanitized_versioned_transaction(), - MessageHash::Precomputed(hash), - None, - ) - .unwrap(); - - assert_eq!(&hash, runtime_transaction_static.message_hash()); - assert!(!runtime_transaction_static.is_simple_vote_transaction()); - - let signature_details = &runtime_transaction_static.meta.signature_details; - assert_eq!(1, signature_details.num_transaction_signatures()); - assert_eq!(0, signature_details.num_secp256k1_instruction_signatures()); - assert_eq!(0, signature_details.num_ed25519_instruction_signatures()); - - let compute_budget_limits = runtime_transaction_static - .compute_budget_limits(&FeatureSet::default()) - .unwrap(); - assert_eq!(compute_unit_limit, compute_budget_limits.compute_unit_limit); - assert_eq!(compute_unit_price, compute_budget_limits.compute_unit_price); - assert_eq!( - loaded_accounts_bytes, - compute_budget_limits.loaded_accounts_bytes.get() - ); - } -} diff --git a/runtime-transaction/src/runtime_transaction/sdk_transactions.rs b/runtime-transaction/src/runtime_transaction/sdk_transactions.rs new file mode 100644 index 00000000000000..ec7aec0b58534b --- /dev/null +++ b/runtime-transaction/src/runtime_transaction/sdk_transactions.rs @@ -0,0 +1,329 @@ +use { + super::{ComputeBudgetInstructionDetails, RuntimeTransaction}, + crate::{ + signature_details::get_precompile_signature_details, + transaction_meta::{StaticMeta, TransactionMeta}, + }, + solana_pubkey::Pubkey, + solana_sdk::{ + message::{AddressLoader, TransactionSignatureDetails}, + simple_vote_transaction_checker::is_simple_vote_transaction, + transaction::{ + MessageHash, Result, SanitizedTransaction, SanitizedVersionedTransaction, + VersionedTransaction, + }, + }, + solana_svm_transaction::instruction::SVMInstruction, + std::collections::HashSet, +}; + +impl RuntimeTransaction { + pub fn try_from( + sanitized_versioned_tx: SanitizedVersionedTransaction, + message_hash: MessageHash, + is_simple_vote_tx: Option, + ) -> Result { + let message_hash = match message_hash { + MessageHash::Precomputed(hash) => hash, + MessageHash::Compute => sanitized_versioned_tx.get_message().message.hash(), + }; + let is_simple_vote_tx = is_simple_vote_tx + .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx)); + + let precompile_signature_details = get_precompile_signature_details( + sanitized_versioned_tx + .get_message() + .program_instructions_iter() + .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))), + ); + let signature_details = TransactionSignatureDetails::new( + u64::from( + sanitized_versioned_tx + .get_message() + .message + .header() + .num_required_signatures, + ), + precompile_signature_details.num_secp256k1_instruction_signatures, + precompile_signature_details.num_ed25519_instruction_signatures, + ); + let compute_budget_instruction_details = ComputeBudgetInstructionDetails::try_from( + sanitized_versioned_tx + .get_message() + .program_instructions_iter() + .map(|(program_id, ix)| (program_id, SVMInstruction::from(ix))), + )?; + + Ok(Self { + transaction: sanitized_versioned_tx, + meta: TransactionMeta { + message_hash, + is_simple_vote_transaction: is_simple_vote_tx, + signature_details, + compute_budget_instruction_details, + }, + }) + } +} + +impl RuntimeTransaction { + /// Create a new `RuntimeTransaction` from an + /// unsanitized `VersionedTransaction`. + pub fn try_create( + tx: VersionedTransaction, + message_hash: MessageHash, + is_simple_vote_tx: Option, + address_loader: impl AddressLoader, + reserved_account_keys: &HashSet, + ) -> Result { + let statically_loaded_runtime_tx = + RuntimeTransaction::::try_from( + SanitizedVersionedTransaction::try_from(tx)?, + message_hash, + is_simple_vote_tx, + )?; + Self::try_from( + statically_loaded_runtime_tx, + address_loader, + reserved_account_keys, + ) + } + + /// Create a new `RuntimeTransaction` from a + /// `RuntimeTransaction` that already has + /// static metadata loaded. + pub fn try_from( + statically_loaded_runtime_tx: RuntimeTransaction, + address_loader: impl AddressLoader, + reserved_account_keys: &HashSet, + ) -> Result { + let hash = *statically_loaded_runtime_tx.message_hash(); + let is_simple_vote_tx = statically_loaded_runtime_tx.is_simple_vote_transaction(); + let sanitized_transaction = SanitizedTransaction::try_new( + statically_loaded_runtime_tx.transaction, + hash, + is_simple_vote_tx, + address_loader, + reserved_account_keys, + )?; + + let mut tx = Self { + transaction: sanitized_transaction, + meta: statically_loaded_runtime_tx.meta, + }; + tx.load_dynamic_metadata()?; + + Ok(tx) + } + + fn load_dynamic_metadata(&mut self) -> Result<()> { + Ok(()) + } +} + +#[cfg(feature = "dev-context-only-utils")] +impl RuntimeTransaction { + pub fn from_transaction_for_tests(transaction: solana_sdk::transaction::Transaction) -> Self { + let versioned_transaction = VersionedTransaction::from(transaction); + Self::try_create( + versioned_transaction, + MessageHash::Compute, + None, + solana_sdk::message::SimpleAddressLoader::Disabled, + &HashSet::new(), + ) + .expect("failed to create RuntimeTransaction from Transaction") + } +} + +#[cfg(test)] +mod tests { + use { + super::*, + solana_program::{ + system_instruction, + vote::{self, state::Vote}, + }, + solana_sdk::{ + compute_budget::ComputeBudgetInstruction, + feature_set::FeatureSet, + hash::Hash, + instruction::Instruction, + message::Message, + reserved_account_keys::ReservedAccountKeys, + signer::{keypair::Keypair, Signer}, + transaction::{SimpleAddressLoader, Transaction, VersionedTransaction}, + }, + }; + + fn vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction { + let bank_hash = Hash::new_unique(); + let block_hash = Hash::new_unique(); + let vote_keypair = Keypair::new(); + let node_keypair = Keypair::new(); + let auth_keypair = Keypair::new(); + let votes = Vote::new(vec![1, 2, 3], bank_hash); + let vote_ix = + vote::instruction::vote(&vote_keypair.pubkey(), &auth_keypair.pubkey(), votes); + let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey())); + vote_tx.partial_sign(&[&node_keypair], block_hash); + vote_tx.partial_sign(&[&auth_keypair], block_hash); + + SanitizedVersionedTransaction::try_from(VersionedTransaction::from(vote_tx)).unwrap() + } + + fn non_vote_sanitized_versioned_transaction() -> SanitizedVersionedTransaction { + TestTransaction::new().to_sanitized_versioned_transaction() + } + + // Simple transfer transaction for testing, it does not support vote instruction + // because simple vote transaction will not request limits + struct TestTransaction { + from_keypair: Keypair, + hash: Hash, + instructions: Vec, + } + + impl TestTransaction { + fn new() -> Self { + let from_keypair = Keypair::new(); + let instructions = vec![system_instruction::transfer( + &from_keypair.pubkey(), + &solana_sdk::pubkey::new_rand(), + 1, + )]; + TestTransaction { + from_keypair, + hash: Hash::new_unique(), + instructions, + } + } + + fn add_compute_unit_limit(&mut self, val: u32) -> &mut TestTransaction { + self.instructions + .push(ComputeBudgetInstruction::set_compute_unit_limit(val)); + self + } + + fn add_compute_unit_price(&mut self, val: u64) -> &mut TestTransaction { + self.instructions + .push(ComputeBudgetInstruction::set_compute_unit_price(val)); + self + } + + fn add_loaded_accounts_bytes(&mut self, val: u32) -> &mut TestTransaction { + self.instructions + .push(ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(val)); + self + } + + fn to_sanitized_versioned_transaction(&self) -> SanitizedVersionedTransaction { + let message = Message::new(&self.instructions, Some(&self.from_keypair.pubkey())); + let tx = Transaction::new(&[&self.from_keypair], message, self.hash); + SanitizedVersionedTransaction::try_from(VersionedTransaction::from(tx)).unwrap() + } + } + + #[test] + fn test_runtime_transaction_is_vote_meta() { + fn get_is_simple_vote( + svt: SanitizedVersionedTransaction, + is_simple_vote: Option, + ) -> bool { + RuntimeTransaction::::try_from( + svt, + MessageHash::Compute, + is_simple_vote, + ) + .unwrap() + .meta + .is_simple_vote_transaction + } + + assert!(!get_is_simple_vote( + non_vote_sanitized_versioned_transaction(), + None + )); + + assert!(get_is_simple_vote( + non_vote_sanitized_versioned_transaction(), + Some(true), // override + )); + + assert!(get_is_simple_vote( + vote_sanitized_versioned_transaction(), + None + )); + + assert!(!get_is_simple_vote( + vote_sanitized_versioned_transaction(), + Some(false), // override + )); + } + + #[test] + fn test_advancing_transaction_type() { + let hash = Hash::new_unique(); + + let statically_loaded_transaction = + RuntimeTransaction::::try_from( + non_vote_sanitized_versioned_transaction(), + MessageHash::Precomputed(hash), + None, + ) + .unwrap(); + + assert_eq!(hash, *statically_loaded_transaction.message_hash()); + assert!(!statically_loaded_transaction.is_simple_vote_transaction()); + + let dynamically_loaded_transaction = RuntimeTransaction::::try_from( + statically_loaded_transaction, + SimpleAddressLoader::Disabled, + &ReservedAccountKeys::empty_key_set(), + ); + let dynamically_loaded_transaction = + dynamically_loaded_transaction.expect("created from statically loaded tx"); + + assert_eq!(hash, *dynamically_loaded_transaction.message_hash()); + assert!(!dynamically_loaded_transaction.is_simple_vote_transaction()); + } + + #[test] + fn test_runtime_transaction_static_meta() { + let hash = Hash::new_unique(); + let compute_unit_limit = 250_000; + let compute_unit_price = 1_000; + let loaded_accounts_bytes = 1_024; + let mut test_transaction = TestTransaction::new(); + + let runtime_transaction_static = + RuntimeTransaction::::try_from( + test_transaction + .add_compute_unit_limit(compute_unit_limit) + .add_compute_unit_price(compute_unit_price) + .add_loaded_accounts_bytes(loaded_accounts_bytes) + .to_sanitized_versioned_transaction(), + MessageHash::Precomputed(hash), + None, + ) + .unwrap(); + + assert_eq!(&hash, runtime_transaction_static.message_hash()); + assert!(!runtime_transaction_static.is_simple_vote_transaction()); + + let signature_details = &runtime_transaction_static.meta.signature_details; + assert_eq!(1, signature_details.num_transaction_signatures()); + assert_eq!(0, signature_details.num_secp256k1_instruction_signatures()); + assert_eq!(0, signature_details.num_ed25519_instruction_signatures()); + + let compute_budget_limits = runtime_transaction_static + .compute_budget_limits(&FeatureSet::default()) + .unwrap(); + assert_eq!(compute_unit_limit, compute_budget_limits.compute_unit_limit); + assert_eq!(compute_unit_price, compute_budget_limits.compute_unit_price); + assert_eq!( + loaded_accounts_bytes, + compute_budget_limits.loaded_accounts_bytes.get() + ); + } +} diff --git a/runtime-transaction/src/runtime_transaction/transaction_view.rs b/runtime-transaction/src/runtime_transaction/transaction_view.rs new file mode 100644 index 00000000000000..dfeb54037018fc --- /dev/null +++ b/runtime-transaction/src/runtime_transaction/transaction_view.rs @@ -0,0 +1,150 @@ +use { + super::{ComputeBudgetInstructionDetails, RuntimeTransaction}, + crate::{ + signature_details::get_precompile_signature_details, transaction_meta::TransactionMeta, + }, + agave_transaction_view::{ + resolved_transaction_view::ResolvedTransactionView, transaction_data::TransactionData, + transaction_version::TransactionVersion, transaction_view::SanitizedTransactionView, + }, + solana_pubkey::Pubkey, + solana_sdk::{ + message::{v0::LoadedAddresses, TransactionSignatureDetails, VersionedMessage}, + simple_vote_transaction_checker::is_simple_vote_transaction_impl, + transaction::{MessageHash, Result, TransactionError}, + }, + std::collections::HashSet, +}; + +fn is_simple_vote_transaction( + transaction: &SanitizedTransactionView, +) -> bool { + let signatures = transaction.signatures(); + let is_legacy_message = matches!(transaction.version(), TransactionVersion::Legacy); + let instruction_programs = transaction + .program_instructions_iter() + .map(|(program_id, _ix)| program_id); + + is_simple_vote_transaction_impl(signatures, is_legacy_message, instruction_programs) +} + +impl RuntimeTransaction> { + pub fn try_from( + transaction: SanitizedTransactionView, + message_hash: MessageHash, + is_simple_vote_tx: Option, + ) -> Result { + let message_hash = match message_hash { + MessageHash::Precomputed(hash) => hash, + MessageHash::Compute => VersionedMessage::hash_raw_message(transaction.message_data()), + }; + let is_simple_vote_tx = + is_simple_vote_tx.unwrap_or_else(|| is_simple_vote_transaction(&transaction)); + + let precompile_signature_details = + get_precompile_signature_details(transaction.program_instructions_iter()); + let signature_details = TransactionSignatureDetails::new( + u64::from(transaction.num_required_signatures()), + precompile_signature_details.num_secp256k1_instruction_signatures, + precompile_signature_details.num_ed25519_instruction_signatures, + ); + let compute_budget_instruction_details = + ComputeBudgetInstructionDetails::try_from(transaction.program_instructions_iter())?; + + Ok(Self { + transaction, + meta: TransactionMeta { + message_hash, + is_simple_vote_transaction: is_simple_vote_tx, + signature_details, + compute_budget_instruction_details, + }, + }) + } +} + +impl RuntimeTransaction> { + /// Create a new `RuntimeTransaction` from a + /// `RuntimeTransaction` that already has + /// static metadata loaded. + pub fn try_from( + statically_loaded_runtime_tx: RuntimeTransaction>, + loaded_addresses: Option, + reserved_account_keys: &HashSet, + ) -> Result { + let RuntimeTransaction { transaction, meta } = statically_loaded_runtime_tx; + // transaction-view does not distinguish between different types of errors here. + // return generic sanitize failure error here. + // these transactions should be immediately dropped, and we generally + // will not care about the specific error at this point. + let transaction = + ResolvedTransactionView::try_new(transaction, loaded_addresses, reserved_account_keys) + .map_err(|_| TransactionError::SanitizeFailure)?; + let mut tx = Self { transaction, meta }; + tx.load_dynamic_metadata()?; + + Ok(tx) + } + + fn load_dynamic_metadata(&mut self) -> Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use { + crate::{runtime_transaction::RuntimeTransaction, transaction_meta::StaticMeta}, + agave_transaction_view::{ + resolved_transaction_view::ResolvedTransactionView, + transaction_view::SanitizedTransactionView, + }, + solana_pubkey::Pubkey, + solana_sdk::{ + hash::Hash, + reserved_account_keys::ReservedAccountKeys, + signature::Keypair, + system_transaction, + transaction::{MessageHash, VersionedTransaction}, + }, + }; + + #[test] + fn test_advancing_transaction_type() { + // Create serialized simple transfer. + let serialized_transaction = { + let transaction = VersionedTransaction::from(system_transaction::transfer( + &Keypair::new(), + &Pubkey::new_unique(), + 1, + Hash::new_unique(), + )); + bincode::serialize(&transaction).unwrap() + }; + + let hash = Hash::new_unique(); + let transaction = + SanitizedTransactionView::try_new_sanitized(&serialized_transaction[..]).unwrap(); + let static_runtime_transaction = + RuntimeTransaction::>::try_from( + transaction, + MessageHash::Precomputed(hash), + None, + ) + .unwrap(); + + assert_eq!(hash, *static_runtime_transaction.message_hash()); + assert!(!static_runtime_transaction.is_simple_vote_transaction()); + + let dynamic_runtime_transaction = + RuntimeTransaction::>::try_from( + static_runtime_transaction, + None, + &ReservedAccountKeys::empty_key_set(), + ) + .unwrap(); + + assert_eq!(hash, *dynamic_runtime_transaction.message_hash()); + assert!(!dynamic_runtime_transaction.is_simple_vote_transaction()); + } +} diff --git a/sdk/src/simple_vote_transaction_checker.rs b/sdk/src/simple_vote_transaction_checker.rs index 33be20773afb21..cc8ccc02f2484f 100644 --- a/sdk/src/simple_vote_transaction_checker.rs +++ b/sdk/src/simple_vote_transaction_checker.rs @@ -1,6 +1,10 @@ #![cfg(feature = "full")] -use crate::{message::VersionedMessage, transaction::SanitizedVersionedTransaction}; +use { + crate::{message::VersionedMessage, transaction::SanitizedVersionedTransaction}, + solana_pubkey::Pubkey, + solana_signature::Signature, +}; /// Simple vote transaction meets these conditions: /// 1. has 1 or 2 signatures; @@ -10,19 +14,38 @@ use crate::{message::VersionedMessage, transaction::SanitizedVersionedTransactio pub fn is_simple_vote_transaction( sanitized_versioned_transaction: &SanitizedVersionedTransaction, ) -> bool { - let signatures_count = sanitized_versioned_transaction.signatures.len(); let is_legacy_message = matches!( sanitized_versioned_transaction.message.message, VersionedMessage::Legacy(_) ); - let mut instructions = sanitized_versioned_transaction + let instruction_programs = sanitized_versioned_transaction .message - .program_instructions_iter(); - signatures_count < 3 + .program_instructions_iter() + .map(|(program_id, _ix)| program_id); + + is_simple_vote_transaction_impl( + &sanitized_versioned_transaction.signatures, + is_legacy_message, + instruction_programs, + ) +} + +/// Simple vote transaction meets these conditions: +/// 1. has 1 or 2 signatures; +/// 2. is legacy message; +/// 3. has only one instruction; +/// 4. which must be Vote instruction; +#[inline] +pub fn is_simple_vote_transaction_impl<'a>( + signatures: &[Signature], + is_legacy_message: bool, + mut instruction_programs: impl Iterator, +) -> bool { + signatures.len() < 3 && is_legacy_message - && instructions + && instruction_programs .next() - .xor(instructions.next()) - .map(|(program_id, _ix)| program_id == &solana_sdk::vote::program::id()) + .xor(instruction_programs.next()) + .map(|program_id| program_id == &solana_sdk::vote::program::ID) .unwrap_or(false) } diff --git a/transaction-view/src/lib.rs b/transaction-view/src/lib.rs index 62c69e40841f45..025b6ed8130e49 100644 --- a/transaction-view/src/lib.rs +++ b/transaction-view/src/lib.rs @@ -14,4 +14,5 @@ mod signature_frame; pub mod static_account_keys_frame; pub mod transaction_data; mod transaction_frame; +pub mod transaction_version; pub mod transaction_view; diff --git a/transaction-view/src/message_header_frame.rs b/transaction-view/src/message_header_frame.rs index 13eb8b3e7b629b..bfaec5092db3b1 100644 --- a/transaction-view/src/message_header_frame.rs +++ b/transaction-view/src/message_header_frame.rs @@ -2,19 +2,11 @@ use { crate::{ bytes::read_byte, result::{Result, TransactionViewError}, + transaction_version::TransactionVersion, }, solana_sdk::message::MESSAGE_VERSION_PREFIX, }; -/// A byte that represents the version of the transaction. -#[derive(Copy, Clone, Debug, Default)] -#[repr(u8)] -pub enum TransactionVersion { - #[default] - Legacy = u8::MAX, - V0 = 0, -} - /// Metadata for accessing message header fields in a transaction view. #[derive(Debug)] pub(crate) struct MessageHeaderFrame { diff --git a/transaction-view/src/transaction_frame.rs b/transaction-view/src/transaction_frame.rs index 933736d5d251f3..d2c97edbeb07bb 100644 --- a/transaction-view/src/transaction_frame.rs +++ b/transaction-view/src/transaction_frame.rs @@ -3,10 +3,11 @@ use { address_table_lookup_frame::{AddressTableLookupFrame, AddressTableLookupIterator}, bytes::advance_offset_for_type, instructions_frame::{InstructionsFrame, InstructionsIterator}, - message_header_frame::{MessageHeaderFrame, TransactionVersion}, + message_header_frame::MessageHeaderFrame, result::{Result, TransactionViewError}, signature_frame::SignatureFrame, static_account_keys_frame::StaticAccountKeysFrame, + transaction_version::TransactionVersion, }, solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature}, }; diff --git a/transaction-view/src/transaction_version.rs b/transaction-view/src/transaction_version.rs new file mode 100644 index 00000000000000..441211f17f2c41 --- /dev/null +++ b/transaction-view/src/transaction_version.rs @@ -0,0 +1,8 @@ +/// A byte that represents the version of the transaction. +#[derive(Copy, Clone, Debug, Default)] +#[repr(u8)] +pub enum TransactionVersion { + #[default] + Legacy = u8::MAX, + V0 = 0, +} diff --git a/transaction-view/src/transaction_view.rs b/transaction-view/src/transaction_view.rs index 1d0c3c9bdc2034..3eb51a5e31e0ab 100644 --- a/transaction-view/src/transaction_view.rs +++ b/transaction-view/src/transaction_view.rs @@ -1,9 +1,9 @@ use { crate::{ address_table_lookup_frame::AddressTableLookupIterator, - instructions_frame::InstructionsIterator, message_header_frame::TransactionVersion, - result::Result, sanitize::sanitize, transaction_data::TransactionData, - transaction_frame::TransactionFrame, + instructions_frame::InstructionsIterator, result::Result, sanitize::sanitize, + transaction_data::TransactionData, transaction_frame::TransactionFrame, + transaction_version::TransactionVersion, }, core::fmt::{Debug, Formatter}, solana_sdk::{hash::Hash, pubkey::Pubkey, signature::Signature},