From 973a5fb0e94e5569f5f4de72a2cdcaf524203195 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Thu, 15 Sep 2022 09:00:30 -0700 Subject: [PATCH] Add new TransactionDetails level (#27601) * Add new transaction details level * Dedupe common code * Update docs * Respect showRewards parameter in tx meta --- docs/src/developing/clients/jsonrpc-api.md | 2 +- transaction-status/src/lib.rs | 138 +++++++++++++++++++-- 2 files changed, 132 insertions(+), 8 deletions(-) diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index eef0f3d9b2aa33..51aa2903571d6f 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -412,7 +412,7 @@ Returns identity and transaction information about a confirmed block in the ledg - (optional) `` - Configuration object containing the following optional fields: - (optional) `encoding: ` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (_slow_), "base64". If parameter not provided, the default encoding is "json". ["jsonParsed" encoding](jsonrpc-api.md#parsed-responses) attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If "jsonParsed" is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). - - (optional) `transactionDetails: ` - level of transaction detail to return, either "full", "signatures", or "none". If parameter not provided, the default detail level is "full". + - (optional) `transactionDetails: ` - level of transaction detail to return, either "full", "accounts", "signatures", or "none". If parameter not provided, the default detail level is "full". If "accounts" are requested, transaction details only include signatures and an annotated list of accounts in each transaction. Transaction metadata is limited to only: fee, err, pre_balances, post_balances, pre_token_balances, and post_token_balances. - (optional) `rewards: bool` - whether to populate the `rewards` array. If parameter not provided, the default includes rewards. - (optional) `commitment: ` - [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized". - (optional) `maxSupportedTransactionVersion: ` - set the max transaction version to return in responses. If the requested block contains a transaction with a higher version, an error will be returned. If this parameter is omitted, only legacy transactions will be returned, and a block containing any versioned transaction will prompt the error. diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 32a0b5e0f03233..d907ebcf69ebfb 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -75,6 +75,11 @@ pub trait EncodableWithMeta { fn json_encode(&self) -> Self::Encoded; } +trait JsonAccounts { + type Encoded; + fn build_json_accounts(&self) -> Self::Encoded; +} + #[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)] #[serde(rename_all = "camelCase")] pub enum TransactionBinaryEncoding { @@ -116,6 +121,7 @@ pub enum TransactionDetails { Full, Signatures, None, + Accounts, } impl Default for TransactionDetails { @@ -429,6 +435,34 @@ impl UiTransactionStatusMeta { compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed), } } + + fn build_simple(meta: TransactionStatusMeta, show_rewards: bool) -> Self { + Self { + err: meta.status.clone().err(), + status: meta.status, + fee: meta.fee, + pre_balances: meta.pre_balances, + post_balances: meta.post_balances, + inner_instructions: OptionSerializer::Skip, + log_messages: OptionSerializer::Skip, + pre_token_balances: meta + .pre_token_balances + .map(|balance| balance.into_iter().map(Into::into).collect()) + .into(), + post_token_balances: meta + .post_token_balances + .map(|balance| balance.into_iter().map(Into::into).collect()) + .into(), + rewards: if show_rewards { + meta.rewards.into() + } else { + OptionSerializer::Skip + }, + loaded_addresses: OptionSerializer::Skip, + return_data: OptionSerializer::Skip, + compute_units_consumed: OptionSerializer::Skip, + } + } } impl From for UiTransactionStatusMeta { @@ -610,6 +644,20 @@ impl ConfirmedBlock { ), ), TransactionDetails::None => (None, None), + TransactionDetails::Accounts => ( + Some( + self.transactions + .into_iter() + .map(|tx_with_meta| { + tx_with_meta.build_json_accounts( + options.max_supported_transaction_version, + options.show_rewards, + ) + }) + .collect::, _>>()?, + ), + None, + ), }; Ok(UiConfirmedBlock { previous_blockhash: self.previous_blockhash, @@ -733,16 +781,31 @@ impl TransactionWithStatusMeta { Self::Complete(tx_with_meta) => tx_with_meta.account_keys(), } } -} -impl VersionedTransactionWithStatusMeta { - pub fn encode( + fn build_json_accounts( self, - encoding: UiTransactionEncoding, max_supported_transaction_version: Option, show_rewards: bool, ) -> Result { - let version = match ( + match self { + Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta { + version: None, + transaction: transaction.build_json_accounts(), + meta: None, + }), + Self::Complete(tx_with_meta) => { + tx_with_meta.build_json_accounts(max_supported_transaction_version, show_rewards) + } + } + } +} + +impl VersionedTransactionWithStatusMeta { + fn validate_version( + &self, + max_supported_transaction_version: Option, + ) -> Result, EncodeError> { + match ( max_supported_transaction_version, self.transaction.version(), ) { @@ -759,7 +822,16 @@ impl VersionedTransactionWithStatusMeta { Err(EncodeError::UnsupportedTransactionVersion(version)) } } - }?; + } + } + + pub fn encode( + self, + encoding: UiTransactionEncoding, + max_supported_transaction_version: Option, + show_rewards: bool, + ) -> Result { + let version = self.validate_version(max_supported_transaction_version)?; Ok(EncodedTransactionWithStatusMeta { transaction: self.transaction.encode_with_meta(encoding, &self.meta), @@ -787,6 +859,40 @@ impl VersionedTransactionWithStatusMeta { Some(&self.meta.loaded_addresses), ) } + + fn build_json_accounts( + self, + max_supported_transaction_version: Option, + show_rewards: bool, + ) -> Result { + let version = self.validate_version(max_supported_transaction_version)?; + + let account_keys = match &self.transaction.message { + VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message), + VersionedMessage::V0(message) => { + let loaded_message = + LoadedMessage::new_borrowed(message, &self.meta.loaded_addresses); + parse_v0_message_accounts(&loaded_message) + } + }; + + Ok(EncodedTransactionWithStatusMeta { + transaction: EncodedTransaction::Accounts(UiAccountsList { + signatures: self + .transaction + .signatures + .iter() + .map(ToString::to_string) + .collect(), + account_keys, + }), + meta: Some(UiTransactionStatusMeta::build_simple( + self.meta, + show_rewards, + )), + version, + }) + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -849,6 +955,7 @@ pub enum EncodedTransaction { LegacyBinary(String), // Old way of expressing base-58, retained for RPC backwards compatibility Binary(String, TransactionBinaryEncoding), Json(UiTransaction), + Accounts(UiAccountsList), } impl EncodableWithMeta for VersionedTransaction { @@ -920,10 +1027,20 @@ impl Encodable for Transaction { } } +impl JsonAccounts for Transaction { + type Encoded = EncodedTransaction; + fn build_json_accounts(&self) -> Self::Encoded { + EncodedTransaction::Accounts(UiAccountsList { + signatures: self.signatures.iter().map(ToString::to_string).collect(), + account_keys: parse_legacy_message_accounts(&self.message), + }) + } +} + impl EncodedTransaction { pub fn decode(&self) -> Option { let (blob, encoding) = match self { - Self::Json(_) => return None, + Self::Json(_) | Self::Accounts(_) => return None, Self::LegacyBinary(blob) => (blob, TransactionBinaryEncoding::Base58), Self::Binary(blob, encoding) => (blob, *encoding), }; @@ -1041,6 +1158,13 @@ pub struct UiRawMessage { pub address_table_lookups: Option>, } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiAccountsList { + pub signatures: Vec, + pub account_keys: Vec, +} + /// A duplicate representation of a MessageAddressTableLookup, in raw format, for pretty JSON serialization #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")]