From 2a00382d71d3574ac88b7945b13d361de3a1a1fd Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Mon, 3 Jan 2022 23:45:18 +0800 Subject: [PATCH] Refactor: cleanup solana_transaction_status crate (#22230) --- cli/src/wallet.rs | 10 +- client/src/mock_sender.rs | 4 +- client/src/rpc_client.rs | 12 +- ledger-tool/src/bigtable.rs | 10 +- ledger/src/blockstore.rs | 19 +- programs/bpf/tests/programs.rs | 31 +-- rpc/src/rpc.rs | 16 +- sdk/program/src/message/mod.rs | 2 +- storage-bigtable/src/lib.rs | 8 +- transaction-status/src/lib.rs | 394 ++++++++++++++++----------------- 10 files changed, 248 insertions(+), 258 deletions(-) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 8f53a0cd75..fbab92c5db 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -39,7 +39,7 @@ use { system_program, transaction::Transaction, }, - solana_transaction_status::{EncodedTransaction, UiTransactionEncoding}, + solana_transaction_status::{Encodable, EncodedTransaction, UiTransactionEncoding}, std::{fmt::Write as FmtWrite, fs::File, io::Write, sync::Arc}, }; @@ -564,10 +564,8 @@ pub fn process_confirm( .transaction .decode() .expect("Successful decode"); - let json_transaction = EncodedTransaction::encode( - decoded_transaction.clone(), - UiTransactionEncoding::Json, - ); + let json_transaction = + decoded_transaction.encode(UiTransactionEncoding::Json); transaction = Some(CliTransaction { transaction: json_transaction, @@ -609,7 +607,7 @@ pub fn process_decode_transaction(config: &CliConfig, transaction: &Transaction) let sigverify_status = CliSignatureVerificationStatus::verify_transaction(transaction); let decode_transaction = CliTransaction { decoded_transaction: transaction.clone(), - transaction: EncodedTransaction::encode(transaction.clone(), UiTransactionEncoding::Json), + transaction: transaction.encode(UiTransactionEncoding::Json), meta: None, block_time: None, slot: None, diff --git a/client/src/mock_sender.rs b/client/src/mock_sender.rs index a7d8344fe6..1133e5ad4c 100644 --- a/client/src/mock_sender.rs +++ b/client/src/mock_sender.rs @@ -30,7 +30,7 @@ use { transaction::{self, Transaction, TransactionError}, }, solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransaction, EncodedTransaction, + EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, EncodedTransactionWithStatusMeta, Rewards, TransactionConfirmationStatus, TransactionStatus, UiCompiledInstruction, UiMessage, UiRawMessage, UiTransaction, UiTransactionEncoding, UiTransactionStatusMeta, @@ -185,7 +185,7 @@ impl RpcSender for MockSender { value: statuses, })? } - "getTransaction" => serde_json::to_value(EncodedConfirmedTransaction { + "getTransaction" => serde_json::to_value(EncodedConfirmedTransactionWithStatusMeta { slot: 2, transaction: EncodedTransactionWithStatusMeta { transaction: EncodedTransaction::Json( diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index e36169a85f..e7c1e82a13 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -43,8 +43,8 @@ use { transaction::{self, uses_durable_nonce, Transaction}, }, solana_transaction_status::{ - EncodedConfirmedBlock, EncodedConfirmedTransaction, TransactionStatus, UiConfirmedBlock, - UiTransactionEncoding, + EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, TransactionStatus, + UiConfirmedBlock, UiTransactionEncoding, }, solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY, std::{ @@ -2943,7 +2943,7 @@ impl RpcClient { &self, signature: &Signature, encoding: UiTransactionEncoding, - ) -> ClientResult { + ) -> ClientResult { self.send( self.maybe_map_request(RpcRequest::GetTransaction)?, json!([signature.to_string(), encoding]), @@ -3006,7 +3006,7 @@ impl RpcClient { &self, signature: &Signature, config: RpcTransactionConfig, - ) -> ClientResult { + ) -> ClientResult { self.send( self.maybe_map_request(RpcRequest::GetTransaction)?, json!([signature.to_string(), config]), @@ -3022,7 +3022,7 @@ impl RpcClient { &self, signature: &Signature, encoding: UiTransactionEncoding, - ) -> ClientResult { + ) -> ClientResult { self.send( RpcRequest::GetConfirmedTransaction, json!([signature.to_string(), encoding]), @@ -3038,7 +3038,7 @@ impl RpcClient { &self, signature: &Signature, config: RpcConfirmedTransactionConfig, - ) -> ClientResult { + ) -> ClientResult { self.send( RpcRequest::GetConfirmedTransaction, json!([signature.to_string(), config]), diff --git a/ledger-tool/src/bigtable.rs b/ledger-tool/src/bigtable.rs index b3d9ac8e15..e15c4058f3 100644 --- a/ledger-tool/src/bigtable.rs +++ b/ledger-tool/src/bigtable.rs @@ -14,7 +14,7 @@ use { }, solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType}, solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature}, - solana_transaction_status::{ConfirmedBlock, EncodedTransaction, UiTransactionEncoding}, + solana_transaction_status::{ConfirmedBlock, Encodable, UiTransactionEncoding}, std::{ path::Path, process::exit, @@ -109,10 +109,10 @@ async fn confirm( match bigtable.get_confirmed_transaction(signature).await { Ok(Some(confirmed_transaction)) => { transaction = Some(CliTransaction { - transaction: EncodedTransaction::encode( - confirmed_transaction.transaction.transaction.clone(), - UiTransactionEncoding::Json, - ), + transaction: confirmed_transaction + .transaction + .transaction + .encode(UiTransactionEncoding::Json), meta: confirmed_transaction.transaction.meta.map(|m| m.into()), block_time: confirmed_transaction.block_time, slot: Some(confirmed_transaction.slot), diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index ab698c3ae2..58212761de 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -41,8 +41,9 @@ use { }, solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta}, solana_transaction_status::{ - ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, Rewards, - TransactionStatusMeta, TransactionWithStatusMeta, + ConfirmedBlock, ConfirmedTransactionStatusWithSignature, + ConfirmedTransactionWithStatusMeta, Rewards, TransactionStatusMeta, + TransactionWithStatusMeta, }, std::{ borrow::Cow, @@ -2307,7 +2308,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) @@ -2320,7 +2321,7 @@ impl Blockstore { &self, signature: Signature, highest_confirmed_slot: Slot, - ) -> Result> { + ) -> Result> { datapoint_info!( "blockstore-rpc-api", ("method", "get_complete_transaction".to_string(), String) @@ -2337,7 +2338,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)? { @@ -2351,7 +2352,7 @@ impl Blockstore { .ok_or(BlockstoreError::UnsupportedTransactionVersion)?; let block_time = self.get_block_time(slot)?; - Ok(Some(ConfirmedTransaction { + Ok(Some(ConfirmedTransactionWithStatusMeta { slot, transaction: TransactionWithStatusMeta { transaction, @@ -7171,7 +7172,7 @@ pub mod tests { let signature = transaction.transaction.signatures[0]; assert_eq!( blockstore.get_rooted_transaction(signature).unwrap(), - Some(ConfirmedTransaction { + Some(ConfirmedTransactionWithStatusMeta { slot, transaction: transaction.clone(), block_time: None @@ -7181,7 +7182,7 @@ pub mod tests { blockstore .get_complete_transaction(signature, slot + 1) .unwrap(), - Some(ConfirmedTransaction { + Some(ConfirmedTransactionWithStatusMeta { slot, transaction, block_time: None @@ -7277,7 +7278,7 @@ pub mod tests { blockstore .get_complete_transaction(signature, slot) .unwrap(), - Some(ConfirmedTransaction { + Some(ConfirmedTransactionWithStatusMeta { slot, transaction, block_time: None diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index a5773db269..a5fc4afdda 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -52,8 +52,8 @@ use solana_sdk::{ transaction::{SanitizedTransaction, Transaction, TransactionError}, }; use solana_transaction_status::{ - token_balances::collect_token_balances, ConfirmedTransaction, InnerInstructions, - TransactionStatusMeta, TransactionWithStatusMeta, UiTransactionEncoding, + token_balances::collect_token_balances, ConfirmedTransactionWithStatusMeta, Encodable, + InnerInstructions, TransactionStatusMeta, TransactionWithStatusMeta, UiTransactionEncoding, }; use std::{ collections::HashMap, convert::TryFrom, env, fs::File, io::Read, path::PathBuf, str::FromStr, @@ -320,7 +320,10 @@ fn process_transaction_and_record_inner( ) } -fn execute_transactions(bank: &Bank, txs: Vec) -> Vec { +fn execute_transactions( + bank: &Bank, + txs: Vec, +) -> Vec { let batch = bank.prepare_batch_for_tests(txs.clone()); let mut timings = ExecuteTimings::default(); let mut mint_decimals = HashMap::new(); @@ -402,7 +405,7 @@ fn execute_transactions(bank: &Bank, txs: Vec) -> Vec) -> Vec>, - ) -> Result> { + ) -> Result> { let config = config .map(|config| config.convert_to_current()) .unwrap_or_default(); @@ -3214,7 +3214,7 @@ pub mod rpc_full { meta: Self::Metadata, signature_str: String, config: Option>, - ) -> BoxFuture>>; + ) -> BoxFuture>>; #[rpc(meta, name = "getSignaturesForAddress")] fn get_signatures_for_address( @@ -3689,7 +3689,7 @@ pub mod rpc_full { meta: Self::Metadata, signature_str: String, config: Option>, - ) -> BoxFuture>> { + ) -> BoxFuture>> { debug!("get_transaction rpc request received: {:?}", signature_str); let signature = verify_signature(&signature_str); if let Err(err) = signature { @@ -3928,7 +3928,7 @@ pub mod rpc_deprecated_v1_7 { meta: Self::Metadata, signature_str: String, config: Option>, - ) -> BoxFuture>>; + ) -> BoxFuture>>; // DEPRECATED #[rpc(meta, name = "getConfirmedSignaturesForAddress2")] @@ -3998,7 +3998,7 @@ pub mod rpc_deprecated_v1_7 { meta: Self::Metadata, signature_str: String, config: Option>, - ) -> BoxFuture>> { + ) -> BoxFuture>> { debug!( "get_confirmed_transaction rpc request received: {:?}", signature_str diff --git a/sdk/program/src/message/mod.rs b/sdk/program/src/message/mod.rs index 910a03c39c..b63e84a720 100644 --- a/sdk/program/src/message/mod.rs +++ b/sdk/program/src/message/mod.rs @@ -18,7 +18,7 @@ pub use non_bpf_modules::*; /// The length of a message header in bytes pub const MESSAGE_HEADER_LENGTH: usize = 3; -#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, AbiExample)] +#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone, Copy, AbiExample)] #[serde(rename_all = "camelCase")] pub struct MessageHeader { /// The number of signatures required for this message to be considered valid. The diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index 18c6c1d8a2..59e5fbf02d 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -13,8 +13,8 @@ use { }, solana_storage_proto::convert::{generated, tx_by_addr}, solana_transaction_status::{ - extract_and_fmt_memos, ConfirmedBlock, ConfirmedTransaction, - ConfirmedTransactionStatusWithSignature, Reward, TransactionByAddrInfo, + extract_and_fmt_memos, ConfirmedBlock, ConfirmedTransactionStatusWithSignature, + ConfirmedTransactionWithStatusMeta, Reward, TransactionByAddrInfo, TransactionConfirmationStatus, TransactionStatus, TransactionStatusMeta, TransactionWithStatusMeta, }, @@ -433,7 +433,7 @@ impl LedgerStorage { pub async fn get_confirmed_transaction( &self, signature: &Signature, - ) -> Result> { + ) -> Result> { debug!( "LedgerStorage::get_confirmed_transaction request received: {:?}", signature @@ -466,7 +466,7 @@ impl LedgerStorage { ); Ok(None) } else { - Ok(Some(ConfirmedTransaction { + Ok(Some(ConfirmedTransactionWithStatusMeta { slot, transaction: bucket_block_transaction, block_time: block.block_time, diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index acfc2a8dc2..55ea242b13 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -34,6 +34,45 @@ use { }, std::fmt, }; + +/// Represents types that can be encoded into one of several encoding formats +pub trait Encodable { + type Encoded; + fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded; +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum UiTransactionEncoding { + Binary, // Legacy. Retained for RPC backwards compatibility + Base64, + Base58, + Json, + JsonParsed, +} + +impl fmt::Display for UiTransactionEncoding { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let v = serde_json::to_value(self).map_err(|_| fmt::Error)?; + let s = v.as_str().ok_or(fmt::Error)?; + write!(f, "{}", s) + } +} + +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TransactionDetails { + Full, + Signatures, + None, +} + +impl Default for TransactionDetails { + fn default() -> Self { + Self::Full + } +} + /// A duplicate representation of an Instruction for pretty JSON serialization #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase", untagged)] @@ -42,13 +81,6 @@ pub enum UiInstruction { Parsed(UiParsedInstruction), } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub enum UiParsedInstruction { - Parsed(ParsedInstruction), - PartiallyDecoded(UiPartiallyDecodedInstruction), -} - impl UiInstruction { fn parse(instruction: &CompiledInstruction, message: &Message) -> Self { let program_id = instruction.program_id(&message.account_keys); @@ -62,6 +94,13 @@ impl UiInstruction { } } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum UiParsedInstruction { + Parsed(ParsedInstruction), + PartiallyDecoded(UiPartiallyDecodedInstruction), +} + /// A duplicate representation of a CompiledInstruction for pretty JSON serialization #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -121,6 +160,32 @@ pub struct UiInnerInstructions { pub instructions: Vec, } +impl UiInnerInstructions { + fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self { + Self { + index: inner_instructions.index, + instructions: inner_instructions + .instructions + .iter() + .map(|ix| UiInstruction::parse(ix, message)) + .collect(), + } + } +} + +impl From for UiInnerInstructions { + fn from(inner_instructions: InnerInstructions) -> Self { + Self { + index: inner_instructions.index, + instructions: inner_instructions + .instructions + .iter() + .map(|ix| UiInstruction::Compiled(ix.into())) + .collect(), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct TransactionTokenBalance { pub account_index: u8, @@ -154,32 +219,6 @@ impl From for UiTransactionTokenBalance { } } -impl UiInnerInstructions { - fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self { - Self { - index: inner_instructions.index, - instructions: inner_instructions - .instructions - .iter() - .map(|ix| UiInstruction::parse(ix, message)) - .collect(), - } - } -} - -impl From for UiInnerInstructions { - fn from(inner_instructions: InnerInstructions) -> Self { - Self { - index: inner_instructions.index, - instructions: inner_instructions - .instructions - .iter() - .map(|ix| UiInstruction::Compiled(ix.into())) - .collect(), - } - } -} - #[derive(Clone, Debug, PartialEq)] pub struct TransactionStatusMeta { pub status: Result<()>, @@ -241,10 +280,10 @@ impl UiTransactionStatusMeta { log_messages: meta.log_messages, pre_token_balances: meta .pre_token_balances - .map(|balance| balance.into_iter().map(|balance| balance.into()).collect()), + .map(|balance| balance.into_iter().map(Into::into).collect()), post_token_balances: meta .post_token_balances - .map(|balance| balance.into_iter().map(|balance| balance.into()).collect()), + .map(|balance| balance.into_iter().map(Into::into).collect()), rewards: meta.rewards, } } @@ -260,14 +299,14 @@ impl From for UiTransactionStatusMeta { post_balances: meta.post_balances, inner_instructions: meta .inner_instructions - .map(|ixs| ixs.into_iter().map(|ix| ix.into()).collect()), + .map(|ixs| ixs.into_iter().map(Into::into).collect()), log_messages: meta.log_messages, pre_token_balances: meta .pre_token_balances - .map(|balance| balance.into_iter().map(|balance| balance.into()).collect()), + .map(|balance| balance.into_iter().map(Into::into).collect()), post_token_balances: meta .post_token_balances - .map(|balance| balance.into_iter().map(|balance| balance.into()).collect()), + .map(|balance| balance.into_iter().map(Into::into).collect()), rewards: meta.rewards, } } @@ -358,9 +397,10 @@ pub struct ConfirmedBlock { pub block_height: Option, } -impl ConfirmedBlock { - pub fn encode(self, encoding: UiTransactionEncoding) -> EncodedConfirmedBlock { - EncodedConfirmedBlock { +impl Encodable for ConfirmedBlock { + type Encoded = EncodedConfirmedBlock; + fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded { + Self::Encoded { previous_blockhash: self.previous_blockhash, blockhash: self.blockhash, parent_slot: self.parent_slot, @@ -374,7 +414,9 @@ impl ConfirmedBlock { block_height: self.block_height, } } +} +impl ConfirmedBlock { pub fn configure( self, encoding: UiTransactionEncoding, @@ -431,6 +473,20 @@ pub struct EncodedConfirmedBlock { pub block_height: Option, } +impl From for EncodedConfirmedBlock { + fn from(block: UiConfirmedBlock) -> Self { + Self { + previous_blockhash: block.previous_blockhash, + blockhash: block.blockhash, + parent_slot: block.parent_slot, + transactions: block.transactions.unwrap_or_default(), + rewards: block.rewards.unwrap_or_default(), + block_time: block.block_time, + block_height: block.block_height, + } + } +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UiConfirmedBlock { @@ -462,107 +518,23 @@ impl From for UiConfirmedBlock { } } -impl From for EncodedConfirmedBlock { - fn from(block: UiConfirmedBlock) -> Self { - Self { - previous_blockhash: block.previous_blockhash, - blockhash: block.blockhash, - parent_slot: block.parent_slot, - transactions: block.transactions.unwrap_or_default(), - rewards: block.rewards.unwrap_or_default(), - block_time: block.block_time, - block_height: block.block_height, - } - } -} - -#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum TransactionDetails { - Full, - Signatures, - None, -} - -impl Default for TransactionDetails { - fn default() -> Self { - Self::Full - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ConfirmedTransaction { - pub slot: Slot, - pub transaction: TransactionWithStatusMeta, - pub block_time: Option, -} - -impl ConfirmedTransaction { - pub fn encode(self, encoding: UiTransactionEncoding) -> EncodedConfirmedTransaction { - EncodedConfirmedTransaction { - slot: self.slot, - transaction: self.transaction.encode(encoding), - block_time: self.block_time, - } - } -} - -#[derive(Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EncodedConfirmedTransaction { - pub slot: Slot, - #[serde(flatten)] - pub transaction: EncodedTransactionWithStatusMeta, - pub block_time: Option, -} - -/// A duplicate representation of a Transaction for pretty JSON serialization -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UiTransaction { - pub signatures: Vec, - pub message: UiMessage, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub enum UiMessage { - Parsed(UiParsedMessage), - Raw(UiRawMessage), -} - -/// A duplicate representation of a Message, in raw format, for pretty JSON serialization -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UiRawMessage { - pub header: MessageHeader, - pub account_keys: Vec, - pub recent_blockhash: String, - pub instructions: Vec, -} - -/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UiParsedMessage { - pub account_keys: Vec, - pub recent_blockhash: String, - pub instructions: Vec, -} - #[derive(Clone, Debug, PartialEq)] pub struct TransactionWithStatusMeta { pub transaction: Transaction, pub meta: Option, } -impl TransactionWithStatusMeta { - fn encode(self, encoding: UiTransactionEncoding) -> EncodedTransactionWithStatusMeta { - let message = self.transaction.message(); - let meta = self.meta.map(|meta| meta.encode(encoding, message)); - EncodedTransactionWithStatusMeta { - transaction: EncodedTransaction::encode(self.transaction, encoding), - meta, +impl Encodable for TransactionWithStatusMeta { + type Encoded = EncodedTransactionWithStatusMeta; + fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded { + Self::Encoded { + transaction: self.transaction.encode(encoding), + meta: self.meta.map(|meta| match encoding { + UiTransactionEncoding::JsonParsed => { + UiTransactionStatusMeta::parse(meta, self.transaction.message()) + } + _ => UiTransactionStatusMeta::from(meta), + }), } } } @@ -573,32 +545,31 @@ pub struct EncodedTransactionWithStatusMeta { pub transaction: EncodedTransaction, pub meta: Option, } +#[derive(Debug, Clone, PartialEq)] +pub struct ConfirmedTransactionWithStatusMeta { + pub slot: Slot, + pub transaction: TransactionWithStatusMeta, + pub block_time: Option, +} -impl TransactionStatusMeta { - fn encode(self, encoding: UiTransactionEncoding, message: &Message) -> UiTransactionStatusMeta { - match encoding { - UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(self, message), - _ => self.into(), +impl Encodable for ConfirmedTransactionWithStatusMeta { + type Encoded = EncodedConfirmedTransactionWithStatusMeta; + fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded { + Self::Encoded { + slot: self.slot, + transaction: self.transaction.encode(encoding), + block_time: self.block_time, } } } -#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub enum UiTransactionEncoding { - Binary, // Legacy. Retained for RPC backwards compatibility - Base64, - Base58, - Json, - JsonParsed, -} - -impl fmt::Display for UiTransactionEncoding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let v = serde_json::to_value(self).map_err(|_| fmt::Error)?; - let s = v.as_str().ok_or(fmt::Error)?; - write!(f, "{}", s) - } +pub struct EncodedConfirmedTransactionWithStatusMeta { + pub slot: Slot, + #[serde(flatten)] + pub transaction: EncodedTransactionWithStatusMeta, + pub block_time: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -609,63 +580,32 @@ pub enum EncodedTransaction { Json(UiTransaction), } -impl EncodedTransaction { - pub fn encode(transaction: Transaction, encoding: UiTransactionEncoding) -> Self { +impl Encodable for &Transaction { + type Encoded = EncodedTransaction; + fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded { match encoding { UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary( - bs58::encode(bincode::serialize(&transaction).unwrap()).into_string(), + bs58::encode(bincode::serialize(self).unwrap()).into_string(), ), UiTransactionEncoding::Base58 => EncodedTransaction::Binary( - bs58::encode(bincode::serialize(&transaction).unwrap()).into_string(), + bs58::encode(bincode::serialize(self).unwrap()).into_string(), encoding, ), UiTransactionEncoding::Base64 => EncodedTransaction::Binary( - base64::encode(bincode::serialize(&transaction).unwrap()), + base64::encode(bincode::serialize(self).unwrap()), encoding, ), UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => { - let message = if encoding == UiTransactionEncoding::Json { - UiMessage::Raw(UiRawMessage { - header: transaction.message.header, - account_keys: transaction - .message - .account_keys - .iter() - .map(|pubkey| pubkey.to_string()) - .collect(), - recent_blockhash: transaction.message.recent_blockhash.to_string(), - instructions: transaction - .message - .instructions - .iter() - .map(|instruction| instruction.into()) - .collect(), - }) - } else { - UiMessage::Parsed(UiParsedMessage { - account_keys: parse_accounts(&transaction.message), - recent_blockhash: transaction.message.recent_blockhash.to_string(), - instructions: transaction - .message - .instructions - .iter() - .map(|instruction| { - UiInstruction::parse(instruction, &transaction.message) - }) - .collect(), - }) - }; EncodedTransaction::Json(UiTransaction { - signatures: transaction - .signatures - .iter() - .map(|sig| sig.to_string()) - .collect(), - message, + signatures: self.signatures.iter().map(ToString::to_string).collect(), + message: self.message.encode(encoding), }) } } } +} + +impl EncodedTransaction { pub fn decode(&self) -> Option { let transaction: Option = match self { EncodedTransaction::Json(_) => None, @@ -690,6 +630,64 @@ impl EncodedTransaction { } } +/// A duplicate representation of a Transaction for pretty JSON serialization +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiTransaction { + pub signatures: Vec, + pub message: UiMessage, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum UiMessage { + Parsed(UiParsedMessage), + Raw(UiRawMessage), +} + +impl Encodable for &Message { + type Encoded = UiMessage; + fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded { + if encoding == UiTransactionEncoding::JsonParsed { + UiMessage::Parsed(UiParsedMessage { + account_keys: parse_accounts(self), + recent_blockhash: self.recent_blockhash.to_string(), + instructions: self + .instructions + .iter() + .map(|instruction| UiInstruction::parse(instruction, self)) + .collect(), + }) + } else { + UiMessage::Raw(UiRawMessage { + header: self.header, + 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(), + }) + } + } +} + +/// A duplicate representation of a Message, in raw format, for pretty JSON serialization +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiRawMessage { + pub header: MessageHeader, + pub account_keys: Vec, + pub recent_blockhash: String, + pub instructions: Vec, +} + +/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiParsedMessage { + pub account_keys: Vec, + pub recent_blockhash: String, + pub instructions: Vec, +} + // A serialized `Vec` is stored in the `tx-by-addr` table. The row keys are // the one's compliment of the slot so that rows may be listed in reverse order #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]