From fa726e78d4f815e371e10983397c88e49cf7af0e Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 3 Mar 2022 10:34:02 +0800 Subject: [PATCH] Add rpc test for versioned txs --- Cargo.lock | 1 + client-test/tests/client.rs | 11 +- client/src/mock_sender.rs | 4 +- core/src/banking_stage.rs | 4 +- core/src/replay_stage.rs | 13 +- .../tests/common.rs | 4 +- programs/address-lookup-table/src/state.rs | 13 +- rpc/Cargo.toml | 1 + rpc/src/rpc.rs | 178 +++++++++++++++--- rpc/src/rpc_subscriptions.rs | 29 ++- runtime/src/accounts.rs | 7 +- sdk/src/transaction/versioned.rs | 17 +- transaction-status/src/lib.rs | 4 +- 13 files changed, 219 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e3ce89ffb0569..bfe4ebf52531d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5504,6 +5504,7 @@ dependencies = [ "serial_test", "soketto", "solana-account-decoder", + "solana-address-lookup-table-program", "solana-client", "solana-entry", "solana-faucet", diff --git a/client-test/tests/client.rs b/client-test/tests/client.rs index c0d696f017decb..38fe73f3855840 100644 --- a/client-test/tests/client.rs +++ b/client-test/tests/client.rs @@ -15,7 +15,7 @@ use { solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path}, solana_rpc::{ optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank, - rpc::create_test_transactions_and_populate_blockstore, + rpc::{create_test_transaction_entries, populate_blockstore_for_tests}, rpc_pubsub_service::{PubSubConfig, PubSubService}, rpc_subscriptions::RpcSubscriptions, }, @@ -232,9 +232,12 @@ fn test_block_subscription() { let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root())); bank.transfer(rent_exempt_amount, &alice, &keypair2.pubkey()) .unwrap(); - let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore( - vec![&alice, &keypair1, &keypair2, &keypair3], - 0, + populate_blockstore_for_tests( + create_test_transaction_entries( + vec![&alice, &keypair1, &keypair2, &keypair3], + bank.clone(), + ) + .0, bank, blockstore.clone(), max_complete_transaction_status_slot, diff --git a/client/src/mock_sender.rs b/client/src/mock_sender.rs index e096cedd400666..06422831487466 100644 --- a/client/src/mock_sender.rs +++ b/client/src/mock_sender.rs @@ -192,7 +192,7 @@ impl RpcSender for MockSender { "getTransaction" => serde_json::to_value(EncodedConfirmedTransactionWithStatusMeta { slot: 2, transaction: EncodedTransactionWithStatusMeta { - version: Some(TransactionVersion::Legacy), + version: Some(TransactionVersion::LEGACY), transaction: EncodedTransaction::Json( UiTransaction { signatures: vec!["3AsdoALgZFuq2oUVWrDYhg2pNeaLJKPLf8hU2mQ6U8qJxeJ6hsrPVpMn9ma39DtfYCrDQSvngWRP8NnTpEhezJpE".to_string()], @@ -384,7 +384,7 @@ impl RpcSender for MockSender { UiTransactionEncoding::Base58, ), meta: None, - version: Some(TransactionVersion::Legacy), + version: Some(TransactionVersion::LEGACY), }], rewards: Rewards::new(), block_time: None, diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index 4e43408d02bd90..123107ee9d2023 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -3384,9 +3384,7 @@ mod tests { account_address: Pubkey, address_lookup_table: AddressLookupTable<'static>, ) -> AccountSharedData { - let mut data = Vec::new(); - address_lookup_table.serialize_for_tests(&mut data).unwrap(); - + let data = address_lookup_table.serialize_for_tests().unwrap(); let mut account = AccountSharedData::new(1, data.len(), &solana_address_lookup_table_program::id()); account.set_data(data); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index db5064c8ad50c3..98f910346d17e6 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -3129,7 +3129,7 @@ pub mod tests { }, solana_rpc::{ optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank, - rpc::create_test_transactions_and_populate_blockstore, + rpc::{create_test_transaction_entries, populate_blockstore_for_tests}, }, solana_runtime::{ accounts_background_service::AbsRequestSender, @@ -3998,15 +3998,18 @@ pub mod tests { let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1)); let slot = bank1.slot(); - let mut test_signatures_iter = create_test_transactions_and_populate_blockstore( + let (entries, test_signatures) = create_test_transaction_entries( vec![&mint_keypair, &keypair1, &keypair2, &keypair3], - bank0.slot(), + bank1.clone(), + ); + populate_blockstore_for_tests( + entries, bank1, blockstore.clone(), Arc::new(AtomicU64::default()), - ) - .into_iter(); + ); + let mut test_signatures_iter = test_signatures.into_iter(); let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap(); let actual_tx_results: Vec<_> = confirmed_block .transactions diff --git a/programs/address-lookup-table-tests/tests/common.rs b/programs/address-lookup-table-tests/tests/common.rs index 3aa21bc89805f9..d5c77c2088ebe4 100644 --- a/programs/address-lookup-table-tests/tests/common.rs +++ b/programs/address-lookup-table-tests/tests/common.rs @@ -76,9 +76,7 @@ pub async fn add_lookup_table_account( account_address: Pubkey, address_lookup_table: AddressLookupTable<'static>, ) -> AccountSharedData { - let mut data = Vec::new(); - address_lookup_table.serialize_for_tests(&mut data).unwrap(); - + let data = address_lookup_table.serialize_for_tests().unwrap(); let rent = context.banks_client.get_rent().await.unwrap(); let rent_exempt_balance = rent.minimum_balance(data.len()); diff --git a/programs/address-lookup-table/src/state.rs b/programs/address-lookup-table/src/state.rs index 78f4c1d931e2b6..4768cd563a2a41 100644 --- a/programs/address-lookup-table/src/state.rs +++ b/programs/address-lookup-table/src/state.rs @@ -178,13 +178,13 @@ impl<'a> AddressLookupTable<'a> { } /// Serialize an address table including its addresses - pub fn serialize_for_tests(self, data: &mut Vec) -> Result<(), InstructionError> { - data.resize(LOOKUP_TABLE_META_SIZE, 0); - Self::overwrite_meta_data(data, self.meta)?; + pub fn serialize_for_tests(self) -> Result, InstructionError> { + let mut data = vec![0; LOOKUP_TABLE_META_SIZE]; + Self::overwrite_meta_data(&mut data, self.meta)?; self.addresses.iter().for_each(|address| { data.extend_from_slice(address.as_ref()); }); - Ok(()) + Ok(data) } /// Efficiently deserialize an address table without allocating @@ -352,9 +352,8 @@ mod tests { fn test_case(num_addresses: usize) { let lookup_table_meta = LookupTableMeta::new_for_tests(); let address_table = AddressLookupTable::new_for_tests(lookup_table_meta, num_addresses); - let mut address_table_data = Vec::new(); - AddressLookupTable::serialize_for_tests(address_table.clone(), &mut address_table_data) - .unwrap(); + let address_table_data = + AddressLookupTable::serialize_for_tests(address_table.clone()).unwrap(); assert_eq!( AddressLookupTable::deserialize(&address_table_data).unwrap(), address_table, diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 3cb3945142256c..a51b666dca91c6 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -56,6 +56,7 @@ tokio-util = { version = "0.6", features = ["codec", "compat"] } [dev-dependencies] serial_test = "0.6.0" +solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.1" } solana-net-utils = { path = "../net-utils", version = "=1.10.1" } solana-stake-program = { path = "../programs/stake", version = "=1.10.1" } symlink = "0.1.0" diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 08cee86bf7c13e..57b4e455652858 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -29,6 +29,7 @@ use { }, rpc_response::{Response as RpcResponse, *}, }, + solana_entry::entry::Entry, solana_faucet::faucet::request_airdrop_transaction, solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo}, solana_ledger::{ @@ -4286,22 +4287,18 @@ pub(crate) fn create_validator_exit(exit: &Arc) -> Arc> Arc::new(RwLock::new(validator_exit)) } -// Used for tests -pub fn create_test_transactions_and_populate_blockstore( +pub fn create_test_transaction_entries( keypairs: Vec<&Keypair>, - previous_slot: Slot, bank: Arc, - blockstore: Arc, - max_complete_transaction_status_slot: Arc, -) -> Vec { +) -> (Vec, Vec) { let mint_keypair = keypairs[0]; let keypair1 = keypairs[1]; let keypair2 = keypairs[2]; let keypair3 = keypairs[3]; - let slot = bank.slot(); let blockhash = bank.confirmed_last_blockhash(); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); + let mut signatures = Vec::new(); // Generate transactions for processing // Successful transaction let success_tx = solana_sdk::system_transaction::transfer( @@ -4310,7 +4307,7 @@ pub fn create_test_transactions_and_populate_blockstore( rent_exempt_amount, blockhash, ); - let success_signature = success_tx.signatures[0]; + signatures.push(success_tx.signatures[0]); let entry_1 = solana_entry::entry::next_entry(&blockhash, 1, vec![success_tx]); // Failed transaction, InstructionError let ix_error_tx = solana_sdk::system_transaction::transfer( @@ -4319,12 +4316,21 @@ pub fn create_test_transactions_and_populate_blockstore( 2 * rent_exempt_amount, blockhash, ); - let ix_error_signature = ix_error_tx.signatures[0]; + signatures.push(ix_error_tx.signatures[0]); let entry_2 = solana_entry::entry::next_entry(&entry_1.hash, 1, vec![ix_error_tx]); - let entries = vec![entry_1, entry_2]; + (vec![entry_1, entry_2], signatures) +} +pub fn populate_blockstore_for_tests( + entries: Vec, + bank: Arc, + blockstore: Arc, + max_complete_transaction_status_slot: Arc, +) { + let slot = bank.slot(); + let parent_slot = bank.parent_slot(); let shreds = - solana_ledger::blockstore::entries_to_test_shreds(&entries, slot, previous_slot, true, 0); + solana_ledger::blockstore::entries_to_test_shreds(&entries, slot, parent_slot, true, 0); blockstore.insert_shreds(shreds, None, false).unwrap(); blockstore.set_roots(std::iter::once(&slot)).unwrap(); @@ -4359,8 +4365,6 @@ pub fn create_test_transactions_and_populate_blockstore( ); transaction_status_service.join().unwrap(); - - vec![success_signature, ix_error_signature] } #[cfg(test)] @@ -4379,13 +4383,16 @@ pub mod tests { jsonrpc_core::{futures, ErrorCode, MetaIoHandler, Output, Response, Value}, jsonrpc_core_client::transports::local, serde::de::DeserializeOwned, + solana_address_lookup_table_program::state::{AddressLookupTable, LookupTableMeta}, solana_client::{ rpc_custom_error::{ JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE, JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE, + JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, }, rpc_filter::{Memcmp, MemcmpEncodedBytes}, }, + solana_entry::entry::next_versioned_entry, solana_gossip::{contact_info::ContactInfo, socketaddr}, solana_ledger::{ blockstore_meta::PerfSample, @@ -4402,12 +4409,15 @@ pub mod tests { fee_calculator::DEFAULT_BURN_PERCENT, hash::{hash, Hash}, instruction::InstructionError, - message::{v0, MessageHeader, VersionedMessage}, + message::{v0, v0::MessageAddressTableLookup, MessageHeader, VersionedMessage}, nonce, rpc_port, signature::{Keypair, Signer}, + slot_hashes::SlotHashes, system_program, system_transaction, timing::slot_duration_from_slots_per_year, - transaction::{self, DisabledAddressLoader, Transaction, TransactionError}, + transaction::{ + self, DisabledAddressLoader, Transaction, TransactionError, TransactionVersion, + }, }, solana_transaction_status::{ EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, @@ -4421,7 +4431,7 @@ pub mod tests { solana_program::{program_option::COption, pubkey::Pubkey as SplTokenPubkey}, state::{AccountState as TokenAccountState, Mint}, }, - std::collections::HashMap, + std::{borrow::Cow, collections::HashMap}, }; fn spl_token_id() -> Pubkey { @@ -4557,22 +4567,107 @@ pub mod tests { serde_json::from_str(response).expect("failed to deserialize response") } + fn overwrite_working_bank_entries(&self, entries: Vec) { + populate_blockstore_for_tests( + entries, + self.working_bank(), + self.blockstore.clone(), + self.max_complete_transaction_status_slot.clone(), + ); + } + fn create_test_transactions_and_populate_blockstore(&self) -> Vec { let mint_keypair = &self.mint_keypair; let keypair1 = Keypair::new(); let keypair2 = Keypair::new(); let keypair3 = Keypair::new(); - let bank = self.bank_forks.read().unwrap().working_bank(); + let bank = self.working_bank(); let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0); bank.transfer(rent_exempt_amount, mint_keypair, &keypair2.pubkey()) .unwrap(); - create_test_transactions_and_populate_blockstore( + + let (entries, signatures) = create_test_transaction_entries( vec![&self.mint_keypair, &keypair1, &keypair2, &keypair3], - 0, bank, - self.blockstore.clone(), - self.max_complete_transaction_status_slot.clone(), - ) + ); + self.overwrite_working_bank_entries(entries); + signatures + } + + fn create_test_versioned_transactions_and_populate_blockstore( + &self, + address_table_key: Option, + ) -> Vec { + let address_table_key = + address_table_key.unwrap_or_else(|| self.store_address_lookup_table()); + + let bank = self.working_bank(); + let recent_blockhash = bank.confirmed_last_blockhash(); + let legacy_message = VersionedMessage::Legacy(Message { + header: MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 0, + }, + recent_blockhash, + account_keys: vec![self.mint_keypair.pubkey()], + instructions: vec![], + }); + let version_0_message = VersionedMessage::V0(v0::Message { + header: MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 0, + }, + recent_blockhash, + account_keys: vec![self.mint_keypair.pubkey()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: address_table_key, + writable_indexes: vec![0], + readonly_indexes: vec![], + }], + instructions: vec![], + }); + + let mut signatures = Vec::new(); + let legacy_tx = + VersionedTransaction::try_new(legacy_message, &[&self.mint_keypair]).unwrap(); + signatures.push(legacy_tx.signatures[0]); + let version_0_tx = + VersionedTransaction::try_new(version_0_message, &[&self.mint_keypair]).unwrap(); + signatures.push(version_0_tx.signatures[0]); + let entry1 = next_versioned_entry(&recent_blockhash, 1, vec![legacy_tx]); + let entry2 = next_versioned_entry(&entry1.hash, 1, vec![version_0_tx]); + let entries = vec![entry1, entry2]; + self.overwrite_working_bank_entries(entries); + signatures + } + + fn store_address_lookup_table(&self) -> Pubkey { + let bank = self.working_bank(); + let address_table_pubkey = Pubkey::new_unique(); + let address_table_account = { + let address_table_state = AddressLookupTable { + meta: LookupTableMeta { + // ensure that active address length is 1 at slot 0 + last_extended_slot_start_index: 1, + ..LookupTableMeta::default() + }, + addresses: Cow::Owned(vec![Pubkey::new_unique()]), + }; + let address_table_data = address_table_state.serialize_for_tests().unwrap(); + let min_balance_lamports = + bank.get_minimum_balance_for_rent_exemption(address_table_data.len()); + AccountSharedData::create( + min_balance_lamports, + address_table_data, + solana_address_lookup_table_program::id(), + false, + 0, + ) + }; + bank.store_account(&address_table_pubkey, &address_table_account); + address_table_pubkey } fn add_roots_to_blockstore(&self, mut roots: Vec) { @@ -6310,6 +6405,45 @@ pub mod tests { assert_eq!(result, expected); } + #[test] + fn test_get_block_with_versioned_tx() { + let rpc = RpcHandler::start(); + + let bank = rpc.working_bank(); + // Slot hashes is necessary for processing versioned txs. + bank.set_sysvar_for_tests(&SlotHashes::default()); + // Add both legacy and version #0 transactions to the block + rpc.create_test_versioned_transactions_and_populate_blockstore(None); + + let request = create_test_request( + "getBlock", + Some(json!([ + 0u64, + {"maxSupportedTransactionVersion": 0}, + ])), + ); + let result: Option = + parse_success_result(rpc.handle_request_sync(request)); + let confirmed_block = result.unwrap(); + assert_eq!(confirmed_block.transactions.len(), 2); + assert_eq!( + confirmed_block.transactions[0].version, + Some(TransactionVersion::LEGACY) + ); + assert_eq!( + confirmed_block.transactions[1].version, + Some(TransactionVersion::Number(0)) + ); + + let request = create_test_request("getBlock", Some(json!([0u64,]))); + let response = parse_failure_response(rpc.handle_request_sync(request)); + let expected = ( + JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, + String::from("Transaction version (0) is not supported"), + ); + assert_eq!(response, expected); + } + #[test] fn test_get_block() { let mut rpc = RpcHandler::start(); diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 7e3dec532a0802..1d350b2b404925 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -1192,7 +1192,7 @@ pub(crate) mod tests { optimistically_confirmed_bank_tracker::{ BankNotification, OptimisticallyConfirmedBank, OptimisticallyConfirmedBankTracker, }, - rpc::create_test_transactions_and_populate_blockstore, + rpc::{create_test_transaction_entries, populate_blockstore_for_tests}, rpc_pubsub::RpcSolPubSubInternal, rpc_pubsub_service, }, @@ -1411,9 +1411,12 @@ pub(crate) mod tests { let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root())); bank.transfer(rent_exempt_amount, &mint_keypair, &keypair2.pubkey()) .unwrap(); - let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore( - vec![&mint_keypair, &keypair1, &keypair2, &keypair3], - 0, + populate_blockstore_for_tests( + create_test_transaction_entries( + vec![&mint_keypair, &keypair1, &keypair2, &keypair3], + bank.clone(), + ) + .0, bank, blockstore.clone(), max_complete_transaction_status_slot, @@ -1527,9 +1530,12 @@ pub(crate) mod tests { let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root())); bank.transfer(rent_exempt_amount, &mint_keypair, &keypair2.pubkey()) .unwrap(); - let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore( - vec![&mint_keypair, &keypair1, &keypair2, &keypair3], - 0, + populate_blockstore_for_tests( + create_test_transaction_entries( + vec![&mint_keypair, &keypair1, &keypair2, &keypair3], + bank.clone(), + ) + .0, bank, blockstore.clone(), max_complete_transaction_status_slot, @@ -1638,9 +1644,12 @@ pub(crate) mod tests { let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root())); bank.transfer(rent_exempt_amount, &mint_keypair, &keypair2.pubkey()) .unwrap(); - let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore( - vec![&mint_keypair, &keypair1, &keypair2, &keypair3], - 0, + populate_blockstore_for_tests( + create_test_transaction_entries( + vec![&mint_keypair, &keypair1, &keypair2, &keypair3], + bank.clone(), + ) + .0, bank, blockstore.clone(), max_complete_transaction_status_slot, diff --git a/runtime/src/accounts.rs b/runtime/src/accounts.rs index b92b0440f2542d..f384e69077b653 100644 --- a/runtime/src/accounts.rs +++ b/runtime/src/accounts.rs @@ -2057,14 +2057,9 @@ mod tests { meta: LookupTableMeta::default(), addresses: Cow::Owned(table_addresses.clone()), }; - let table_data = { - let mut data = vec![]; - table_state.serialize_for_tests(&mut data).unwrap(); - data - }; AccountSharedData::create( 1, - table_data, + table_state.serialize_for_tests().unwrap(), solana_address_lookup_table_program::id(), false, 0, diff --git a/sdk/src/transaction/versioned.rs b/sdk/src/transaction/versioned.rs index 122de5ede872d2..650f0810ebcc6e 100644 --- a/sdk/src/transaction/versioned.rs +++ b/sdk/src/transaction/versioned.rs @@ -17,13 +17,24 @@ use { std::cmp::Ordering, }; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +/// Type that serializes to the string "legacy" +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Legacy { + Legacy, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", untagged)] pub enum TransactionVersion { - Legacy, + Legacy(Legacy), Number(u8), } +impl TransactionVersion { + pub const LEGACY: Self = Self::Legacy(Legacy::Legacy); +} + // NOTE: Serialization-related changes must be paired with the direct read at sigverify. /// An atomic transaction #[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)] @@ -103,7 +114,7 @@ impl VersionedTransaction { /// Returns the version of the transaction pub fn version(&self) -> TransactionVersion { match self.message { - VersionedMessage::Legacy(_) => TransactionVersion::Legacy, + VersionedMessage::Legacy(_) => TransactionVersion::LEGACY, VersionedMessage::V0(_) => TransactionVersion::Number(0), } } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index f9d191467c5008..865b69b07e218d 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -689,11 +689,11 @@ impl VersionedTransactionWithStatusMeta { self.transaction.version(), ) { // Set to none because old clients can't handle this field - (None, TransactionVersion::Legacy) => Ok(None), + (None, TransactionVersion::LEGACY) => Ok(None), (None, TransactionVersion::Number(version)) => { Err(EncodeError::UnsupportedTransactionVersion(version)) } - (Some(_), TransactionVersion::Legacy) => Ok(Some(TransactionVersion::Legacy)), + (Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)), (Some(max_version), TransactionVersion::Number(version)) => { if version <= max_version { Ok(Some(TransactionVersion::Number(version)))