diff --git a/Cargo.lock b/Cargo.lock index 054329e8b0..4dd9580596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4848,6 +4848,7 @@ dependencies = [ "serde_json", "serial_test", "solana-accountsdb-plugin-manager", + "solana-address-lookup-table-program", "solana-client", "solana-entry", "solana-frozen-abi 1.10.0", diff --git a/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs b/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs index c06f9eaf4f..2d6886652b 100644 --- a/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs +++ b/accountsdb-plugin-postgres/src/postgres_client/postgres_client_transaction.rs @@ -1034,6 +1034,10 @@ pub(crate) mod tests { commission: Some(11), }, ]), + loaded_addresses: LoadedAddresses { + writable: vec![Pubkey::new_unique()], + readonly: vec![Pubkey::new_unique()], + }, } } diff --git a/client-test/tests/client.rs b/client-test/tests/client.rs index 8f0116e106..21597ad185 100644 --- a/client-test/tests/client.rs +++ b/client-test/tests/client.rs @@ -275,8 +275,9 @@ fn test_block_subscription() { let maybe_actual = receiver.recv_timeout(Duration::from_millis(400)); match maybe_actual { Ok(actual) => { - let complete_block = blockstore.get_complete_block(slot, false).unwrap(); - let block = complete_block.clone().configure( + let versioned_block = blockstore.get_complete_block(slot, false).unwrap(); + let legacy_block = versioned_block.into_legacy_block().unwrap(); + let block = legacy_block.clone().configure( UiTransactionEncoding::Json, TransactionDetails::Signatures, false, @@ -286,7 +287,7 @@ fn test_block_subscription() { block: Some(block), err: None, }; - let block = complete_block.configure( + let block = legacy_block.configure( UiTransactionEncoding::Json, TransactionDetails::Signatures, false, diff --git a/client/src/rpc_custom_error.rs b/client/src/rpc_custom_error.rs index 6b0aa2f959..2f1bcd7e17 100644 --- a/client/src/rpc_custom_error.rs +++ b/client/src/rpc_custom_error.rs @@ -20,6 +20,7 @@ pub const JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: i64 = -32011; pub const JSON_RPC_SCAN_ERROR: i64 = -32012; pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013; pub const JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: i64 = -32014; +pub const JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: i64 = -32015; #[derive(Error, Debug)] pub enum RpcCustomError { @@ -57,6 +58,8 @@ pub enum RpcCustomError { TransactionSignatureLenMismatch, #[error("BlockStatusNotAvailableYet")] BlockStatusNotAvailableYet { slot: Slot }, + #[error("UnsupportedTransactionVersion")] + UnsupportedTransactionVersion, } #[derive(Debug, Serialize, Deserialize)] @@ -169,6 +172,11 @@ impl From for Error { message: format!("Block status not yet available for slot {}", slot), data: None, }, + RpcCustomError::UnsupportedTransactionVersion => Self { + code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION), + message: "Versioned transactions are not supported".to_string(), + data: None, + }, } } } diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index a576c6168e..38bea673c3 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -429,6 +429,9 @@ pub struct RpcInflationReward { pub enum RpcBlockUpdateError { #[error("block store error")] BlockStoreError, + + #[error("unsupported transaction version")] + UnsupportedTransactionVersion, } #[derive(Serialize, Deserialize, Debug)] diff --git a/core/Cargo.toml b/core/Cargo.toml index 66fc4fab3e..4c732bd255 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -33,6 +33,7 @@ rayon = "1.5.1" retain_mut = "0.1.5" serde = "1.0.133" serde_derive = "1.0.103" +solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.0" } solana-accountsdb-plugin-manager = { path = "../accountsdb-plugin-manager", version = "=1.10.0" } solana-client = { path = "../client", version = "=1.10.0" } solana-entry = { path = "../entry", version = "=1.10.0" } diff --git a/core/src/banking_stage.rs b/core/src/banking_stage.rs index b0c9f6b6d5..bee0de635a 100644 --- a/core/src/banking_stage.rs +++ b/core/src/banking_stage.rs @@ -1477,7 +1477,8 @@ mod tests { super::*, crossbeam_channel::{unbounded, Receiver}, itertools::Itertools, - solana_entry::entry::{next_entry, Entry, EntrySlice}, + solana_address_lookup_table_program::state::{AddressLookupTable, LookupTableMeta}, + solana_entry::entry::{next_entry, next_versioned_entry, Entry, EntrySlice}, solana_gossip::{cluster_info::Node, contact_info::ContactInfo}, solana_ledger::{ blockstore::{entries_to_test_shreds, Blockstore}, @@ -1493,8 +1494,10 @@ mod tests { solana_rpc::transaction_status_service::TransactionStatusService, solana_runtime::bank::TransactionExecutionDetails, solana_sdk::{ + account::AccountSharedData, hash::Hash, instruction::InstructionError, + message::{v0, MessageHeader, VersionedMessage}, poh_config::PohConfig, signature::{Keypair, Signer}, system_instruction::SystemError, @@ -1502,9 +1505,10 @@ mod tests { transaction::{Transaction, TransactionError}, }, solana_streamer::{recvmmsg::recv_mmsg, socket::SocketAddrSpace}, - solana_transaction_status::TransactionWithStatusMeta, + solana_transaction_status::{TransactionStatusMeta, VersionedTransactionWithStatusMeta}, solana_vote_program::vote_transaction, std::{ + borrow::Cow, net::SocketAddr, path::Path, sync::atomic::{AtomicBool, Ordering}, @@ -2525,7 +2529,7 @@ mod tests { let confirmed_block = blockstore.get_rooted_block(bank.slot(), false).unwrap(); assert_eq!(confirmed_block.transactions.len(), 3); - for TransactionWithStatusMeta { transaction, meta } in + for VersionedTransactionWithStatusMeta { transaction, meta } in confirmed_block.transactions.into_iter() { if transaction.signatures[0] == success_signature { @@ -2555,6 +2559,165 @@ mod tests { Blockstore::destroy(&ledger_path).unwrap(); } + fn generate_new_address_lookup_table( + authority: Option, + num_addresses: usize, + ) -> AddressLookupTable<'static> { + let mut addresses = Vec::with_capacity(num_addresses); + addresses.resize_with(num_addresses, Pubkey::new_unique); + AddressLookupTable { + meta: LookupTableMeta { + authority, + ..LookupTableMeta::default() + }, + addresses: Cow::Owned(addresses), + } + } + + fn store_address_lookup_table( + bank: &Bank, + account_address: Pubkey, + address_lookup_table: AddressLookupTable<'static>, + ) -> AccountSharedData { + let mut data = Vec::new(); + address_lookup_table.serialize_for_tests(&mut data).unwrap(); + + let mut account = + AccountSharedData::new(1, data.len(), &solana_address_lookup_table_program::id()); + account.set_data(data); + bank.store_account(&account_address, &account); + + account + } + + #[test] + fn test_write_persist_loaded_addresses() { + solana_logger::setup(); + let GenesisConfigInfo { + genesis_config, + mint_keypair, + .. + } = create_slow_genesis_config(10_000); + let bank = Arc::new(Bank::new_no_wallclock_throttle_for_tests(&genesis_config)); + let keypair = Keypair::new(); + + let address_table_key = Pubkey::new_unique(); + let address_table_state = generate_new_address_lookup_table(None, 2); + store_address_lookup_table(&bank, address_table_key, address_table_state); + + let bank = Arc::new(Bank::new_from_parent(&bank, &Pubkey::new_unique(), 1)); + let message = VersionedMessage::V0(v0::Message { + header: MessageHeader { + num_required_signatures: 1, + num_readonly_signed_accounts: 0, + num_readonly_unsigned_accounts: 0, + }, + recent_blockhash: genesis_config.hash(), + account_keys: vec![keypair.pubkey()], + address_table_lookups: vec![MessageAddressTableLookup { + account_key: address_table_key, + writable_indexes: vec![0], + readonly_indexes: vec![1], + }], + instructions: vec![], + }); + + let tx = VersionedTransaction::try_new(message, &[&keypair]).unwrap(); + let message_hash = tx.message.hash(); + let sanitized_tx = + SanitizedTransaction::try_create(tx.clone(), message_hash, Some(false), |lookups| { + Ok(bank.load_lookup_table_addresses(lookups).unwrap()) + }) + .unwrap(); + + let entry = next_versioned_entry(&genesis_config.hash(), 1, vec![tx]); + let entries = vec![entry]; + + // todo: check if sig fees are needed + bank.transfer(1, &mint_keypair, &keypair.pubkey()).unwrap(); + + let ledger_path = get_tmp_ledger_path!(); + { + let blockstore = Blockstore::open(&ledger_path) + .expect("Expected to be able to open database ledger"); + let blockstore = Arc::new(blockstore); + let (poh_recorder, _entry_receiver, record_receiver) = PohRecorder::new( + bank.tick_height(), + bank.last_blockhash(), + bank.clone(), + Some((4, 4)), + bank.ticks_per_slot(), + &Pubkey::new_unique(), + &blockstore, + &Arc::new(LeaderScheduleCache::new_from_bank(&bank)), + &Arc::new(PohConfig::default()), + Arc::new(AtomicBool::default()), + ); + let recorder = poh_recorder.recorder(); + let poh_recorder = Arc::new(Mutex::new(poh_recorder)); + + let poh_simulator = simulate_poh(record_receiver, &poh_recorder); + + poh_recorder.lock().unwrap().set_bank(&bank); + + let shreds = entries_to_test_shreds(&entries, bank.slot(), 0, true, 0); + blockstore.insert_shreds(shreds, None, false).unwrap(); + blockstore.set_roots(std::iter::once(&bank.slot())).unwrap(); + + let (transaction_status_sender, transaction_status_receiver) = unbounded(); + let transaction_status_service = TransactionStatusService::new( + transaction_status_receiver, + Arc::new(AtomicU64::default()), + true, + None, + blockstore.clone(), + &Arc::new(AtomicBool::new(false)), + ); + + let (gossip_vote_sender, _gossip_vote_receiver) = unbounded(); + + let _ = BankingStage::process_and_record_transactions( + &bank, + &[sanitized_tx.clone()], + &recorder, + 0, + Some(TransactionStatusSender { + sender: transaction_status_sender, + enable_cpi_and_log_storage: false, + }), + &gossip_vote_sender, + &QosService::new(Arc::new(RwLock::new(CostModel::default())), 1), + ); + + transaction_status_service.join().unwrap(); + + let mut confirmed_block = blockstore.get_rooted_block(bank.slot(), false).unwrap(); + assert_eq!(confirmed_block.transactions.len(), 1); + + let recorded_meta = confirmed_block.transactions.pop().unwrap().meta; + assert_eq!( + recorded_meta, + Some(TransactionStatusMeta { + status: Ok(()), + pre_balances: vec![1, 0, 0], + post_balances: vec![1, 0, 0], + pre_token_balances: Some(vec![]), + post_token_balances: Some(vec![]), + rewards: Some(vec![]), + loaded_addresses: sanitized_tx.get_loaded_addresses(), + ..TransactionStatusMeta::default() + }) + ); + poh_recorder + .lock() + .unwrap() + .is_exited + .store(true, Ordering::Relaxed); + let _ = poh_simulator.join(); + } + Blockstore::destroy(&ledger_path).unwrap(); + } + #[allow(clippy::type_complexity)] fn setup_conflicting_transactions( ledger_path: &Path, diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index b958f0e21e..7d9fb24c60 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -3003,7 +3003,7 @@ pub mod tests { transaction::TransactionError, }, solana_streamer::socket::SocketAddrSpace, - solana_transaction_status::TransactionWithStatusMeta, + solana_transaction_status::VersionedTransactionWithStatusMeta, solana_vote_program::{ vote_state::{VoteState, VoteStateVersions}, vote_transaction, @@ -3860,7 +3860,7 @@ pub mod tests { let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap(); assert_eq!(confirmed_block.transactions.len(), 3); - for TransactionWithStatusMeta { transaction, meta } in + for VersionedTransactionWithStatusMeta { transaction, meta } in confirmed_block.transactions.into_iter() { if transaction.signatures[0] == signatures[0] { diff --git a/entry/src/entry.rs b/entry/src/entry.rs index 1a45e86023..1615b86519 100644 --- a/entry/src/entry.rs +++ b/entry/src/entry.rs @@ -898,8 +898,17 @@ pub fn create_random_ticks(num_ticks: u64, max_hashes_per_tick: u64, mut hash: H /// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`. pub fn next_entry(prev_hash: &Hash, num_hashes: u64, transactions: Vec) -> Entry { - assert!(num_hashes > 0 || transactions.is_empty()); let transactions = transactions.into_iter().map(Into::into).collect::>(); + next_versioned_entry(prev_hash, num_hashes, transactions) +} + +/// Creates the next Tick or Transaction Entry `num_hashes` after `start_hash`. +pub fn next_versioned_entry( + prev_hash: &Hash, + num_hashes: u64, + transactions: Vec, +) -> Entry { + assert!(num_hashes > 0 || transactions.is_empty()); Entry { num_hashes, hash: next_hash(prev_hash, num_hashes, &transactions), diff --git a/ledger-tool/src/bigtable.rs b/ledger-tool/src/bigtable.rs index 0247c4298b..a64f572c69 100644 --- a/ledger-tool/src/bigtable.rs +++ b/ledger-tool/src/bigtable.rs @@ -73,7 +73,10 @@ async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box { + Ok(Some(versioned_confirmed_tx)) => { + let confirmed_transaction = versioned_confirmed_tx + .into_legacy_confirmed_transaction() + .ok_or_else(|| "Failed to read versioned transaction in block".to_string())?; + transaction = Some(CliTransaction { transaction: confirmed_transaction .transaction @@ -260,7 +267,10 @@ pub async fn transaction_history( println!(" Unable to get confirmed transaction details: {}", err); break; } - Ok(block) => { + Ok(versioned_block) => { + let block = versioned_block.into_legacy_block().ok_or_else(|| { + "Failed to read versioned transaction in block".to_string() + })?; loaded_block = Some((result.slot, block)); } } diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 52965fe2f6..f998a0e690 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -42,9 +42,9 @@ use { }, solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta}, solana_transaction_status::{ - ConfirmedBlock, ConfirmedTransactionStatusWithSignature, - ConfirmedTransactionWithStatusMeta, Rewards, TransactionStatusMeta, - TransactionWithStatusMeta, + ConfirmedTransactionStatusWithSignature, Rewards, TransactionStatusMeta, + VersionedConfirmedBlock, VersionedConfirmedTransactionWithStatusMeta, + VersionedTransactionWithStatusMeta, }, std::{ borrow::Cow, @@ -1923,7 +1923,7 @@ impl Blockstore { &self, slot: Slot, require_previous_blockhash: bool, - ) -> Result { + ) -> Result { datapoint_info!( "blockstore-rpc-api", ("method", "get_rooted_block".to_string(), String) @@ -1940,7 +1940,7 @@ impl Blockstore { &self, slot: Slot, require_previous_blockhash: bool, - ) -> Result { + ) -> Result { let slot_meta_cf = self.db.column::(); let slot_meta = match slot_meta_cf.get(slot)? { Some(slot_meta) => slot_meta, @@ -1998,7 +1998,7 @@ impl Blockstore { let block_time = self.blocktime_cf.get(slot)?; let block_height = self.block_height_cf.get(slot)?; - let block = ConfirmedBlock { + let block = VersionedConfirmedBlock { previous_blockhash: previous_blockhash.to_string(), blockhash: blockhash.to_string(), // If the slot is full it should have parent_slot populated @@ -2020,22 +2020,17 @@ impl Blockstore { &self, slot: Slot, iterator: impl Iterator, - ) -> Result> { + ) -> Result> { iterator - .map(|versioned_tx| { - // TODO: add support for versioned transactions - if let Some(transaction) = versioned_tx.into_legacy_transaction() { - let signature = transaction.signatures[0]; - Ok(TransactionWithStatusMeta { - transaction, - meta: self - .read_transaction_status((signature, slot)) - .ok() - .flatten(), - }) - } else { - Err(BlockstoreError::UnsupportedTransactionVersion) - } + .map(|transaction| { + let signature = transaction.signatures[0]; + Ok(VersionedTransactionWithStatusMeta { + transaction, + meta: self + .read_transaction_status((signature, slot)) + .ok() + .flatten(), + }) }) .collect() } @@ -2287,7 +2282,7 @@ impl Blockstore { pub fn get_rooted_transaction( &self, signature: Signature, - ) -> Result> { + ) -> Result> { datapoint_info!( "blockstore-rpc-api", ("method", "get_rooted_transaction".to_string(), String) @@ -2300,7 +2295,7 @@ impl Blockstore { &self, signature: Signature, highest_confirmed_slot: Slot, - ) -> Result> { + ) -> Result> { datapoint_info!( "blockstore-rpc-api", ("method", "get_complete_transaction".to_string(), String) @@ -2317,7 +2312,7 @@ impl Blockstore { &self, signature: Signature, confirmed_unrooted_slots: &[Slot], - ) -> Result> { + ) -> Result> { if let Some((slot, status)) = self.get_transaction_status(signature, confirmed_unrooted_slots)? { @@ -2325,15 +2320,10 @@ impl Blockstore { .find_transaction_in_slot(slot, signature)? .ok_or(BlockstoreError::TransactionStatusSlotMismatch)?; // Should not happen - // TODO: support retrieving versioned transactions - let transaction = transaction - .into_legacy_transaction() - .ok_or(BlockstoreError::UnsupportedTransactionVersion)?; - let block_time = self.get_block_time(slot)?; - Ok(Some(ConfirmedTransactionWithStatusMeta { + Ok(Some(VersionedConfirmedTransactionWithStatusMeta { slot, - transaction: TransactionWithStatusMeta { + tx_with_meta: VersionedTransactionWithStatusMeta { transaction, meta: Some(status), }, @@ -4146,6 +4136,7 @@ pub mod tests { solana_sdk::{ hash::{self, hash, Hash}, instruction::CompiledInstruction, + message::v0::LoadedAddresses, packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Signature, @@ -6237,20 +6228,15 @@ pub mod tests { .put_meta_bytes(slot - 1, &serialize(&parent_meta).unwrap()) .unwrap(); - let expected_transactions: Vec = entries + let expected_transactions: Vec = entries .iter() .cloned() .filter(|entry| !entry.is_tick()) .flat_map(|entry| entry.transactions) - .map(|transaction| { - transaction - .into_legacy_transaction() - .expect("versioned transactions not supported") - }) .map(|transaction| { let mut pre_balances: Vec = vec![]; let mut post_balances: Vec = vec![]; - for (i, _account_key) in transaction.message.account_keys.iter().enumerate() { + for i in 0..transaction.message.total_account_keys_len() { pre_balances.push(i as u64 * 10); post_balances.push(i as u64 * 11); } @@ -6265,6 +6251,7 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), } .into(); blockstore @@ -6281,6 +6268,7 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), } .into(); blockstore @@ -6297,13 +6285,14 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), } .into(); blockstore .transaction_status_cf .put_protobuf((0, signature, slot + 2), &status) .unwrap(); - TransactionWithStatusMeta { + VersionedTransactionWithStatusMeta { transaction, meta: Some(TransactionStatusMeta { status: Ok(()), @@ -6315,6 +6304,7 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), }), } }) @@ -6335,7 +6325,7 @@ pub mod tests { // Test if require_previous_blockhash is false let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap(); assert_eq!(confirmed_block.transactions.len(), 100); - let expected_block = ConfirmedBlock { + let expected_block = VersionedConfirmedBlock { transactions: expected_transactions.clone(), parent_slot: slot - 1, blockhash: blockhash.to_string(), @@ -6349,7 +6339,7 @@ pub mod tests { let confirmed_block = blockstore.get_rooted_block(slot + 1, true).unwrap(); assert_eq!(confirmed_block.transactions.len(), 100); - let mut expected_block = ConfirmedBlock { + let mut expected_block = VersionedConfirmedBlock { transactions: expected_transactions.clone(), parent_slot: slot, blockhash: blockhash.to_string(), @@ -6366,7 +6356,7 @@ pub mod tests { let complete_block = blockstore.get_complete_block(slot + 2, true).unwrap(); assert_eq!(complete_block.transactions.len(), 100); - let mut expected_complete_block = ConfirmedBlock { + let mut expected_complete_block = VersionedConfirmedBlock { transactions: expected_transactions, parent_slot: slot + 1, blockhash: blockhash.to_string(), @@ -6422,6 +6412,10 @@ pub mod tests { let pre_token_balances_vec = vec![]; let post_token_balances_vec = vec![]; let rewards_vec = vec![]; + let test_loaded_addresses = LoadedAddresses { + writable: vec![Pubkey::new_unique()], + readonly: vec![Pubkey::new_unique()], + }; // result not found assert!(transaction_status_cf @@ -6440,6 +6434,7 @@ pub mod tests { pre_token_balances: Some(pre_token_balances_vec.clone()), post_token_balances: Some(post_token_balances_vec.clone()), rewards: Some(rewards_vec.clone()), + loaded_addresses: test_loaded_addresses.clone(), } .into(); assert!(transaction_status_cf @@ -6457,6 +6452,7 @@ pub mod tests { pre_token_balances, post_token_balances, rewards, + loaded_addresses, } = transaction_status_cf .get_protobuf_or_bincode::((0, Signature::default(), 0)) .unwrap() @@ -6472,6 +6468,7 @@ pub mod tests { assert_eq!(pre_token_balances.unwrap(), pre_token_balances_vec); assert_eq!(post_token_balances.unwrap(), post_token_balances_vec); assert_eq!(rewards.unwrap(), rewards_vec); + assert_eq!(loaded_addresses, test_loaded_addresses); // insert value let status = TransactionStatusMeta { @@ -6484,6 +6481,7 @@ pub mod tests { pre_token_balances: Some(pre_token_balances_vec.clone()), post_token_balances: Some(post_token_balances_vec.clone()), rewards: Some(rewards_vec.clone()), + loaded_addresses: test_loaded_addresses.clone(), } .into(); assert!(transaction_status_cf @@ -6501,6 +6499,7 @@ pub mod tests { pre_token_balances, post_token_balances, rewards, + loaded_addresses, } = transaction_status_cf .get_protobuf_or_bincode::(( 0, @@ -6522,6 +6521,7 @@ pub mod tests { assert_eq!(pre_token_balances.unwrap(), pre_token_balances_vec); assert_eq!(post_token_balances.unwrap(), post_token_balances_vec); assert_eq!(rewards.unwrap(), rewards_vec); + assert_eq!(loaded_addresses, test_loaded_addresses); } #[test] @@ -6749,6 +6749,7 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), } .into(); @@ -6943,6 +6944,7 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), } .into(); @@ -7093,19 +7095,15 @@ pub mod tests { blockstore.insert_shreds(shreds, None, false).unwrap(); blockstore.set_roots(vec![slot - 1, slot].iter()).unwrap(); - let expected_transactions: Vec = entries + let expected_transactions: Vec = entries .iter() .cloned() .filter(|entry| !entry.is_tick()) .flat_map(|entry| entry.transactions) - .map(|tx| { - tx.into_legacy_transaction() - .expect("versioned transactions not supported") - }) .map(|transaction| { let mut pre_balances: Vec = vec![]; let mut post_balances: Vec = vec![]; - for (i, _account_key) in transaction.message.account_keys.iter().enumerate() { + for i in 0..transaction.message.total_account_keys_len() { pre_balances.push(i as u64 * 10); post_balances.push(i as u64 * 11); } @@ -7128,13 +7126,14 @@ pub mod tests { pre_token_balances: pre_token_balances.clone(), post_token_balances: post_token_balances.clone(), rewards: rewards.clone(), + loaded_addresses: LoadedAddresses::default(), } .into(); blockstore .transaction_status_cf .put_protobuf((0, signature, slot), &status) .unwrap(); - TransactionWithStatusMeta { + VersionedTransactionWithStatusMeta { transaction, meta: Some(TransactionStatusMeta { status: Ok(()), @@ -7146,18 +7145,19 @@ pub mod tests { pre_token_balances, post_token_balances, rewards, + loaded_addresses: LoadedAddresses::default(), }), } }) .collect(); - for transaction in expected_transactions.clone() { - let signature = transaction.transaction.signatures[0]; + for tx_with_meta in expected_transactions.clone() { + let signature = tx_with_meta.transaction.signatures[0]; assert_eq!( blockstore.get_rooted_transaction(signature).unwrap(), - Some(ConfirmedTransactionWithStatusMeta { + Some(VersionedConfirmedTransactionWithStatusMeta { slot, - transaction: transaction.clone(), + tx_with_meta: tx_with_meta.clone(), block_time: None }) ); @@ -7165,9 +7165,9 @@ pub mod tests { blockstore .get_complete_transaction(signature, slot + 1) .unwrap(), - Some(ConfirmedTransactionWithStatusMeta { + Some(VersionedConfirmedTransactionWithStatusMeta { slot, - transaction, + tx_with_meta, block_time: None }) ); @@ -7175,7 +7175,7 @@ pub mod tests { blockstore.run_purge(0, 2, PurgeType::PrimaryIndex).unwrap(); *blockstore.lowest_cleanup_slot.write().unwrap() = slot; - for TransactionWithStatusMeta { transaction, .. } in expected_transactions { + for VersionedTransactionWithStatusMeta { transaction, .. } in expected_transactions { let signature = transaction.signatures[0]; assert_eq!(blockstore.get_rooted_transaction(signature).unwrap(), None,); assert_eq!( @@ -7197,19 +7197,15 @@ pub mod tests { let shreds = entries_to_test_shreds(&entries, slot, slot - 1, true, 0); blockstore.insert_shreds(shreds, None, false).unwrap(); - let expected_transactions: Vec = entries + let expected_transactions: Vec = entries .iter() .cloned() .filter(|entry| !entry.is_tick()) .flat_map(|entry| entry.transactions) - .map(|tx| { - tx.into_legacy_transaction() - .expect("versioned transactions not supported") - }) .map(|transaction| { let mut pre_balances: Vec = vec![]; let mut post_balances: Vec = vec![]; - for (i, _account_key) in transaction.message.account_keys.iter().enumerate() { + for i in 0..transaction.message.total_account_keys_len() { pre_balances.push(i as u64 * 10); post_balances.push(i as u64 * 11); } @@ -7232,13 +7228,14 @@ pub mod tests { pre_token_balances: pre_token_balances.clone(), post_token_balances: post_token_balances.clone(), rewards: rewards.clone(), + loaded_addresses: LoadedAddresses::default(), } .into(); blockstore .transaction_status_cf .put_protobuf((0, signature, slot), &status) .unwrap(); - TransactionWithStatusMeta { + VersionedTransactionWithStatusMeta { transaction, meta: Some(TransactionStatusMeta { status: Ok(()), @@ -7250,20 +7247,21 @@ pub mod tests { pre_token_balances, post_token_balances, rewards, + loaded_addresses: LoadedAddresses::default(), }), } }) .collect(); - for transaction in expected_transactions.clone() { - let signature = transaction.transaction.signatures[0]; + for tx_with_meta in expected_transactions.clone() { + let signature = tx_with_meta.transaction.signatures[0]; assert_eq!( blockstore .get_complete_transaction(signature, slot) .unwrap(), - Some(ConfirmedTransactionWithStatusMeta { + Some(VersionedConfirmedTransactionWithStatusMeta { slot, - transaction, + tx_with_meta, block_time: None }) ); @@ -7272,7 +7270,7 @@ pub mod tests { blockstore.run_purge(0, 2, PurgeType::PrimaryIndex).unwrap(); *blockstore.lowest_cleanup_slot.write().unwrap() = slot; - for TransactionWithStatusMeta { transaction, .. } in expected_transactions { + for VersionedTransactionWithStatusMeta { transaction, .. } in expected_transactions { let signature = transaction.signatures[0]; assert_eq!( blockstore @@ -7560,16 +7558,13 @@ pub mod tests { // Purge to freeze index 0 and write address-signatures in new primary index blockstore.run_purge(0, 1, PurgeType::PrimaryIndex).unwrap(); } - for tx in entry.transactions { - let transaction = tx - .into_legacy_transaction() - .expect("versioned transactions not supported"); + for transaction in entry.transactions { assert_eq!(transaction.signatures.len(), 1); blockstore .write_transaction_status( slot, transaction.signatures[0], - transaction.message.account_keys.iter().collect(), + transaction.message.static_account_keys_iter().collect(), vec![], TransactionStatusMeta::default(), ) @@ -7587,16 +7582,13 @@ pub mod tests { blockstore.insert_shreds(shreds, None, false).unwrap(); for entry in entries.into_iter() { - for tx in entry.transactions { - let transaction = tx - .into_legacy_transaction() - .expect("versioned transactions not supported"); + for transaction in entry.transactions { assert_eq!(transaction.signatures.len(), 1); blockstore .write_transaction_status( slot, transaction.signatures[0], - transaction.message.account_keys.iter().collect(), + transaction.message.static_account_keys_iter().collect(), vec![], TransactionStatusMeta::default(), ) @@ -8019,6 +8011,7 @@ pub mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), } .into(); transaction_status_cf @@ -8570,8 +8563,9 @@ pub mod tests { reward_type: Some(RewardType::Rent), commission: None, }]), + loaded_addresses: LoadedAddresses::default(), }; - let deprecated_status: StoredTransactionStatusMeta = status.clone().into(); + let deprecated_status: StoredTransactionStatusMeta = status.clone().try_into().unwrap(); let protobuf_status: generated::TransactionStatusMeta = status.into(); for slot in 0..2 { diff --git a/ledger/src/blockstore/blockstore_purge.rs b/ledger/src/blockstore/blockstore_purge.rs index 0779e27b04..af9dffebb3 100644 --- a/ledger/src/blockstore/blockstore_purge.rs +++ b/ledger/src/blockstore/blockstore_purge.rs @@ -327,19 +327,24 @@ impl Blockstore { let mut index1 = self.transaction_status_index_cf.get(1)?.unwrap_or_default(); for slot in from_slot..to_slot { let slot_entries = self.get_any_valid_slot_entries(slot, 0); - for transaction in slot_entries - .iter() - .cloned() - .flat_map(|entry| entry.transactions) - { + let transactions = slot_entries + .into_iter() + .flat_map(|entry| entry.transactions); + for transaction in transactions { if let Some(&signature) = transaction.signatures.get(0) { batch.delete::((0, signature, slot))?; batch.delete::((1, signature, slot))?; - // TODO: support purging dynamically loaded addresses from versioned transactions for pubkey in transaction.message.into_static_account_keys() { batch.delete::((0, pubkey, slot, signature))?; batch.delete::((1, pubkey, slot, signature))?; } + let meta = self.read_transaction_status((signature, slot))?; + let loaded_addresses = + meta.map(|meta| meta.loaded_addresses).unwrap_or_default(); + for address in loaded_addresses.into_ordered_iter() { + batch.delete::((0, address, slot, signature))?; + batch.delete::((1, address, slot, signature))?; + } } } } diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index b1c1f49eae..40a07790d7 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -45,7 +45,7 @@ use solana_sdk::{ entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, instruction::{AccountMeta, CompiledInstruction, Instruction, InstructionError}, loader_instruction, - message::{Message, SanitizedMessage}, + message::{v0::LoadedAddresses, Message, SanitizedMessage}, pubkey::Pubkey, signature::{keypair_from_seed, Keypair, Signer}, system_instruction::{self, MAX_PERMITTED_DATA_LENGTH}, @@ -421,6 +421,7 @@ fn execute_transactions( inner_instructions, log_messages, rewards: None, + loaded_addresses: LoadedAddresses::default(), }; Ok(ConfirmedTransactionWithStatusMeta { diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 3ce12dbb2b..77b5db5bbc 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -77,9 +77,10 @@ use { solana_storage_bigtable::Error as StorageError, solana_streamer::socket::SocketAddrSpace, solana_transaction_status::{ - ConfirmedBlock, ConfirmedTransactionStatusWithSignature, Encodable, + ConfirmedTransactionStatusWithSignature, Encodable, EncodedConfirmedTransactionWithStatusMeta, Reward, RewardType, TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding, + VersionedConfirmedBlock, }, solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}, spl_token::{ @@ -1002,48 +1003,62 @@ impl JsonRpcRequestProcessor { self.check_status_is_complete(slot)?; let result = self.blockstore.get_rooted_block(slot, true); self.check_blockstore_root(&result, slot)?; - let configure_block = |confirmed_block: ConfirmedBlock| { + let configure_block = |versioned_block: VersionedConfirmedBlock| { + let confirmed_block = versioned_block + .into_legacy_block() + .ok_or(RpcCustomError::UnsupportedTransactionVersion)?; let mut confirmed_block = confirmed_block.configure(encoding, transaction_details, show_rewards); if slot == 0 { confirmed_block.block_time = Some(self.genesis_creation_time()); confirmed_block.block_height = Some(0); } - confirmed_block + Ok(confirmed_block) }; if result.is_err() { if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage { let bigtable_result = bigtable_ledger_storage.get_confirmed_block(slot).await; self.check_bigtable_result(&bigtable_result)?; - return Ok(bigtable_result.ok().map(configure_block)); + return bigtable_result.ok().map(configure_block).transpose(); } } self.check_slot_cleaned_up(&result, slot)?; - return Ok(result.ok().map(configure_block)); + return result.ok().map(configure_block).transpose(); } else if commitment.is_confirmed() { // Check if block is confirmed let confirmed_bank = self.bank(Some(CommitmentConfig::confirmed())); if confirmed_bank.status_cache_ancestors().contains(&slot) { self.check_status_is_complete(slot)?; let result = self.blockstore.get_complete_block(slot, true); - return Ok(result.ok().map(|mut confirmed_block| { - if confirmed_block.block_time.is_none() - || confirmed_block.block_height.is_none() - { - let r_bank_forks = self.bank_forks.read().unwrap(); - let bank = r_bank_forks.get(slot).cloned(); - if let Some(bank) = bank { - if confirmed_block.block_time.is_none() { - confirmed_block.block_time = Some(bank.clock().unix_timestamp); - } - if confirmed_block.block_height.is_none() { - confirmed_block.block_height = Some(bank.block_height()); + return result + .ok() + .map(|versioned_block| { + let mut confirmed_block = versioned_block + .into_legacy_block() + .ok_or(RpcCustomError::UnsupportedTransactionVersion)?; + if confirmed_block.block_time.is_none() + || confirmed_block.block_height.is_none() + { + let r_bank_forks = self.bank_forks.read().unwrap(); + let bank = r_bank_forks.get(slot).cloned(); + if let Some(bank) = bank { + if confirmed_block.block_time.is_none() { + confirmed_block.block_time = + Some(bank.clock().unix_timestamp); + } + if confirmed_block.block_height.is_none() { + confirmed_block.block_height = Some(bank.block_height()); + } } } - } - confirmed_block.configure(encoding, transaction_details, show_rewards) - })); + Ok(confirmed_block.configure( + encoding, + transaction_details, + show_rewards, + )) + }) + .transpose(); } } } else { @@ -1368,15 +1383,20 @@ impl JsonRpcRequestProcessor { if self.config.enable_rpc_transaction_history { let confirmed_bank = self.bank(Some(CommitmentConfig::confirmed())); - let transaction = if commitment.is_confirmed() { + let versioned_confirmed_tx = if commitment.is_confirmed() { let highest_confirmed_slot = confirmed_bank.slot(); self.blockstore .get_complete_transaction(signature, highest_confirmed_slot) } else { self.blockstore.get_rooted_transaction(signature) }; - match transaction.unwrap_or(None) { - Some(mut confirmed_transaction) => { + + match versioned_confirmed_tx.unwrap_or(None) { + Some(versioned_confirmed_tx) => { + let mut confirmed_transaction = versioned_confirmed_tx + .into_legacy_confirmed_transaction() + .ok_or(RpcCustomError::UnsupportedTransactionVersion)?; + if commitment.is_confirmed() && confirmed_bank // should be redundant .status_cache_ancestors() @@ -1402,11 +1422,18 @@ impl JsonRpcRequestProcessor { } None => { if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage { - return Ok(bigtable_ledger_storage + return bigtable_ledger_storage .get_confirmed_transaction(&signature) .await .unwrap_or(None) - .map(|confirmed| confirmed.encode(encoding))); + .map(|versioned_confirmed_tx| { + let confirmed_tx = versioned_confirmed_tx + .into_legacy_confirmed_transaction() + .ok_or(RpcCustomError::UnsupportedTransactionVersion)?; + + Ok(confirmed_tx.encode(encoding)) + }) + .transpose(); } } } diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index 4ff4668b35..4982a272e2 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -957,7 +957,20 @@ impl RpcSubscriptions { if s > max_complete_transaction_status_slot.load(Ordering::SeqCst) { break; } - match blockstore.get_complete_block(s, false) { + + let block_result = blockstore + .get_complete_block(s, false) + .map_err(|e| { + error!("get_complete_block error: {}", e); + RpcBlockUpdateError::BlockStoreError + }) + .and_then(|versioned_block| { + versioned_block.into_legacy_block().ok_or( + RpcBlockUpdateError::UnsupportedTransactionVersion, + ) + }); + + match block_result { Ok(block) => { if let Some(res) = filter_block_result_txs(block, s, params) { @@ -975,17 +988,16 @@ impl RpcSubscriptions { *w_last_unnotified_slot = s + 1; } } - Err(e) => { + Err(err) => { // we don't advance `w_last_unnotified_slot` so that // it'll retry on the next notification trigger - error!("get_complete_block error: {}", e); notifier.notify( Response { context: RpcResponseContext { slot: s }, value: RpcBlockUpdate { slot, block: None, - err: Some(RpcBlockUpdateError::BlockStoreError), + err: Some(err), }, }, subscription, @@ -1398,8 +1410,9 @@ pub(crate) mod tests { let actual_resp = receiver.recv(); let actual_resp = serde_json::from_str::(&actual_resp).unwrap(); - let block = blockstore.get_complete_block(slot, false).unwrap(); - let block = block.configure(params.encoding, params.transaction_details, false); + let versioned_block = blockstore.get_complete_block(slot, false).unwrap(); + let legacy_block = versioned_block.into_legacy_block().unwrap(); + let block = legacy_block.configure(params.encoding, params.transaction_details, false); let expected_resp = RpcBlockUpdate { slot, block: Some(block), @@ -1497,14 +1510,16 @@ pub(crate) mod tests { let actual_resp = serde_json::from_str::(&actual_resp).unwrap(); // make sure it filtered out the other keypairs - let mut block = blockstore.get_complete_block(slot, false).unwrap(); - block.transactions.retain(|tx| { - tx.transaction + let versioned_block = blockstore.get_complete_block(slot, false).unwrap(); + let mut legacy_block = versioned_block.into_legacy_block().unwrap(); + legacy_block.transactions.retain(|tx_with_meta| { + tx_with_meta + .transaction .message .account_keys .contains(&keypair1.pubkey()) }); - let block = block.configure(params.encoding, params.transaction_details, false); + let block = legacy_block.configure(params.encoding, params.transaction_details, false); let expected_resp = RpcBlockUpdate { slot, block: Some(block), @@ -1594,8 +1609,9 @@ pub(crate) mod tests { let actual_resp = receiver.recv(); let actual_resp = serde_json::from_str::(&actual_resp).unwrap(); - let block = blockstore.get_complete_block(slot, false).unwrap(); - let block = block.configure(params.encoding, params.transaction_details, false); + let versioned_block = blockstore.get_complete_block(slot, false).unwrap(); + let legacy_block = versioned_block.into_legacy_block().unwrap(); + let block = legacy_block.configure(params.encoding, params.transaction_details, false); let expected_resp = RpcBlockUpdate { slot, block: Some(block), diff --git a/rpc/src/transaction_status_service.rs b/rpc/src/transaction_status_service.rs index 25618fe22c..79f4f22e82 100644 --- a/rpc/src/transaction_status_service.rs +++ b/rpc/src/transaction_status_service.rs @@ -141,7 +141,7 @@ impl TransactionStatusService { }) .collect(), ); - + let loaded_addresses = transaction.get_loaded_addresses(); let transaction_status_meta = TransactionStatusMeta { status, fee, @@ -152,6 +152,7 @@ impl TransactionStatusService { pre_token_balances, post_token_balances, rewards, + loaded_addresses, }; if let Some(transaction_notifier) = transaction_notifier.as_ref() { diff --git a/sdk/program/src/message/versions/mod.rs b/sdk/program/src/message/versions/mod.rs index ace0a3bf72..1b1d21047d 100644 --- a/sdk/program/src/message/versions/mod.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -71,6 +71,24 @@ impl VersionedMessage { } } + pub fn total_account_keys_len(&self) -> usize { + match self { + Self::Legacy(message) => message.account_keys.len(), + Self::V0(message) => message.account_keys.len().saturating_add( + message + .address_table_lookups + .iter() + .map(|lookup| { + lookup + .writable_indexes + .len() + .saturating_add(lookup.readonly_indexes.len()) + }) + .sum(), + ), + } + } + pub fn recent_blockhash(&self) -> &Hash { match self { Self::Legacy(message) => &message.recent_blockhash, @@ -85,6 +103,13 @@ impl VersionedMessage { } } + pub fn instructions(&self) -> &[CompiledInstruction] { + match self { + Self::Legacy(message) => &message.instructions, + Self::V0(message) => &message.instructions, + } + } + pub fn serialize(&self) -> Vec { bincode::serialize(self).unwrap() } diff --git a/sdk/program/src/message/versions/v0/loaded.rs b/sdk/program/src/message/versions/v0/loaded.rs index 1f67dfdf85..c3959427eb 100644 --- a/sdk/program/src/message/versions/v0/loaded.rs +++ b/sdk/program/src/message/versions/v0/loaded.rs @@ -5,7 +5,7 @@ use { pubkey::Pubkey, sysvar, }, - std::{collections::HashSet, convert::TryFrom, ops::Deref}, + std::{collections::HashSet, ops::Deref}, }; /// Combination of a version #0 message and its loaded addresses @@ -47,6 +47,30 @@ impl FromIterator for LoadedAddresses { } } +impl LoadedAddresses { + /// Checks if there are no writable or readonly addresses + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Combined length of loaded writable and readonly addresses + pub fn len(&self) -> usize { + self.writable.len().saturating_add(self.readonly.len()) + } + + /// Iterate over loaded addresses in the order that they are used + /// as account indexes + pub fn ordered_iter(&self) -> impl Iterator { + self.writable.iter().chain(self.readonly.iter()) + } + + /// Iterate over loaded addresses in the order that they are used + /// as account indexes + pub fn into_ordered_iter(self) -> impl Iterator { + self.writable.into_iter().chain(self.readonly.into_iter()) + } +} + impl LoadedMessage { /// Returns an iterator of account key segments. The ordering of segments /// affects how account indexes from compiled instructions are resolved and diff --git a/sdk/src/signer/mod.rs b/sdk/src/signer/mod.rs index 3a4c4cd022..f7d8cdc905 100644 --- a/sdk/src/signer/mod.rs +++ b/sdk/src/signer/mod.rs @@ -48,6 +48,9 @@ pub enum SignerError { #[error("{0}")] UserCancel(String), + + #[error("too many signers")] + TooManySigners, } /// The `Signer` trait declares operations that all digital signature providers diff --git a/sdk/src/transaction/sanitized.rs b/sdk/src/transaction/sanitized.rs index af95850fa6..43008efa02 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/src/transaction/sanitized.rs @@ -176,6 +176,14 @@ impl SanitizedTransaction { account_locks } + /// Return the list of addresses loaded from on-chain address lookup tables + pub fn get_loaded_addresses(&self) -> LoadedAddresses { + match &self.message { + SanitizedMessage::Legacy(_) => LoadedAddresses::default(), + SanitizedMessage::V0(message) => message.loaded_addresses.clone(), + } + } + /// If the transaction 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.message.get_durable_nonce(nonce_must_be_writable) diff --git a/sdk/src/transaction/versioned.rs b/sdk/src/transaction/versioned.rs index fa56596e11..222260d822 100644 --- a/sdk/src/transaction/versioned.rs +++ b/sdk/src/transaction/versioned.rs @@ -9,6 +9,8 @@ use { sanitize::{Sanitize, SanitizeError}, short_vec, signature::Signature, + signer::SignerError, + signers::Signers, transaction::{Result, Transaction, TransactionError}, }, serde::Serialize, @@ -57,6 +59,40 @@ impl From for VersionedTransaction { } impl VersionedTransaction { + /// Signs a versioned message and if successful, returns a signed + /// transaction. + pub fn try_new( + message: VersionedMessage, + keypairs: &T, + ) -> std::result::Result { + let static_account_keys = message.static_account_keys(); + if static_account_keys.len() < message.header().num_required_signatures as usize { + return Err(SignerError::InvalidInput("invalid message".to_string())); + } + + let signer_keys = keypairs.pubkeys(); + let expected_signer_keys = + &static_account_keys[0..message.header().num_required_signatures as usize]; + + match signer_keys.len().cmp(&expected_signer_keys.len()) { + Ordering::Greater => Err(SignerError::TooManySigners), + Ordering::Less => Err(SignerError::NotEnoughSigners), + Ordering::Equal => Ok(()), + }?; + + if signer_keys != expected_signer_keys { + return Err(SignerError::KeypairPubkeyMismatch); + } + + let message_data = message.serialize(); + let signatures = keypairs.try_sign_message(&message_data)?; + + Ok(Self { + signatures, + message, + }) + } + /// Returns a legacy transaction if the transaction message is legacy. pub fn into_legacy_transaction(self) -> Option { match self.message { diff --git a/storage-bigtable/src/bigtable.rs b/storage-bigtable/src/bigtable.rs index bdd3e766e5..e38f27c062 100644 --- a/storage-bigtable/src/bigtable.rs +++ b/storage-bigtable/src/bigtable.rs @@ -790,10 +790,13 @@ mod tests { super::*, crate::StoredConfirmedBlock, prost::Message, - solana_sdk::{hash::Hash, signature::Keypair, system_transaction}, + solana_sdk::{ + hash::Hash, message::v0::LoadedAddresses, signature::Keypair, system_transaction, + }, solana_storage_proto::convert::generated, solana_transaction_status::{ ConfirmedBlock, TransactionStatusMeta, TransactionWithStatusMeta, + VersionedConfirmedBlock, }, std::convert::TryInto, }; @@ -815,6 +818,7 @@ mod tests { pre_token_balances: Some(vec![]), post_token_balances: Some(vec![]), rewards: Some(vec![]), + loaded_addresses: LoadedAddresses::default(), }), }; let block = ConfirmedBlock { @@ -845,8 +849,9 @@ mod tests { "".to_string(), ) .unwrap(); + let expected_block: VersionedConfirmedBlock = block.into(); if let CellData::Protobuf(protobuf_block) = deserialized { - assert_eq!(block, protobuf_block.try_into().unwrap()); + assert_eq!(expected_block, protobuf_block.try_into().unwrap()); } else { panic!("deserialization should produce CellData::Protobuf"); } @@ -861,7 +866,7 @@ mod tests { ) .unwrap(); if let CellData::Bincode(bincode_block) = deserialized { - let mut block = block; + let mut block = expected_block; if let Some(meta) = &mut block.transactions[0].meta { meta.inner_instructions = None; // Legacy bincode implementation does not support inner_instructions meta.log_messages = None; // Legacy bincode implementation does not support log_messages diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index 8a349b476b..237ca758bd 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -6,17 +6,18 @@ use { solana_sdk::{ clock::{Slot, UnixTimestamp}, deserialize_utils::default_on_eof, + message::v0::LoadedAddresses, pubkey::Pubkey, signature::Signature, sysvar::is_sysvar_id, - transaction::{Transaction, TransactionError}, + transaction::{TransactionError, VersionedTransaction}, }, solana_storage_proto::convert::{generated, tx_by_addr}, solana_transaction_status::{ - extract_and_fmt_memos, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, - ConfirmedTransactionWithStatusMeta, Reward, TransactionByAddrInfo, - TransactionConfirmationStatus, TransactionStatus, TransactionStatusMeta, - TransactionWithStatusMeta, + extract_and_fmt_memos, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, Reward, + TransactionByAddrInfo, TransactionConfirmationStatus, TransactionStatus, + TransactionStatusMeta, TransactionWithStatusMeta, VersionedConfirmedBlock, + VersionedConfirmedTransactionWithStatusMeta, VersionedTransactionWithStatusMeta, }, std::{ collections::{HashMap, HashSet}, @@ -138,7 +139,7 @@ impl From for StoredConfirmedBlock { } } -impl From for ConfirmedBlock { +impl From for VersionedConfirmedBlock { fn from(confirmed_block: StoredConfirmedBlock) -> Self { let StoredConfirmedBlock { previous_blockhash, @@ -164,20 +165,20 @@ impl From for ConfirmedBlock { #[derive(Serialize, Deserialize)] struct StoredConfirmedBlockTransaction { - transaction: Transaction, + transaction: VersionedTransaction, meta: Option, } impl From for StoredConfirmedBlockTransaction { fn from(value: TransactionWithStatusMeta) -> Self { Self { - transaction: value.transaction, + transaction: value.transaction.into(), meta: value.meta.map(|meta| meta.into()), } } } -impl From for TransactionWithStatusMeta { +impl From for VersionedTransactionWithStatusMeta { fn from(value: StoredConfirmedBlockTransaction) -> Self { Self { transaction: value.transaction, @@ -216,6 +217,7 @@ impl From for TransactionStatusMeta { pre_token_balances: None, post_token_balances: None, rewards: None, + loaded_addresses: LoadedAddresses::default(), } } } @@ -392,7 +394,7 @@ impl LedgerStorage { } /// Fetch the confirmed block from the desired slot - pub async fn get_confirmed_block(&self, slot: Slot) -> Result { + pub async fn get_confirmed_block(&self, slot: Slot) -> Result { debug!( "LedgerStorage::get_confirmed_block request received: {:?}", slot @@ -438,7 +440,7 @@ impl LedgerStorage { pub async fn get_confirmed_transaction( &self, signature: &Signature, - ) -> Result> { + ) -> Result> { debug!( "LedgerStorage::get_confirmed_transaction request received: {:?}", signature @@ -471,9 +473,9 @@ impl LedgerStorage { ); Ok(None) } else { - Ok(Some(ConfirmedTransactionWithStatusMeta { + Ok(Some(VersionedConfirmedTransactionWithStatusMeta { slot, - transaction: bucket_block_transaction, + tx_with_meta: bucket_block_transaction, block_time: block.block_time, })) } @@ -627,7 +629,7 @@ impl LedgerStorage { pub async fn upload_confirmed_block( &self, slot: Slot, - confirmed_block: ConfirmedBlock, + confirmed_block: VersionedConfirmedBlock, ) -> Result<()> { let mut bytes_written = 0; @@ -635,13 +637,13 @@ impl LedgerStorage { let mut tx_cells = vec![]; for (index, transaction_with_meta) in confirmed_block.transactions.iter().enumerate() { - let TransactionWithStatusMeta { meta, transaction } = transaction_with_meta; + let VersionedTransactionWithStatusMeta { meta, transaction } = transaction_with_meta; let err = meta.as_ref().and_then(|meta| meta.status.clone().err()); let index = index as u32; let signature = transaction.signatures[0]; - let memo = extract_and_fmt_memos(&transaction.message); + let memo = extract_and_fmt_memos(transaction_with_meta); - for address in &transaction.message.account_keys { + for address in transaction_with_meta.account_keys_iter() { if !is_sysvar_id(address) { by_addr .entry(address) @@ -723,12 +725,12 @@ impl LedgerStorage { let mut expected_tx_infos: HashMap = HashMap::new(); let confirmed_block = self.get_confirmed_block(slot).await?; for (index, transaction_with_meta) in confirmed_block.transactions.iter().enumerate() { - let TransactionWithStatusMeta { meta, transaction } = transaction_with_meta; + let VersionedTransactionWithStatusMeta { transaction, meta } = transaction_with_meta; let signature = transaction.signatures[0]; let index = index as u32; let err = meta.as_ref().and_then(|meta| meta.status.clone().err()); - for address in &transaction.message.account_keys { + for address in transaction_with_meta.account_keys_iter() { if !is_sysvar_id(address) { addresses.insert(address); } diff --git a/storage-proto/proto/confirmed_block.proto b/storage-proto/proto/confirmed_block.proto index 2399e78db8..4387fe99ae 100644 --- a/storage-proto/proto/confirmed_block.proto +++ b/storage-proto/proto/confirmed_block.proto @@ -27,6 +27,8 @@ message Message { repeated bytes account_keys = 2; bytes recent_blockhash = 3; repeated CompiledInstruction instructions = 4; + bool versioned = 5; + repeated MessageAddressTableLookup address_table_lookups = 6; } message MessageHeader { @@ -35,6 +37,12 @@ message MessageHeader { uint32 num_readonly_unsigned_accounts = 3; } +message MessageAddressTableLookup { + bytes account_key = 1; + bytes writable_indexes = 2; + bytes readonly_indexes = 3; +} + message TransactionStatusMeta { TransactionError err = 1; uint64 fee = 2; @@ -47,6 +55,8 @@ message TransactionStatusMeta { repeated TokenBalance pre_token_balances = 7; repeated TokenBalance post_token_balances = 8; repeated Reward rewards = 9; + repeated bytes loaded_writable_addresses = 12; + repeated bytes loaded_readonly_addresses = 13; } message TransactionError { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index e5b1807f1a..0e48ba611c 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -4,14 +4,19 @@ use { solana_sdk::{ hash::Hash, instruction::{CompiledInstruction, InstructionError}, - message::{Message, MessageHeader}, + message::{ + legacy::Message as LegacyMessage, + v0::{self, LoadedAddresses, MessageAddressTableLookup}, + MessageHeader, VersionedMessage, + }, pubkey::Pubkey, signature::Signature, - transaction::{Transaction, TransactionError}, + transaction::{Transaction, TransactionError, VersionedTransaction}, }, solana_transaction_status::{ ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo, TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta, + VersionedConfirmedBlock, VersionedTransactionWithStatusMeta, }, std::{ convert::{TryFrom, TryInto}, @@ -111,6 +116,30 @@ impl From for Reward { } } +impl From for generated::ConfirmedBlock { + fn from(confirmed_block: VersionedConfirmedBlock) -> Self { + let VersionedConfirmedBlock { + previous_blockhash, + blockhash, + parent_slot, + transactions, + rewards, + block_time, + block_height, + } = confirmed_block; + + Self { + previous_blockhash, + blockhash, + parent_slot, + transactions: transactions.into_iter().map(|tx| tx.into()).collect(), + rewards: rewards.into_iter().map(|r| r.into()).collect(), + block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }), + block_height: block_height.map(|block_height| generated::BlockHeight { block_height }), + } + } +} + impl From for generated::ConfirmedBlock { fn from(confirmed_block: ConfirmedBlock) -> Self { let ConfirmedBlock { @@ -135,7 +164,7 @@ impl From for generated::ConfirmedBlock { } } -impl TryFrom for ConfirmedBlock { +impl TryFrom for VersionedConfirmedBlock { type Error = bincode::Error; fn try_from( confirmed_block: generated::ConfirmedBlock, @@ -157,7 +186,7 @@ impl TryFrom for ConfirmedBlock { transactions: transactions .into_iter() .map(|tx| tx.try_into()) - .collect::, Self::Error>>()?, + .collect::, Self::Error>>()?, rewards: rewards.into_iter().map(|r| r.into()).collect(), block_time: block_time.map(|generated::UnixTimestamp { timestamp }| timestamp), block_height: block_height.map(|generated::BlockHeight { block_height }| block_height), @@ -175,7 +204,17 @@ impl From for generated::ConfirmedTransaction { } } -impl TryFrom for TransactionWithStatusMeta { +impl From for generated::ConfirmedTransaction { + fn from(value: VersionedTransactionWithStatusMeta) -> Self { + let meta = value.meta.map(|meta| meta.into()); + Self { + transaction: Some(value.transaction.into()), + meta, + } + } +} + +impl TryFrom for VersionedTransactionWithStatusMeta { type Error = bincode::Error; fn try_from(value: generated::ConfirmedTransaction) -> std::result::Result { let meta = value.meta.map(|meta| meta.try_into()).transpose()?; @@ -199,7 +238,20 @@ impl From for generated::Transaction { } } -impl From for Transaction { +impl From for generated::Transaction { + fn from(value: VersionedTransaction) -> Self { + Self { + signatures: value + .signatures + .into_iter() + .map(|signature| >::as_ref(&signature).into()) + .collect(), + message: Some(value.message.into()), + } + } +} + +impl From for VersionedTransaction { fn from(value: generated::Transaction) -> Self { Self { signatures: value @@ -212,32 +264,86 @@ impl From for Transaction { } } -impl From for generated::Message { - fn from(value: Message) -> Self { +impl From for generated::Message { + fn from(message: LegacyMessage) -> Self { Self { - header: Some(value.header.into()), - account_keys: value + header: Some(message.header.into()), + account_keys: message .account_keys - .into_iter() - .map(|key| >::as_ref(&key).into()) + .iter() + .map(|key| >::as_ref(key).into()) .collect(), - recent_blockhash: value.recent_blockhash.to_bytes().into(), - instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(), + recent_blockhash: message.recent_blockhash.to_bytes().into(), + instructions: message + .instructions + .into_iter() + .map(|ix| ix.into()) + .collect(), + versioned: false, + address_table_lookups: vec![], } } } -impl From for Message { +impl From for generated::Message { + fn from(message: VersionedMessage) -> Self { + match message { + VersionedMessage::Legacy(message) => Self::from(message), + VersionedMessage::V0(message) => Self { + header: Some(message.header.into()), + account_keys: message + .account_keys + .iter() + .map(|key| >::as_ref(key).into()) + .collect(), + recent_blockhash: message.recent_blockhash.to_bytes().into(), + instructions: message + .instructions + .into_iter() + .map(|ix| ix.into()) + .collect(), + versioned: true, + address_table_lookups: message + .address_table_lookups + .into_iter() + .map(|lookup| lookup.into()) + .collect(), + }, + } + } +} + +impl From for VersionedMessage { fn from(value: generated::Message) -> Self { - Self { - header: value.header.expect("header is required").into(), - account_keys: value - .account_keys - .into_iter() - .map(|key| Pubkey::new(&key)) - .collect(), - recent_blockhash: Hash::new(&value.recent_blockhash), - instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(), + let header = value.header.expect("header is required").into(); + let account_keys = value + .account_keys + .into_iter() + .map(|key| Pubkey::new(&key)) + .collect(); + let recent_blockhash = Hash::new(&value.recent_blockhash); + let instructions = value.instructions.into_iter().map(|ix| ix.into()).collect(); + let address_table_lookups = value + .address_table_lookups + .into_iter() + .map(|lookup| lookup.into()) + .collect(); + + if !value.versioned { + Self::Legacy(LegacyMessage { + header, + account_keys, + recent_blockhash, + instructions, + }) + } else { + Self::V0(v0::Message { + header, + account_keys, + recent_blockhash, + instructions, + address_table_lookups, + }) } } } @@ -274,6 +380,7 @@ impl From for generated::TransactionStatusMeta { pre_token_balances, post_token_balances, rewards, + loaded_addresses, } = value; let err = match status { Ok(()) => None, @@ -304,6 +411,16 @@ impl From for generated::TransactionStatusMeta { .into_iter() .map(|reward| reward.into()) .collect(); + let loaded_writable_addresses = loaded_addresses + .writable + .into_iter() + .map(|key| >::as_ref(&key).into()) + .collect(); + let loaded_readonly_addresses = loaded_addresses + .readonly + .into_iter() + .map(|key| >::as_ref(&key).into()) + .collect(); Self { err, @@ -317,6 +434,8 @@ impl From for generated::TransactionStatusMeta { pre_token_balances, post_token_balances, rewards, + loaded_writable_addresses, + loaded_readonly_addresses, } } } @@ -344,6 +463,8 @@ impl TryFrom for TransactionStatusMeta { pre_token_balances, post_token_balances, rewards, + loaded_writable_addresses, + loaded_readonly_addresses, } = value; let status = match &err { None => Ok(()), @@ -377,6 +498,16 @@ impl TryFrom for TransactionStatusMeta { .collect(), ); let rewards = Some(rewards.into_iter().map(|reward| reward.into()).collect()); + let loaded_addresses = LoadedAddresses { + writable: loaded_writable_addresses + .into_iter() + .map(|key| Pubkey::new(&key)) + .collect(), + readonly: loaded_readonly_addresses + .into_iter() + .map(|key| Pubkey::new(&key)) + .collect(), + }; Ok(Self { status, fee, @@ -387,6 +518,7 @@ impl TryFrom for TransactionStatusMeta { pre_token_balances, post_token_balances, rewards, + loaded_addresses, }) } } @@ -453,6 +585,26 @@ impl From for TransactionTokenBalance { } } +impl From for generated::MessageAddressTableLookup { + fn from(lookup: MessageAddressTableLookup) -> Self { + Self { + account_key: >::as_ref(&lookup.account_key).into(), + writable_indexes: lookup.writable_indexes, + readonly_indexes: lookup.readonly_indexes, + } + } +} + +impl From for MessageAddressTableLookup { + fn from(value: generated::MessageAddressTableLookup) -> Self { + Self { + account_key: Pubkey::new(&value.account_key), + writable_indexes: value.writable_indexes, + readonly_indexes: value.readonly_indexes, + } + } +} + impl From for generated::CompiledInstruction { fn from(value: CompiledInstruction) -> Self { Self { diff --git a/storage-proto/src/lib.rs b/storage-proto/src/lib.rs index 529397b98b..ac85e3672e 100644 --- a/storage-proto/src/lib.rs +++ b/storage-proto/src/lib.rs @@ -4,7 +4,9 @@ use { parse_token::{real_number_string_trimmed, UiTokenAmount}, StringAmount, }, - solana_sdk::{deserialize_utils::default_on_eof, transaction::Result}, + solana_sdk::{ + deserialize_utils::default_on_eof, message::v0::LoadedAddresses, transaction::Result, + }, solana_transaction_status::{ InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance, }, @@ -193,12 +195,14 @@ impl From for TransactionStatusMeta { .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()), rewards: rewards .map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()), + loaded_addresses: LoadedAddresses::default(), } } } -impl From for StoredTransactionStatusMeta { - fn from(value: TransactionStatusMeta) -> Self { +impl TryFrom for StoredTransactionStatusMeta { + type Error = bincode::Error; + fn try_from(value: TransactionStatusMeta) -> std::result::Result { let TransactionStatusMeta { status, fee, @@ -209,8 +213,18 @@ impl From for StoredTransactionStatusMeta { pre_token_balances, post_token_balances, rewards, + loaded_addresses, } = value; - Self { + + if !loaded_addresses.is_empty() { + // Deprecated bincode serialized status metadata doesn't support + // loaded addresses. + return Err( + bincode::ErrorKind::Custom("Bincode serialization is deprecated".into()).into(), + ); + } + + Ok(Self { status, fee, pre_balances, @@ -223,6 +237,6 @@ impl From for StoredTransactionStatusMeta { .map(|balances| balances.into_iter().map(|balance| balance.into()).collect()), rewards: rewards .map(|rewards| rewards.into_iter().map(|reward| reward.into()).collect()), - } + }) } } diff --git a/transaction-status/src/extract_memos.rs b/transaction-status/src/extract_memos.rs index b83547b8bf..1eccdc5b3b 100644 --- a/transaction-status/src/extract_memos.rs +++ b/transaction-status/src/extract_memos.rs @@ -1,5 +1,5 @@ use { - crate::parse_instruction::parse_memo_data, + crate::{parse_instruction::parse_memo_data, VersionedTransactionWithStatusMeta}, solana_sdk::{ instruction::CompiledInstruction, message::{Message, SanitizedMessage}, @@ -55,6 +55,15 @@ impl ExtractMemos for SanitizedMessage { } } +impl ExtractMemos for VersionedTransactionWithStatusMeta { + fn extract_memos(&self) -> Vec { + extract_memos_inner( + self.account_keys_iter(), + self.transaction.message.instructions(), + ) + } +} + enum KeyType<'a> { MemoProgram, OtherProgram, diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 55ea242b13..c69ffd66ff 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -26,11 +26,11 @@ use { clock::{Slot, UnixTimestamp}, commitment_config::CommitmentConfig, instruction::CompiledInstruction, - message::{Message, MessageHeader}, + message::{v0::LoadedAddresses, Message, MessageHeader}, pubkey::Pubkey, sanitize::Sanitize, signature::Signature, - transaction::{Result, Transaction, TransactionError}, + transaction::{Result, Transaction, TransactionError, VersionedTransaction}, }, std::fmt, }; @@ -82,13 +82,13 @@ pub enum UiInstruction { } impl UiInstruction { - fn parse(instruction: &CompiledInstruction, message: &Message) -> Self { - let program_id = instruction.program_id(&message.account_keys); - if let Ok(parsed_instruction) = parse(program_id, instruction, &message.account_keys) { + fn parse(instruction: &CompiledInstruction, account_keys: &[Pubkey]) -> Self { + let program_id = instruction.program_id(account_keys); + if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys) { UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction)) } else { UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded( - UiPartiallyDecodedInstruction::from(instruction, &message.account_keys), + UiPartiallyDecodedInstruction::from(instruction, account_keys), )) } } @@ -167,7 +167,7 @@ impl UiInnerInstructions { instructions: inner_instructions .instructions .iter() - .map(|ix| UiInstruction::parse(ix, message)) + .map(|ix| UiInstruction::parse(ix, &message.account_keys)) .collect(), } } @@ -230,6 +230,7 @@ pub struct TransactionStatusMeta { pub pre_token_balances: Option>, pub post_token_balances: Option>, pub rewards: Option, + pub loaded_addresses: LoadedAddresses, } impl Default for TransactionStatusMeta { @@ -244,6 +245,7 @@ impl Default for TransactionStatusMeta { pre_token_balances: None, post_token_balances: None, rewards: None, + loaded_addresses: LoadedAddresses::default(), } } } @@ -397,6 +399,51 @@ pub struct ConfirmedBlock { pub block_height: Option, } +#[derive(Clone, Debug, Default, PartialEq)] +pub struct VersionedConfirmedBlock { + pub previous_blockhash: String, + pub blockhash: String, + pub parent_slot: Slot, + pub transactions: Vec, + pub rewards: Rewards, + pub block_time: Option, + pub block_height: Option, +} + +impl VersionedConfirmedBlock { + /// Downgrades a versioned block into a legacy block type + /// if it only contains legacy transactions + pub fn into_legacy_block(self) -> Option { + Some(ConfirmedBlock { + previous_blockhash: self.previous_blockhash, + blockhash: self.blockhash, + parent_slot: self.parent_slot, + transactions: self + .transactions + .into_iter() + .map(|tx_with_meta| tx_with_meta.into_legacy_transaction_with_meta()) + .collect::>>()?, + rewards: self.rewards, + block_time: self.block_time, + block_height: self.block_height, + }) + } +} + +impl From for VersionedConfirmedBlock { + fn from(block: ConfirmedBlock) -> Self { + VersionedConfirmedBlock { + previous_blockhash: block.previous_blockhash, + blockhash: block.blockhash, + parent_slot: block.parent_slot, + transactions: block.transactions.into_iter().map(|tx| tx.into()).collect(), + rewards: block.rewards, + block_time: block.block_time, + block_height: block.block_height, + } + } +} + impl Encodable for ConfirmedBlock { type Encoded = EncodedConfirmedBlock; fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded { @@ -428,7 +475,7 @@ impl ConfirmedBlock { Some( self.transactions .into_iter() - .map(|tx| tx.encode(encoding)) + .map(|tx_with_meta| tx_with_meta.encode(encoding)) .collect(), ), None, @@ -518,6 +565,41 @@ impl From for UiConfirmedBlock { } } +#[derive(Clone, Debug, PartialEq)] +pub struct VersionedTransactionWithStatusMeta { + pub transaction: VersionedTransaction, + pub meta: Option, +} + +impl VersionedTransactionWithStatusMeta { + pub fn account_keys_iter(&self) -> impl Iterator { + let static_keys_iter = self.transaction.message.static_account_keys().iter(); + let dynamic_keys_iter = self + .meta + .iter() + .map(|meta| meta.loaded_addresses.ordered_iter()) + .flatten(); + + static_keys_iter.chain(dynamic_keys_iter) + } + + pub fn into_legacy_transaction_with_meta(self) -> Option { + Some(TransactionWithStatusMeta { + transaction: self.transaction.into_legacy_transaction()?, + meta: self.meta, + }) + } +} + +impl From for VersionedTransactionWithStatusMeta { + fn from(tx_with_meta: TransactionWithStatusMeta) -> Self { + Self { + transaction: tx_with_meta.transaction.into(), + meta: tx_with_meta.meta, + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct TransactionWithStatusMeta { pub transaction: Transaction, @@ -531,7 +613,7 @@ impl Encodable for TransactionWithStatusMeta { transaction: self.transaction.encode(encoding), meta: self.meta.map(|meta| match encoding { UiTransactionEncoding::JsonParsed => { - UiTransactionStatusMeta::parse(meta, self.transaction.message()) + UiTransactionStatusMeta::parse(meta, &self.transaction.message) } _ => UiTransactionStatusMeta::from(meta), }), @@ -545,6 +627,7 @@ pub struct EncodedTransactionWithStatusMeta { pub transaction: EncodedTransaction, pub meta: Option, } + #[derive(Debug, Clone, PartialEq)] pub struct ConfirmedTransactionWithStatusMeta { pub slot: Slot, @@ -563,6 +646,28 @@ impl Encodable for ConfirmedTransactionWithStatusMeta { } } +#[derive(Debug, Clone, PartialEq)] +pub struct VersionedConfirmedTransactionWithStatusMeta { + pub slot: Slot, + pub tx_with_meta: VersionedTransactionWithStatusMeta, + pub block_time: Option, +} + +impl VersionedConfirmedTransactionWithStatusMeta { + /// Downgrades a versioned confirmed transaction into a legacy + /// confirmed transaction if it contains a legacy transaction. + pub fn into_legacy_confirmed_transaction(self) -> Option { + Some(ConfirmedTransactionWithStatusMeta { + transaction: TransactionWithStatusMeta { + transaction: self.tx_with_meta.transaction.into_legacy_transaction()?, + meta: self.tx_with_meta.meta, + }, + block_time: self.block_time, + slot: self.slot, + }) + } +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EncodedConfirmedTransactionWithStatusMeta { @@ -655,7 +760,7 @@ impl Encodable for &Message { instructions: self .instructions .iter() - .map(|instruction| UiInstruction::parse(instruction, self)) + .map(|instruction| UiInstruction::parse(instruction, &self.account_keys)) .collect(), }) } else {