From cf6b5dcc18ffc8d7cad08e6bf9c24c507a714f73 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 1 Feb 2022 22:57:12 +0800 Subject: [PATCH] wip: pushing to save progress --- cli/src/cluster_query.rs | 1 + cli/src/wallet.rs | 1 + client-test/tests/client.rs | 12 +- client/src/mock_sender.rs | 6 +- client/src/rpc_config.rs | 3 + client/src/rpc_custom_error.rs | 10 +- client/src/rpc_deprecated_config.rs | 2 + client/src/rpc_response.rs | 4 +- ledger-tool/src/bigtable.rs | 17 +- rpc/src/rpc.rs | 69 ++++-- rpc/src/rpc_pubsub.rs | 1 + rpc/src/rpc_subscription_tracker.rs | 1 + rpc/src/rpc_subscriptions.rs | 48 +++- sdk/program/src/message/versions/mod.rs | 22 ++ sdk/program/src/message/versions/v0/mod.rs | 23 +- sdk/src/transaction/versioned.rs | 15 ++ transaction-status/src/lib.rs | 269 +++++++++++++++++---- transaction-status/src/parse_accounts.rs | 14 +- 18 files changed, 420 insertions(+), 98 deletions(-) diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 3d828046c689ce..e661c22879289b 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -2004,6 +2004,7 @@ pub fn process_transaction_history( RpcTransactionConfig { encoding: Some(UiTransactionEncoding::Base64), commitment: Some(CommitmentConfig::confirmed()), + enable_versioned_transactions: Some(false), }, ) { Ok(confirmed_transaction) => { diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index fbab92c5dbff0b..68fe25a6b827af 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -556,6 +556,7 @@ pub fn process_confirm( RpcTransactionConfig { encoding: Some(UiTransactionEncoding::Base64), commitment: Some(CommitmentConfig::confirmed()), + enable_versioned_transactions: Some(false), }, ) { Ok(confirmed_transaction) => { diff --git a/client-test/tests/client.rs b/client-test/tests/client.rs index 15eb331df59135..50e9e6583b9baa 100644 --- a/client-test/tests/client.rs +++ b/client-test/tests/client.rs @@ -36,7 +36,9 @@ use { }, solana_streamer::socket::SocketAddrSpace, solana_test_validator::TestValidator, - solana_transaction_status::{ConfirmedBlock, TransactionDetails, UiTransactionEncoding}, + solana_transaction_status::{ + BlockEncodingOptions, ConfirmedBlock, TransactionDetails, UiTransactionEncoding, + }, std::{ collections::HashSet, net::{IpAddr, SocketAddr}, @@ -267,6 +269,7 @@ fn test_block_subscription() { encoding: Some(UiTransactionEncoding::Json), transaction_details: Some(TransactionDetails::Signatures), show_rewards: None, + enable_versioned_transactions: None, }), ) .unwrap(); @@ -282,8 +285,11 @@ fn test_block_subscription() { let block = confirmed_block .encode_with_options( UiTransactionEncoding::Json, - TransactionDetails::Signatures, - false, + BlockEncodingOptions { + transaction_details: TransactionDetails::Signatures, + show_rewards: false, + versioned_transactions_disabled: true, + }, ) .unwrap(); assert_eq!(actual.value.slot, slot); diff --git a/client/src/mock_sender.rs b/client/src/mock_sender.rs index aa301bc4265170..a4cde6e1e83e6a 100644 --- a/client/src/mock_sender.rs +++ b/client/src/mock_sender.rs @@ -28,7 +28,7 @@ use { pubkey::Pubkey, signature::Signature, sysvar::epoch_schedule::EpochSchedule, - transaction::{self, Transaction, TransactionError}, + transaction::{self, Transaction, TransactionError, TransactionVersion}, }, solana_transaction_status::{ EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, @@ -192,6 +192,7 @@ impl RpcSender for MockSender { "getTransaction" => serde_json::to_value(EncodedConfirmedTransactionWithStatusMeta { slot: 2, transaction: EncodedTransactionWithStatusMeta { + version: Some(TransactionVersion::Legacy), transaction: EncodedTransaction::Json( UiTransaction { signatures: vec!["3AsdoALgZFuq2oUVWrDYhg2pNeaLJKPLf8hU2mQ6U8qJxeJ6hsrPVpMn9ma39DtfYCrDQSvngWRP8NnTpEhezJpE".to_string()], @@ -213,6 +214,7 @@ impl RpcSender for MockSender { accounts: vec![0, 1], data: "3Bxs49DitAvXtoDR".to_string(), }], + address_table_lookups: None, }) }), meta: Some(UiTransactionStatusMeta { @@ -226,6 +228,7 @@ impl RpcSender for MockSender { pre_token_balances: None, post_token_balances: None, rewards: None, + loaded_addresses: None, }), }, block_time: Some(1628633791), @@ -381,6 +384,7 @@ impl RpcSender for MockSender { UiTransactionEncoding::Base58, ), meta: None, + version: Some(TransactionVersion::Legacy), }], rewards: Rewards::new(), block_time: None, diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index edf3dc819877d8..e9951c4b4037b1 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -197,6 +197,7 @@ pub struct RpcBlockSubscribeConfig { pub encoding: Option, pub transaction_details: Option, pub show_rewards: Option, + pub enable_versioned_transactions: Option, } #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] @@ -248,6 +249,7 @@ pub struct RpcBlockConfig { pub rewards: Option, #[serde(flatten)] pub commitment: Option, + pub enable_versioned_transactions: Option, } impl EncodingConfig for RpcBlockConfig { @@ -288,6 +290,7 @@ pub struct RpcTransactionConfig { pub encoding: Option, #[serde(flatten)] pub commitment: Option, + pub enable_versioned_transactions: Option, } impl EncodingConfig for RpcTransactionConfig { diff --git a/client/src/rpc_custom_error.rs b/client/src/rpc_custom_error.rs index d443a294236ffb..ae8fc545c55069 100644 --- a/client/src/rpc_custom_error.rs +++ b/client/src/rpc_custom_error.rs @@ -60,7 +60,7 @@ pub enum RpcCustomError { #[error("BlockStatusNotAvailableYet")] BlockStatusNotAvailableYet { slot: Slot }, #[error("UnsupportedTransactionVersion")] - UnsupportedTransactionVersion, + UnsupportedTransactionVersion(u8), } #[derive(Debug, Serialize, Deserialize)] @@ -72,7 +72,9 @@ pub struct NodeUnhealthyErrorData { impl From for RpcCustomError { fn from(err: EncodeError) -> Self { match err { - EncodeError::UnsupportedTransactionVersion => Self::UnsupportedTransactionVersion, + EncodeError::UnsupportedTransactionVersion(version) => { + Self::UnsupportedTransactionVersion(version) + } } } } @@ -181,9 +183,9 @@ impl From for Error { message: format!("Block status not yet available for slot {}", slot), data: None, }, - RpcCustomError::UnsupportedTransactionVersion => Self { + RpcCustomError::UnsupportedTransactionVersion(version) => Self { code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION), - message: "Versioned transactions are not supported".to_string(), + message: format!("Transaction version ({}) is not supported", version), data: None, }, } diff --git a/client/src/rpc_deprecated_config.rs b/client/src/rpc_deprecated_config.rs index 9062513ba55d79..86b2922f29ac74 100644 --- a/client/src/rpc_deprecated_config.rs +++ b/client/src/rpc_deprecated_config.rs @@ -71,6 +71,7 @@ impl From for RpcBlockConfig { transaction_details: config.transaction_details, rewards: config.rewards, commitment: config.commitment, + enable_versioned_transactions: Some(false), } } } @@ -98,6 +99,7 @@ impl From for RpcTransactionConfig { Self { encoding: config.encoding, commitment: config.commitment, + enable_versioned_transactions: Some(false), } } } diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index a625072ebb06cd..169b52e4e2f5ea 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -432,8 +432,8 @@ pub enum RpcBlockUpdateError { #[error("block store error")] BlockStoreError, - #[error("unsupported transaction version")] - UnsupportedTransactionVersion, + #[error("unsupported transaction version ({0})")] + UnsupportedTransactionVersion(u8), } #[derive(Serialize, Deserialize, Debug)] diff --git a/ledger-tool/src/bigtable.rs b/ledger-tool/src/bigtable.rs index 8b8a0ca0f2d95d..771ba5bf0d642d 100644 --- a/ledger-tool/src/bigtable.rs +++ b/ledger-tool/src/bigtable.rs @@ -17,8 +17,8 @@ use { solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType}, solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature}, solana_transaction_status::{ - ConfirmedBlock, Encodable, EncodeError, LegacyTransactionWithStatusMeta, - TransactionDetails, UiTransactionEncoding, + BlockEncodingOptions, ConfirmedBlock, Encodable, EncodeError, + LegacyTransactionWithStatusMeta, UiTransactionEncoding, }, std::{ collections::HashSet, @@ -78,12 +78,17 @@ async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box { - "Failed to process unsupported transaction version in block".to_string() + EncodeError::UnsupportedTransactionVersion(version) => { + format!( + "Failed to process unsupported transaction version ({}) in block", + version + ) } })?; diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 34c61fc629a023..54842ff4c871e5 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -69,8 +69,7 @@ use { system_instruction, sysvar::stake_history, transaction::{ - self, DisabledAddressLoader, SanitizedTransaction, TransactionError, - VersionedTransaction, + self, AddressLoader, SanitizedTransaction, TransactionError, VersionedTransaction, }, }, solana_send_transaction_service::{ @@ -80,7 +79,7 @@ use { solana_storage_bigtable::Error as StorageError, solana_streamer::socket::SocketAddrSpace, solana_transaction_status::{ - ConfirmedBlock, ConfirmedTransactionStatusWithSignature, + BlockEncodingOptions, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, ConfirmedTransactionWithStatusMeta, EncodedConfirmedTransactionWithStatusMeta, Reward, RewardType, TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock, UiTransactionEncoding, @@ -983,10 +982,15 @@ impl JsonRpcRequestProcessor { let config = config .map(|config| config.convert_to_current()) .unwrap_or_default(); - let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json); - let transaction_details = config.transaction_details.unwrap_or_default(); - let show_rewards = config.rewards.unwrap_or(true); let commitment = config.commitment.unwrap_or_default(); + let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json); + let encoding_options = BlockEncodingOptions { + transaction_details: config.transaction_details.unwrap_or_default(), + show_rewards: config.rewards.unwrap_or(true), + versioned_transactions_disabled: !config + .enable_versioned_transactions + .unwrap_or_default(), + }; check_is_at_least_confirmed(commitment)?; // Block is old enough to be finalized @@ -1007,7 +1011,7 @@ impl JsonRpcRequestProcessor { confirmed_block.block_height = Some(0); } Ok(confirmed_block - .encode_with_options(encoding, transaction_details, show_rewards) + .encode_with_options(encoding, encoding_options) .map_err(RpcCustomError::from)?) }; if result.is_err() { @@ -1051,7 +1055,7 @@ impl JsonRpcRequestProcessor { } Ok(confirmed_block - .encode_with_options(encoding, transaction_details, show_rewards) + .encode_with_options(encoding, encoding_options) .map_err(RpcCustomError::from)?) }) .transpose(); @@ -1373,8 +1377,10 @@ impl JsonRpcRequestProcessor { let config = config .map(|config| config.convert_to_current()) .unwrap_or_default(); - let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json); let commitment = config.commitment.unwrap_or_default(); + let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json); + let versioned_transactions_disabled = + !config.enable_versioned_transactions.unwrap_or_default(); check_is_at_least_confirmed(commitment)?; if self.config.enable_rpc_transaction_history { @@ -1389,7 +1395,7 @@ impl JsonRpcRequestProcessor { let encode_transaction = |confirmed_tx: ConfirmedTransactionWithStatusMeta| -> Result { - Ok(confirmed_tx.encode(encoding).map_err(RpcCustomError::from)?) + Ok(confirmed_tx.encode(encoding, versioned_transactions_disabled).map_err(RpcCustomError::from)?) }; match confirmed_transaction.unwrap_or(None) { @@ -3459,7 +3465,8 @@ pub mod rpc_full { .preflight_commitment .map(|commitment| CommitmentConfig { commitment }); let preflight_bank = &*meta.bank(preflight_commitment); - let transaction = sanitize_transaction(unsanitized_tx)?; + let finalized_bank = &*meta.bank(Some(CommitmentConfig::finalized())); + let transaction = sanitize_transaction(unsanitized_tx, finalized_bank)?; let signature = *transaction.signature(); let mut last_valid_block_height = preflight_bank @@ -3567,7 +3574,8 @@ pub mod rpc_full { .set_recent_blockhash(bank.last_blockhash()); } - let transaction = sanitize_transaction(unsanitized_tx)?; + let finalized_bank = &*meta.bank(Some(CommitmentConfig::finalized())); + let transaction = sanitize_transaction(unsanitized_tx, finalized_bank)?; if config.sig_verify { verify_transaction(&transaction, &bank.feature_set)?; } @@ -4252,9 +4260,12 @@ where .map(|output| (wire_output, output)) } -fn sanitize_transaction(transaction: VersionedTransaction) -> Result { +fn sanitize_transaction( + transaction: VersionedTransaction, + address_loader: &impl AddressLoader, +) -> Result { let message_hash = transaction.message.hash(); - SanitizedTransaction::try_create(transaction, message_hash, None, &DisabledAddressLoader) + SanitizedTransaction::try_create(transaction, message_hash, None, address_loader) .map_err(|err| Error::invalid_params(format!("invalid transaction: {}", err))) } @@ -4379,7 +4390,7 @@ pub mod tests { signature::{Keypair, Signer}, system_program, system_transaction, timing::slot_duration_from_slots_per_year, - transaction::{self, Transaction, TransactionError}, + transaction::{self, DisabledAddressLoader, Transaction, TransactionError}, }, solana_transaction_status::{ EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta, @@ -6579,9 +6590,16 @@ pub mod tests { assert_eq!(confirmed_block.transactions.len(), 2); assert_eq!(confirmed_block.rewards, vec![]); - for EncodedTransactionWithStatusMeta { transaction, meta } in - confirmed_block.transactions.into_iter() + for EncodedTransactionWithStatusMeta { + transaction, + meta, + version, + } in confirmed_block.transactions.into_iter() { + assert_eq!( + version, None, + "requests which don't enable versioned transactions should not receive a version" + ); if let EncodedTransaction::Json(transaction) = transaction { if transaction.signatures[0] == confirmed_block_signatures[0].to_string() { let meta = meta.unwrap(); @@ -6624,9 +6642,16 @@ pub mod tests { assert_eq!(confirmed_block.transactions.len(), 2); assert_eq!(confirmed_block.rewards, vec![]); - for EncodedTransactionWithStatusMeta { transaction, meta } in - confirmed_block.transactions.into_iter() + for EncodedTransactionWithStatusMeta { + transaction, + meta, + version, + } in confirmed_block.transactions.into_iter() { + assert_eq!( + version, None, + "requests which don't enable versioned transactions should not receive a version" + ); if let EncodedTransaction::LegacyBinary(transaction) = transaction { let decoded_transaction: Transaction = deserialize(&bs58::decode(&transaction).into_vec().unwrap()).unwrap(); @@ -6686,6 +6711,7 @@ pub mod tests { transaction_details: Some(TransactionDetails::Signatures), rewards: Some(false), commitment: None, + enable_versioned_transactions: None, }) ); let res = io.handle_request_sync(&req, meta.clone()); @@ -6707,6 +6733,7 @@ pub mod tests { transaction_details: Some(TransactionDetails::None), rewards: Some(true), commitment: None, + enable_versioned_transactions: None, }) ); let res = io.handle_request_sync(&req, meta); @@ -8064,7 +8091,7 @@ pub mod tests { .to_string(), ); assert_eq!( - sanitize_transaction(unsanitary_versioned_tx).unwrap_err(), + sanitize_transaction(unsanitary_versioned_tx, &DisabledAddressLoader).unwrap_err(), expect58 ); } @@ -8084,7 +8111,7 @@ pub mod tests { }; assert_eq!( - sanitize_transaction(versioned_tx).unwrap_err(), + sanitize_transaction(versioned_tx, &DisabledAddressLoader).unwrap_err(), Error::invalid_params( "invalid transaction: Transaction version is unsupported".to_string(), ) diff --git a/rpc/src/rpc_pubsub.rs b/rpc/src/rpc_pubsub.rs index e0144017acffa2..545ec403b91952 100644 --- a/rpc/src/rpc_pubsub.rs +++ b/rpc/src/rpc_pubsub.rs @@ -538,6 +538,7 @@ impl RpcSolPubSubInternal for RpcSolPubSubImpl { }, transaction_details: config.transaction_details.unwrap_or_default(), show_rewards: config.show_rewards.unwrap_or_default(), + enable_versioned_transactions: config.enable_versioned_transactions.unwrap_or_default(), }; self.subscribe(SubscriptionParams::Block(params)) } diff --git a/rpc/src/rpc_subscription_tracker.rs b/rpc/src/rpc_subscription_tracker.rs index c49ff6fd942a24..6e63a229f9d560 100644 --- a/rpc/src/rpc_subscription_tracker.rs +++ b/rpc/src/rpc_subscription_tracker.rs @@ -140,6 +140,7 @@ pub struct BlockSubscriptionParams { pub kind: BlockSubscriptionKind, pub transaction_details: TransactionDetails, pub show_rewards: bool, + pub enable_versioned_transactions: bool, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index cb378db1c40783..3ceedfb30abcb9 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -40,7 +40,9 @@ use { timing::timestamp, transaction, }, - solana_transaction_status::{ConfirmedBlock, EncodeError, VersionedConfirmedBlock}, + solana_transaction_status::{ + BlockEncodingOptions, ConfirmedBlock, EncodeError, VersionedConfirmedBlock, + }, std::{ cell::RefCell, collections::{HashMap, VecDeque}, @@ -300,12 +302,15 @@ fn filter_block_result_txs( let block = ConfirmedBlock::from(block) .encode_with_options( params.encoding, - params.transaction_details, - params.show_rewards, + BlockEncodingOptions { + transaction_details: params.transaction_details, + show_rewards: params.show_rewards, + versioned_transactions_disabled: !params.enable_versioned_transactions, + }, ) .map_err(|err| match err { - EncodeError::UnsupportedTransactionVersion => { - RpcBlockUpdateError::UnsupportedTransactionVersion + EncodeError::UnsupportedTransactionVersion(version) => { + RpcBlockUpdateError::UnsupportedTransactionVersion(version) } })?; @@ -1380,6 +1385,7 @@ pub(crate) mod tests { encoding: Some(UiTransactionEncoding::Json), transaction_details: Some(TransactionDetails::Signatures), show_rewards: None, + enable_versioned_transactions: None, }; let params = BlockSubscriptionParams { kind: BlockSubscriptionKind::All, @@ -1387,6 +1393,7 @@ pub(crate) mod tests { encoding: config.encoding.unwrap(), transaction_details: config.transaction_details.unwrap(), show_rewards: config.show_rewards.unwrap_or_default(), + enable_versioned_transactions: config.enable_versioned_transactions.unwrap_or_default(), }; let sub_id = rpc.block_subscribe(filter, Some(config)).unwrap(); @@ -1417,7 +1424,14 @@ pub(crate) mod tests { let confirmed_block = ConfirmedBlock::from(blockstore.get_complete_block(slot, false).unwrap()); let block = confirmed_block - .encode_with_options(params.encoding, params.transaction_details, false) + .encode_with_options( + params.encoding, + BlockEncodingOptions { + transaction_details: params.transaction_details, + show_rewards: false, + versioned_transactions_disabled: true, + }, + ) .unwrap(); let expected_resp = RpcBlockUpdate { slot, @@ -1488,6 +1502,7 @@ pub(crate) mod tests { encoding: Some(UiTransactionEncoding::Json), transaction_details: Some(TransactionDetails::Signatures), show_rewards: None, + enable_versioned_transactions: None, }; let params = BlockSubscriptionParams { kind: BlockSubscriptionKind::MentionsAccountOrProgram(keypair1.pubkey()), @@ -1495,6 +1510,7 @@ pub(crate) mod tests { encoding: config.encoding.unwrap(), transaction_details: config.transaction_details.unwrap(), show_rewards: config.show_rewards.unwrap_or_default(), + enable_versioned_transactions: config.enable_versioned_transactions.unwrap_or_default(), }; let sub_id = rpc.block_subscribe(filter, Some(config)).unwrap(); @@ -1530,7 +1546,14 @@ pub(crate) mod tests { .any(|key| key == &keypair1.pubkey()) }); let block = confirmed_block - .encode_with_options(params.encoding, params.transaction_details, false) + .encode_with_options( + params.encoding, + BlockEncodingOptions { + transaction_details: params.transaction_details, + show_rewards: false, + versioned_transactions_disabled: true, + }, + ) .unwrap(); let expected_resp = RpcBlockUpdate { slot, @@ -1589,6 +1612,7 @@ pub(crate) mod tests { encoding: Some(UiTransactionEncoding::Json), transaction_details: Some(TransactionDetails::Signatures), show_rewards: None, + enable_versioned_transactions: None, }; let params = BlockSubscriptionParams { kind: BlockSubscriptionKind::All, @@ -1596,6 +1620,7 @@ pub(crate) mod tests { encoding: config.encoding.unwrap(), transaction_details: config.transaction_details.unwrap(), show_rewards: config.show_rewards.unwrap_or_default(), + enable_versioned_transactions: config.enable_versioned_transactions.unwrap_or_default(), }; let sub_id = rpc.block_subscribe(filter, Some(config)).unwrap(); subscriptions @@ -1630,7 +1655,14 @@ pub(crate) mod tests { let confirmed_block = ConfirmedBlock::from(blockstore.get_complete_block(slot, false).unwrap()); let block = confirmed_block - .encode_with_options(params.encoding, params.transaction_details, false) + .encode_with_options( + params.encoding, + BlockEncodingOptions { + transaction_details: params.transaction_details, + show_rewards: false, + versioned_transactions_disabled: true, + }, + ) .unwrap(); let expected_resp = RpcBlockUpdate { slot, diff --git a/sdk/program/src/message/versions/mod.rs b/sdk/program/src/message/versions/mod.rs index 1b1d21047d1494..fd5d21938e9273 100644 --- a/sdk/program/src/message/versions/mod.rs +++ b/sdk/program/src/message/versions/mod.rs @@ -1,4 +1,5 @@ use { + self::v0::MessageAddressTableLookup, crate::{ hash::Hash, instruction::CompiledInstruction, @@ -110,6 +111,27 @@ impl VersionedMessage { } } + pub fn address_table_lookups(&self) -> Option<&Vec> { + match self { + Self::Legacy(_) => None, + Self::V0(message) => Some(&message.address_table_lookups), + } + } + + pub fn is_writable_for_parser(&self, index: usize) -> bool { + match self { + Self::Legacy(message) => message.is_writable(index), + Self::V0(message) => message.is_writable_for_parser(index), + } + } + + pub fn is_signer(&self, index: usize) -> bool { + match self { + Self::Legacy(message) => message.is_signer(index), + Self::V0(message) => message.is_signer(index), + } + } + pub fn serialize(&self) -> Vec { bincode::serialize(self).unwrap() } diff --git a/sdk/program/src/message/versions/v0/mod.rs b/sdk/program/src/message/versions/v0/mod.rs index 1e98d5c3d60a8c..fa4aba7885813d 100644 --- a/sdk/program/src/message/versions/v0/mod.rs +++ b/sdk/program/src/message/versions/v0/mod.rs @@ -1,10 +1,10 @@ use crate::{ hash::Hash, instruction::CompiledInstruction, - message::{MessageHeader, MESSAGE_VERSION_PREFIX}, + message::{legacy::BUILTIN_PROGRAMS_KEYS, MessageHeader, MESSAGE_VERSION_PREFIX}, pubkey::Pubkey, sanitize::{Sanitize, SanitizeError}, - short_vec, + short_vec, sysvar, }; mod loaded; @@ -117,6 +117,25 @@ impl Sanitize for Message { } impl Message { + // Note: this does not demote program ids because it can't check the dynamically + // looked up addresses to see if the upgradeable loader is included. So this + // method should only be used for RPC and not used in the runtime. + pub fn is_writable_for_parser(&self, i: usize) -> bool { + (i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts) + as usize + || (i >= self.header.num_required_signatures as usize + && i < self.account_keys.len() + - self.header.num_readonly_unsigned_accounts as usize)) + && !{ + let key = self.account_keys[i]; + sysvar::is_sysvar_id(&key) || BUILTIN_PROGRAMS_KEYS.contains(&key) + } + } + + pub fn is_signer(&self, i: usize) -> bool { + i < self.header.num_required_signatures as usize + } + /// Serialize this message with a version #0 prefix using bincode encoding. pub fn serialize(&self) -> Vec { bincode::serialize(&(MESSAGE_VERSION_PREFIX, self)).unwrap() diff --git a/sdk/src/transaction/versioned.rs b/sdk/src/transaction/versioned.rs index 222260d822dcbb..0afdfa12862c50 100644 --- a/sdk/src/transaction/versioned.rs +++ b/sdk/src/transaction/versioned.rs @@ -17,6 +17,13 @@ use { std::cmp::Ordering, }; +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum TransactionVersion { + Legacy, + Number(u8), +} + // 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)] @@ -93,6 +100,14 @@ impl VersionedTransaction { }) } + /// Returns the version of the transaction + pub fn version(&self) -> TransactionVersion { + match self.message { + VersionedMessage::Legacy(_) => TransactionVersion::Legacy, + VersionedMessage::V0(_) => TransactionVersion::Number(0), + } + } + /// Returns a legacy transaction if the transaction message is legacy. pub fn into_legacy_transaction(self) -> Option { match self.message { diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 626a35c1e93fa1..a7e6dae2f1da72 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -18,7 +18,7 @@ pub mod token_balances; pub use {crate::extract_memos::extract_and_fmt_memos, solana_runtime::bank::RewardType}; use { crate::{ - parse_accounts::{parse_accounts, ParsedAccount}, + parse_accounts::{parse_accounts, parse_static_accounts, ParsedAccount}, parse_instruction::{parse, ParsedInstruction}, }, solana_account_decoder::parse_token::UiTokenAmount, @@ -26,22 +26,42 @@ use { clock::{Slot, UnixTimestamp}, commitment_config::CommitmentConfig, instruction::CompiledInstruction, - message::{v0::LoadedAddresses, Message, MessageHeader}, + message::{ + v0::{LoadedAddresses, MessageAddressTableLookup}, + Message, MessageHeader, VersionedMessage, + }, pubkey::Pubkey, sanitize::Sanitize, signature::Signature, transaction::{ - Result as TransactionResult, Transaction, TransactionError, VersionedTransaction, + Result as TransactionResult, Transaction, TransactionError, TransactionVersion, + VersionedTransaction, }, }, - std::fmt, + std::{borrow::Cow, fmt}, thiserror::Error, }; +pub struct BlockEncodingOptions { + pub transaction_details: TransactionDetails, + pub show_rewards: bool, + pub versioned_transactions_disabled: bool, +} + +impl Default for BlockEncodingOptions { + fn default() -> Self { + Self { + transaction_details: TransactionDetails::Full, + show_rewards: true, + versioned_transactions_disabled: false, + } + } +} + #[derive(Error, Debug, PartialEq, Eq, Clone)] pub enum EncodeError { - #[error("Failed to encode unsupported transaction version")] - UnsupportedTransactionVersion, + #[error("Encoding does not support transaction version {0}")] + UnsupportedTransactionVersion(u8), } /// Represents types that can be encoded into one of several encoding formats @@ -50,6 +70,16 @@ pub trait Encodable { fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded; } +/// Represents types that can be encoded into one of several encoding formats +pub trait EncodableWithMeta { + type Encoded; + fn encode_with_meta( + &self, + encoding: UiTransactionEncoding, + meta: &TransactionStatusMeta, + ) -> Self::Encoded; +} + #[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] #[serde(rename_all = "camelCase")] pub enum UiTransactionEncoding { @@ -170,13 +200,13 @@ pub struct UiInnerInstructions { } impl UiInnerInstructions { - fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self { + fn parse(inner_instructions: InnerInstructions, account_keys: &[Pubkey]) -> Self { Self { index: inner_instructions.index, instructions: inner_instructions .instructions .iter() - .map(|ix| UiInstruction::parse(ix, &message.account_keys)) + .map(|ix| UiInstruction::parse(ix, account_keys)) .collect(), } } @@ -273,10 +303,36 @@ pub struct UiTransactionStatusMeta { pub pre_token_balances: Option>, pub post_token_balances: Option>, pub rewards: Option, + pub loaded_addresses: Option, +} + +/// A duplicate representation of LoadedAddresses +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiLoadedAddresses { + pub writable: Vec, + pub readonly: Vec, +} + +impl From<&LoadedAddresses> for UiLoadedAddresses { + fn from(loaded_addresses: &LoadedAddresses) -> Self { + Self { + writable: loaded_addresses + .writable + .iter() + .map(ToString::to_string) + .collect(), + readonly: loaded_addresses + .readonly + .iter() + .map(ToString::to_string) + .collect(), + } + } } impl UiTransactionStatusMeta { - fn parse(meta: TransactionStatusMeta, message: &Message) -> Self { + fn parse(meta: TransactionStatusMeta, account_keys: &[Pubkey]) -> Self { Self { err: meta.status.clone().err(), status: meta.status, @@ -285,7 +341,7 @@ impl UiTransactionStatusMeta { post_balances: meta.post_balances, inner_instructions: meta.inner_instructions.map(|ixs| { ixs.into_iter() - .map(|ix| UiInnerInstructions::parse(ix, message)) + .map(|ix| UiInnerInstructions::parse(ix, account_keys)) .collect() }), log_messages: meta.log_messages, @@ -296,6 +352,7 @@ impl UiTransactionStatusMeta { .post_token_balances .map(|balance| balance.into_iter().map(Into::into).collect()), rewards: meta.rewards, + loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)), } } } @@ -319,6 +376,7 @@ impl From for UiTransactionStatusMeta { .post_token_balances .map(|balance| balance.into_iter().map(Into::into).collect()), rewards: meta.rewards, + loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)), } } } @@ -443,15 +501,16 @@ impl ConfirmedBlock { pub fn encode_with_options( self, encoding: UiTransactionEncoding, - transaction_details: TransactionDetails, - show_rewards: bool, + options: BlockEncodingOptions, ) -> Result { - let (transactions, signatures) = match transaction_details { + let (transactions, signatures) = match options.transaction_details { TransactionDetails::Full => ( Some( self.transactions .into_iter() - .map(|tx_with_meta| tx_with_meta.encode(encoding)) + .map(|tx_with_meta| { + tx_with_meta.encode(encoding, options.versioned_transactions_disabled) + }) .collect::, _>>()?, ), None, @@ -473,7 +532,7 @@ impl ConfirmedBlock { parent_slot: self.parent_slot, transactions, signatures, - rewards: if show_rewards { + rewards: if options.show_rewards { Some(self.rewards) } else { None @@ -526,21 +585,6 @@ pub struct UiConfirmedBlock { pub block_height: Option, } -impl From for UiConfirmedBlock { - fn from(block: EncodedConfirmedBlock) -> Self { - Self { - previous_blockhash: block.previous_blockhash, - blockhash: block.blockhash, - parent_slot: block.parent_slot, - transactions: Some(block.transactions), - signatures: None, - rewards: Some(block.rewards), - block_time: block.block_time, - block_height: block.block_height, - } - } -} - #[derive(Clone, Debug, PartialEq)] #[allow(clippy::large_enum_variant)] pub enum TransactionWithStatusMeta { @@ -559,14 +603,18 @@ impl TransactionWithStatusMeta { pub fn encode( self, encoding: UiTransactionEncoding, + versioned_transactions_disabled: bool, ) -> Result { - match self { - Self::MissingMetadata(transaction) => Ok(EncodedTransactionWithStatusMeta { + Ok(match self { + Self::MissingMetadata(ref transaction) => EncodedTransactionWithStatusMeta { + version: None, transaction: transaction.encode(encoding), meta: None, - }), - Self::Ok(tx_with_meta) => tx_with_meta.encode(encoding), - } + }, + Self::Ok(tx_with_meta) => { + tx_with_meta.encode(encoding, versioned_transactions_disabled)? + } + }) } pub fn into_legacy_transaction_with_meta(self) -> Option { @@ -610,33 +658,55 @@ impl VersionedTransactionWithStatusMeta { pub fn encode( self, encoding: UiTransactionEncoding, + versioned_transactions_disabled: bool, ) -> Result { - let LegacyTransactionWithStatusMeta { transaction, meta } = self - .into_legacy_transaction_with_meta() - .ok_or(EncodeError::UnsupportedTransactionVersion)?; + let version = if versioned_transactions_disabled { + match self.transaction.version() { + TransactionVersion::Legacy => { + // Set to none because old clients can't handle this field + None + } + TransactionVersion::Number(version) => { + return Err(EncodeError::UnsupportedTransactionVersion(version)); + } + } + } else { + Some(self.transaction.version()) + }; + Ok(EncodedTransactionWithStatusMeta { - transaction: transaction.encode(encoding), - meta: meta.map(|meta| match encoding { + transaction: self.transaction.encode_with_meta(encoding, &self.meta), + meta: Some(match encoding { UiTransactionEncoding::JsonParsed => { - UiTransactionStatusMeta::parse(meta, &transaction.message) + let full_account_keys = + Self::full_account_keys(&self.transaction.message, &self.meta); + UiTransactionStatusMeta::parse(self.meta, &full_account_keys) } - _ => UiTransactionStatusMeta::from(meta), + _ => UiTransactionStatusMeta::from(self.meta), }), + version, }) } + pub fn full_account_keys<'a>( + message: &'a VersionedMessage, + meta: &TransactionStatusMeta, + ) -> Cow<'a, Vec> { + match message { + VersionedMessage::Legacy(message) => Cow::Borrowed(&message.account_keys), + VersionedMessage::V0(message) => Cow::Owned({ + let static_keys_iter = message.account_keys.iter(); + let dynamic_keys_iter = meta.loaded_addresses.ordered_iter(); + static_keys_iter.chain(dynamic_keys_iter).copied().collect() + }), + } + } + 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.loaded_addresses.ordered_iter(); static_keys_iter.chain(dynamic_keys_iter) } - - pub fn into_legacy_transaction_with_meta(self) -> Option { - Some(LegacyTransactionWithStatusMeta { - transaction: self.transaction.into_legacy_transaction()?, - meta: Some(self.meta), - }) - } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -644,6 +714,8 @@ impl VersionedTransactionWithStatusMeta { pub struct EncodedTransactionWithStatusMeta { pub transaction: EncodedTransaction, pub meta: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version: Option, } #[derive(Debug, PartialEq)] @@ -657,10 +729,13 @@ impl ConfirmedTransactionWithStatusMeta { pub fn encode( self, encoding: UiTransactionEncoding, + versioned_transactions_disabled: bool, ) -> Result { Ok(EncodedConfirmedTransactionWithStatusMeta { slot: self.slot, - transaction: self.tx_with_meta.encode(encoding)?, + transaction: self + .tx_with_meta + .encode(encoding, versioned_transactions_disabled)?, block_time: self.block_time, }) } @@ -683,6 +758,35 @@ pub enum EncodedTransaction { Json(UiTransaction), } +impl EncodableWithMeta for VersionedTransaction { + type Encoded = EncodedTransaction; + fn encode_with_meta( + &self, + encoding: UiTransactionEncoding, + meta: &TransactionStatusMeta, + ) -> Self::Encoded { + match encoding { + UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary( + bs58::encode(bincode::serialize(self).unwrap()).into_string(), + ), + UiTransactionEncoding::Base58 => EncodedTransaction::Binary( + bs58::encode(bincode::serialize(self).unwrap()).into_string(), + encoding, + ), + UiTransactionEncoding::Base64 => EncodedTransaction::Binary( + base64::encode(bincode::serialize(self).unwrap()), + encoding, + ), + UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => { + EncodedTransaction::Json(UiTransaction { + signatures: self.signatures.iter().map(ToString::to_string).collect(), + message: self.message.encode_with_meta(encoding, meta), + }) + } + } + } +} + impl Encodable for Transaction { type Encoded = EncodedTransaction; fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded { @@ -760,6 +864,7 @@ impl Encodable for Message { .iter() .map(|instruction| UiInstruction::parse(instruction, &self.account_keys)) .collect(), + address_table_lookups: None, }) } else { UiMessage::Raw(UiRawMessage { @@ -767,6 +872,49 @@ impl Encodable for Message { account_keys: self.account_keys.iter().map(ToString::to_string).collect(), recent_blockhash: self.recent_blockhash.to_string(), instructions: self.instructions.iter().map(Into::into).collect(), + address_table_lookups: None, + }) + } + } +} + +impl EncodableWithMeta for VersionedMessage { + type Encoded = UiMessage; + fn encode_with_meta( + &self, + encoding: UiTransactionEncoding, + meta: &TransactionStatusMeta, + ) -> Self::Encoded { + if encoding == UiTransactionEncoding::JsonParsed { + UiMessage::Parsed(UiParsedMessage { + account_keys: parse_static_accounts(self), + recent_blockhash: self.recent_blockhash().to_string(), + instructions: self + .instructions() + .iter() + .map(|instruction| { + UiInstruction::parse( + instruction, + &VersionedTransactionWithStatusMeta::full_account_keys(self, meta), + ) + }) + .collect(), + address_table_lookups: self + .address_table_lookups() + .map(|lookups| lookups.iter().map(|lookup| lookup.into()).collect()), + }) + } else { + UiMessage::Raw(UiRawMessage { + header: *self.header(), + account_keys: self + .static_account_keys_iter() + .map(ToString::to_string) + .collect(), + recent_blockhash: self.recent_blockhash().to_string(), + instructions: self.instructions().iter().map(Into::into).collect(), + address_table_lookups: self + .address_table_lookups() + .map(|lookups| lookups.iter().map(|lookup| lookup.into()).collect()), }) } } @@ -780,6 +928,26 @@ pub struct UiRawMessage { pub account_keys: Vec, pub recent_blockhash: String, pub instructions: Vec, + pub address_table_lookups: Option>, +} + +/// A duplicate representation of a MessageAddressTableLookup, in raw format, for pretty JSON serialization +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiAddressTableLookup { + pub account_key: String, + pub writable_indexes: Vec, + pub readonly_indexes: Vec, +} + +impl From<&MessageAddressTableLookup> for UiAddressTableLookup { + fn from(lookup: &MessageAddressTableLookup) -> Self { + Self { + account_key: lookup.account_key.to_string(), + writable_indexes: lookup.writable_indexes.clone(), + readonly_indexes: lookup.readonly_indexes.clone(), + } + } } /// A duplicate representation of a Message, in parsed format, for pretty JSON serialization @@ -789,6 +957,7 @@ pub struct UiParsedMessage { pub account_keys: Vec, pub recent_blockhash: String, pub instructions: Vec, + pub address_table_lookups: Option>, } // A serialized `Vec` is stored in the `tx-by-addr` table. The row keys are diff --git a/transaction-status/src/parse_accounts.rs b/transaction-status/src/parse_accounts.rs index 35d8d4c9e6ea7b..936808c875df68 100644 --- a/transaction-status/src/parse_accounts.rs +++ b/transaction-status/src/parse_accounts.rs @@ -1,4 +1,4 @@ -use solana_sdk::message::Message; +use solana_sdk::message::{Message, VersionedMessage}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] @@ -20,6 +20,18 @@ pub fn parse_accounts(message: &Message) -> Vec { accounts } +pub fn parse_static_accounts(message: &VersionedMessage) -> Vec { + let mut accounts: Vec = vec![]; + for (i, account_key) in message.static_account_keys_iter().enumerate() { + accounts.push(ParsedAccount { + pubkey: account_key.to_string(), + writable: message.is_writable_for_parser(i), + signer: message.is_signer(i), + }); + } + accounts +} + #[cfg(test)] mod test { use {super::*, solana_sdk::message::MessageHeader};