solana_transaction_status/
lib.rs

1#![allow(clippy::arithmetic_side_effects)]
2
3pub use {
4    crate::extract_memos::extract_and_fmt_memos,
5    solana_reward_info::RewardType,
6    solana_transaction_status_client_types::{
7        option_serializer, ConfirmedTransactionStatusWithSignature, EncodeError,
8        EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
9        EncodedTransactionWithStatusMeta, InnerInstruction, InnerInstructions, Reward, Rewards,
10        TransactionBinaryEncoding, TransactionConfirmationStatus, TransactionDetails,
11        TransactionStatus, TransactionStatusMeta, TransactionTokenBalance, UiAccountsList,
12        UiAddressTableLookup, UiCompiledInstruction, UiConfirmedBlock, UiInnerInstructions,
13        UiInstruction, UiLoadedAddresses, UiMessage, UiParsedInstruction, UiParsedMessage,
14        UiPartiallyDecodedInstruction, UiRawMessage, UiReturnDataEncoding, UiTransaction,
15        UiTransactionEncoding, UiTransactionReturnData, UiTransactionStatusMeta,
16        UiTransactionTokenBalance,
17    },
18};
19use {
20    crate::{
21        option_serializer::OptionSerializer,
22        parse_accounts::{parse_legacy_message_accounts, parse_v0_message_accounts},
23        parse_instruction::parse,
24    },
25    base64::{prelude::BASE64_STANDARD, Engine},
26    solana_clock::{Slot, UnixTimestamp},
27    solana_hash::Hash,
28    solana_message::{
29        compiled_instruction::CompiledInstruction,
30        v0::{self, LoadedAddresses, LoadedMessage},
31        AccountKeys, Message, VersionedMessage,
32    },
33    solana_pubkey::Pubkey,
34    solana_reserved_account_keys::ReservedAccountKeys,
35    solana_signature::Signature,
36    solana_transaction::{
37        versioned::{TransactionVersion, VersionedTransaction},
38        Transaction,
39    },
40    solana_transaction_error::TransactionError,
41    std::collections::HashSet,
42    thiserror::Error,
43};
44
45#[macro_use]
46extern crate lazy_static;
47#[macro_use]
48extern crate serde_derive;
49
50pub mod extract_memos;
51pub mod parse_accounts;
52pub mod parse_address_lookup_table;
53pub mod parse_associated_token;
54pub mod parse_bpf_loader;
55pub mod parse_instruction;
56pub mod parse_stake;
57pub mod parse_system;
58pub mod parse_token;
59pub mod parse_vote;
60pub mod token_balances;
61
62pub struct BlockEncodingOptions {
63    pub transaction_details: TransactionDetails,
64    pub show_rewards: bool,
65    pub max_supported_transaction_version: Option<u8>,
66}
67
68/// Represents types that can be encoded into one of several encoding formats
69pub trait Encodable {
70    type Encoded;
71    fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
72}
73
74/// Represents types that can be encoded into one of several encoding formats
75pub trait EncodableWithMeta {
76    type Encoded;
77    fn encode_with_meta(
78        &self,
79        encoding: UiTransactionEncoding,
80        meta: &TransactionStatusMeta,
81    ) -> Self::Encoded;
82    fn json_encode(&self) -> Self::Encoded;
83}
84
85trait JsonAccounts {
86    type Encoded;
87    fn build_json_accounts(&self) -> Self::Encoded;
88}
89
90fn make_ui_partially_decoded_instruction(
91    instruction: &CompiledInstruction,
92    account_keys: &AccountKeys,
93    stack_height: Option<u32>,
94) -> UiPartiallyDecodedInstruction {
95    UiPartiallyDecodedInstruction {
96        program_id: account_keys[instruction.program_id_index as usize].to_string(),
97        accounts: instruction
98            .accounts
99            .iter()
100            .map(|&i| account_keys[i as usize].to_string())
101            .collect(),
102        data: bs58::encode(instruction.data.clone()).into_string(),
103        stack_height,
104    }
105}
106
107pub fn parse_ui_instruction(
108    instruction: &CompiledInstruction,
109    account_keys: &AccountKeys,
110    stack_height: Option<u32>,
111) -> UiInstruction {
112    let program_id = &account_keys[instruction.program_id_index as usize];
113    if let Ok(parsed_instruction) = parse(program_id, instruction, account_keys, stack_height) {
114        UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
115    } else {
116        UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
117            make_ui_partially_decoded_instruction(instruction, account_keys, stack_height),
118        ))
119    }
120}
121
122/// Maps a list of inner instructions from `solana_sdk` into a list of this
123/// crate's representation of inner instructions (with instruction indices).
124pub fn map_inner_instructions(
125    inner_instructions: solana_message::inner_instruction::InnerInstructionsList,
126) -> impl Iterator<Item = InnerInstructions> {
127    inner_instructions
128        .into_iter()
129        .enumerate()
130        .map(|(index, instructions)| InnerInstructions {
131            index: index as u8,
132            instructions: instructions
133                .into_iter()
134                .map(|info| InnerInstruction {
135                    stack_height: Some(u32::from(info.stack_height)),
136                    instruction: info.instruction,
137                })
138                .collect(),
139        })
140        .filter(|i| !i.instructions.is_empty())
141}
142
143pub fn parse_ui_inner_instructions(
144    inner_instructions: InnerInstructions,
145    account_keys: &AccountKeys,
146) -> UiInnerInstructions {
147    UiInnerInstructions {
148        index: inner_instructions.index,
149        instructions: inner_instructions
150            .instructions
151            .iter()
152            .map(
153                |InnerInstruction {
154                     instruction: ix,
155                     stack_height,
156                 }| { parse_ui_instruction(ix, account_keys, *stack_height) },
157            )
158            .collect(),
159    }
160}
161
162fn build_simple_ui_transaction_status_meta(
163    meta: TransactionStatusMeta,
164    show_rewards: bool,
165) -> UiTransactionStatusMeta {
166    UiTransactionStatusMeta {
167        err: meta.status.clone().err(),
168        status: meta.status,
169        fee: meta.fee,
170        pre_balances: meta.pre_balances,
171        post_balances: meta.post_balances,
172        inner_instructions: OptionSerializer::Skip,
173        log_messages: OptionSerializer::Skip,
174        pre_token_balances: meta
175            .pre_token_balances
176            .map(|balance| balance.into_iter().map(Into::into).collect())
177            .into(),
178        post_token_balances: meta
179            .post_token_balances
180            .map(|balance| balance.into_iter().map(Into::into).collect())
181            .into(),
182        rewards: if show_rewards {
183            meta.rewards.into()
184        } else {
185            OptionSerializer::Skip
186        },
187        loaded_addresses: OptionSerializer::Skip,
188        return_data: OptionSerializer::Skip,
189        compute_units_consumed: OptionSerializer::Skip,
190    }
191}
192
193fn parse_ui_transaction_status_meta(
194    meta: TransactionStatusMeta,
195    static_keys: &[Pubkey],
196    show_rewards: bool,
197) -> UiTransactionStatusMeta {
198    let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
199    UiTransactionStatusMeta {
200        err: meta.status.clone().err(),
201        status: meta.status,
202        fee: meta.fee,
203        pre_balances: meta.pre_balances,
204        post_balances: meta.post_balances,
205        inner_instructions: meta
206            .inner_instructions
207            .map(|ixs| {
208                ixs.into_iter()
209                    .map(|ix| parse_ui_inner_instructions(ix, &account_keys))
210                    .collect()
211            })
212            .into(),
213        log_messages: meta.log_messages.into(),
214        pre_token_balances: meta
215            .pre_token_balances
216            .map(|balance| balance.into_iter().map(Into::into).collect())
217            .into(),
218        post_token_balances: meta
219            .post_token_balances
220            .map(|balance| balance.into_iter().map(Into::into).collect())
221            .into(),
222        rewards: if show_rewards { meta.rewards } else { None }.into(),
223        loaded_addresses: OptionSerializer::Skip,
224        return_data: OptionSerializer::or_skip(
225            meta.return_data.map(|return_data| return_data.into()),
226        ),
227        compute_units_consumed: OptionSerializer::or_skip(meta.compute_units_consumed),
228    }
229}
230
231#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
232pub struct RewardsAndNumPartitions {
233    pub rewards: Rewards,
234    pub num_partitions: Option<u64>,
235}
236
237#[derive(Debug, Error)]
238pub enum ConvertBlockError {
239    #[error("transactions missing after converted, before: {0}, after: {1}")]
240    TransactionsMissing(usize, usize),
241}
242
243#[derive(Clone, Debug, PartialEq)]
244pub struct ConfirmedBlock {
245    pub previous_blockhash: String,
246    pub blockhash: String,
247    pub parent_slot: Slot,
248    pub transactions: Vec<TransactionWithStatusMeta>,
249    pub rewards: Rewards,
250    pub num_partitions: Option<u64>,
251    pub block_time: Option<UnixTimestamp>,
252    pub block_height: Option<u64>,
253}
254
255// Confirmed block with type guarantees that transaction metadata
256// is always present. Used for uploading to BigTable.
257#[derive(Clone, Debug, PartialEq)]
258pub struct VersionedConfirmedBlock {
259    pub previous_blockhash: String,
260    pub blockhash: String,
261    pub parent_slot: Slot,
262    pub transactions: Vec<VersionedTransactionWithStatusMeta>,
263    pub rewards: Rewards,
264    pub num_partitions: Option<u64>,
265    pub block_time: Option<UnixTimestamp>,
266    pub block_height: Option<u64>,
267}
268
269impl From<VersionedConfirmedBlock> for ConfirmedBlock {
270    fn from(block: VersionedConfirmedBlock) -> Self {
271        Self {
272            previous_blockhash: block.previous_blockhash,
273            blockhash: block.blockhash,
274            parent_slot: block.parent_slot,
275            transactions: block
276                .transactions
277                .into_iter()
278                .map(TransactionWithStatusMeta::Complete)
279                .collect(),
280            rewards: block.rewards,
281            num_partitions: block.num_partitions,
282            block_time: block.block_time,
283            block_height: block.block_height,
284        }
285    }
286}
287
288impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
289    type Error = ConvertBlockError;
290
291    fn try_from(block: ConfirmedBlock) -> Result<Self, Self::Error> {
292        let expected_transaction_count = block.transactions.len();
293
294        let txs: Vec<_> = block
295            .transactions
296            .into_iter()
297            .filter_map(|tx| match tx {
298                TransactionWithStatusMeta::MissingMetadata(_) => None,
299                TransactionWithStatusMeta::Complete(tx) => Some(tx),
300            })
301            .collect();
302
303        if txs.len() != expected_transaction_count {
304            return Err(ConvertBlockError::TransactionsMissing(
305                expected_transaction_count,
306                txs.len(),
307            ));
308        }
309
310        Ok(Self {
311            previous_blockhash: block.previous_blockhash,
312            blockhash: block.blockhash,
313            parent_slot: block.parent_slot,
314            transactions: txs,
315            rewards: block.rewards,
316            num_partitions: block.num_partitions,
317            block_time: block.block_time,
318            block_height: block.block_height,
319        })
320    }
321}
322
323impl ConfirmedBlock {
324    pub fn encode_with_options(
325        self,
326        encoding: UiTransactionEncoding,
327        options: BlockEncodingOptions,
328    ) -> Result<UiConfirmedBlock, EncodeError> {
329        let (transactions, signatures) = match options.transaction_details {
330            TransactionDetails::Full => (
331                Some(
332                    self.transactions
333                        .into_iter()
334                        .map(|tx_with_meta| {
335                            tx_with_meta.encode(
336                                encoding,
337                                options.max_supported_transaction_version,
338                                options.show_rewards,
339                            )
340                        })
341                        .collect::<Result<Vec<_>, _>>()?,
342                ),
343                None,
344            ),
345            TransactionDetails::Signatures => (
346                None,
347                Some(
348                    self.transactions
349                        .into_iter()
350                        .map(|tx_with_meta| tx_with_meta.transaction_signature().to_string())
351                        .collect(),
352                ),
353            ),
354            TransactionDetails::None => (None, None),
355            TransactionDetails::Accounts => (
356                Some(
357                    self.transactions
358                        .into_iter()
359                        .map(|tx_with_meta| {
360                            tx_with_meta.build_json_accounts(
361                                options.max_supported_transaction_version,
362                                options.show_rewards,
363                            )
364                        })
365                        .collect::<Result<Vec<_>, _>>()?,
366                ),
367                None,
368            ),
369        };
370        Ok(UiConfirmedBlock {
371            previous_blockhash: self.previous_blockhash,
372            blockhash: self.blockhash,
373            parent_slot: self.parent_slot,
374            transactions,
375            signatures,
376            rewards: if options.show_rewards {
377                Some(self.rewards)
378            } else {
379                None
380            },
381            num_reward_partitions: self.num_partitions,
382            block_time: self.block_time,
383            block_height: self.block_height,
384        })
385    }
386}
387
388// Confirmed block with type guarantees that transaction metadata is always
389// present, as well as a list of the entry data needed to cryptographically
390// verify the block. Used for uploading to BigTable.
391pub struct VersionedConfirmedBlockWithEntries {
392    pub block: VersionedConfirmedBlock,
393    pub entries: Vec<EntrySummary>,
394}
395
396// Data needed to reconstruct an Entry, given an ordered list of transactions in
397// a block. Used for uploading to BigTable.
398pub struct EntrySummary {
399    pub num_hashes: u64,
400    pub hash: Hash,
401    pub num_transactions: u64,
402    pub starting_transaction_index: usize,
403}
404
405#[derive(Clone, Debug, PartialEq)]
406#[allow(clippy::large_enum_variant)]
407pub enum TransactionWithStatusMeta {
408    // Very old transactions may be missing metadata
409    MissingMetadata(Transaction),
410    // Versioned stored transaction always have metadata
411    Complete(VersionedTransactionWithStatusMeta),
412}
413
414#[derive(Clone, Debug, PartialEq)]
415pub struct VersionedTransactionWithStatusMeta {
416    pub transaction: VersionedTransaction,
417    pub meta: TransactionStatusMeta,
418}
419
420impl TransactionWithStatusMeta {
421    pub fn get_status_meta(&self) -> Option<TransactionStatusMeta> {
422        match self {
423            Self::MissingMetadata(_) => None,
424            Self::Complete(tx_with_meta) => Some(tx_with_meta.meta.clone()),
425        }
426    }
427
428    pub fn get_transaction(&self) -> VersionedTransaction {
429        match self {
430            Self::MissingMetadata(transaction) => VersionedTransaction::from(transaction.clone()),
431            Self::Complete(tx_with_meta) => tx_with_meta.transaction.clone(),
432        }
433    }
434
435    pub fn transaction_signature(&self) -> &Signature {
436        match self {
437            Self::MissingMetadata(transaction) => &transaction.signatures[0],
438            Self::Complete(VersionedTransactionWithStatusMeta { transaction, .. }) => {
439                &transaction.signatures[0]
440            }
441        }
442    }
443
444    pub fn encode(
445        self,
446        encoding: UiTransactionEncoding,
447        max_supported_transaction_version: Option<u8>,
448        show_rewards: bool,
449    ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
450        match self {
451            Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
452                version: None,
453                transaction: transaction.encode(encoding),
454                meta: None,
455            }),
456            Self::Complete(tx_with_meta) => {
457                tx_with_meta.encode(encoding, max_supported_transaction_version, show_rewards)
458            }
459        }
460    }
461
462    pub fn account_keys(&self) -> AccountKeys {
463        match self {
464            Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
465            Self::Complete(tx_with_meta) => tx_with_meta.account_keys(),
466        }
467    }
468
469    fn build_json_accounts(
470        self,
471        max_supported_transaction_version: Option<u8>,
472        show_rewards: bool,
473    ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
474        match self {
475            Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
476                version: None,
477                transaction: transaction.build_json_accounts(),
478                meta: None,
479            }),
480            Self::Complete(tx_with_meta) => {
481                tx_with_meta.build_json_accounts(max_supported_transaction_version, show_rewards)
482            }
483        }
484    }
485}
486
487impl VersionedTransactionWithStatusMeta {
488    fn validate_version(
489        &self,
490        max_supported_transaction_version: Option<u8>,
491    ) -> Result<Option<TransactionVersion>, EncodeError> {
492        match (
493            max_supported_transaction_version,
494            self.transaction.version(),
495        ) {
496            // Set to none because old clients can't handle this field
497            (None, TransactionVersion::LEGACY) => Ok(None),
498            (None, TransactionVersion::Number(version)) => {
499                Err(EncodeError::UnsupportedTransactionVersion(version))
500            }
501            (Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
502            (Some(max_version), TransactionVersion::Number(version)) => {
503                if version <= max_version {
504                    Ok(Some(TransactionVersion::Number(version)))
505                } else {
506                    Err(EncodeError::UnsupportedTransactionVersion(version))
507                }
508            }
509        }
510    }
511
512    pub fn encode(
513        self,
514        encoding: UiTransactionEncoding,
515        max_supported_transaction_version: Option<u8>,
516        show_rewards: bool,
517    ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
518        let version = self.validate_version(max_supported_transaction_version)?;
519
520        Ok(EncodedTransactionWithStatusMeta {
521            transaction: self.transaction.encode_with_meta(encoding, &self.meta),
522            meta: Some(match encoding {
523                UiTransactionEncoding::JsonParsed => parse_ui_transaction_status_meta(
524                    self.meta,
525                    self.transaction.message.static_account_keys(),
526                    show_rewards,
527                ),
528                _ => {
529                    let mut meta = UiTransactionStatusMeta::from(self.meta);
530                    if !show_rewards {
531                        meta.rewards = OptionSerializer::None;
532                    }
533                    meta
534                }
535            }),
536            version,
537        })
538    }
539
540    pub fn account_keys(&self) -> AccountKeys {
541        AccountKeys::new(
542            self.transaction.message.static_account_keys(),
543            Some(&self.meta.loaded_addresses),
544        )
545    }
546
547    fn build_json_accounts(
548        self,
549        max_supported_transaction_version: Option<u8>,
550        show_rewards: bool,
551    ) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
552        let version = self.validate_version(max_supported_transaction_version)?;
553        let reserved_account_keys = ReservedAccountKeys::new_all_activated();
554
555        let account_keys = match &self.transaction.message {
556            VersionedMessage::Legacy(message) => parse_legacy_message_accounts(message),
557            VersionedMessage::V0(message) => {
558                let loaded_message = LoadedMessage::new_borrowed(
559                    message,
560                    &self.meta.loaded_addresses,
561                    &reserved_account_keys.active,
562                );
563                parse_v0_message_accounts(&loaded_message)
564            }
565        };
566
567        Ok(EncodedTransactionWithStatusMeta {
568            transaction: EncodedTransaction::Accounts(UiAccountsList {
569                signatures: self
570                    .transaction
571                    .signatures
572                    .iter()
573                    .map(ToString::to_string)
574                    .collect(),
575                account_keys,
576            }),
577            meta: Some(build_simple_ui_transaction_status_meta(
578                self.meta,
579                show_rewards,
580            )),
581            version,
582        })
583    }
584}
585
586#[derive(Debug, Clone, PartialEq)]
587pub struct ConfirmedTransactionWithStatusMeta {
588    pub slot: Slot,
589    pub tx_with_meta: TransactionWithStatusMeta,
590    pub block_time: Option<UnixTimestamp>,
591}
592
593#[derive(Debug, Clone, PartialEq)]
594pub struct VersionedConfirmedTransactionWithStatusMeta {
595    pub slot: Slot,
596    pub tx_with_meta: VersionedTransactionWithStatusMeta,
597    pub block_time: Option<UnixTimestamp>,
598}
599
600impl ConfirmedTransactionWithStatusMeta {
601    pub fn encode(
602        self,
603        encoding: UiTransactionEncoding,
604        max_supported_transaction_version: Option<u8>,
605    ) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
606        Ok(EncodedConfirmedTransactionWithStatusMeta {
607            slot: self.slot,
608            transaction: self.tx_with_meta.encode(
609                encoding,
610                max_supported_transaction_version,
611                true,
612            )?,
613            block_time: self.block_time,
614        })
615    }
616
617    pub fn get_transaction(&self) -> VersionedTransaction {
618        self.tx_with_meta.get_transaction()
619    }
620}
621
622impl EncodableWithMeta for VersionedTransaction {
623    type Encoded = EncodedTransaction;
624    fn encode_with_meta(
625        &self,
626        encoding: UiTransactionEncoding,
627        meta: &TransactionStatusMeta,
628    ) -> Self::Encoded {
629        match encoding {
630            UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
631                bs58::encode(bincode::serialize(self).unwrap()).into_string(),
632            ),
633            UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
634                bs58::encode(bincode::serialize(self).unwrap()).into_string(),
635                TransactionBinaryEncoding::Base58,
636            ),
637            UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
638                BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
639                TransactionBinaryEncoding::Base64,
640            ),
641            UiTransactionEncoding::Json => self.json_encode(),
642            UiTransactionEncoding::JsonParsed => EncodedTransaction::Json(UiTransaction {
643                signatures: self.signatures.iter().map(ToString::to_string).collect(),
644                message: match &self.message {
645                    VersionedMessage::Legacy(message) => {
646                        message.encode(UiTransactionEncoding::JsonParsed)
647                    }
648                    VersionedMessage::V0(message) => {
649                        message.encode_with_meta(UiTransactionEncoding::JsonParsed, meta)
650                    }
651                },
652            }),
653        }
654    }
655    fn json_encode(&self) -> Self::Encoded {
656        EncodedTransaction::Json(UiTransaction {
657            signatures: self.signatures.iter().map(ToString::to_string).collect(),
658            message: match &self.message {
659                VersionedMessage::Legacy(message) => message.encode(UiTransactionEncoding::Json),
660                VersionedMessage::V0(message) => message.json_encode(),
661            },
662        })
663    }
664}
665
666impl Encodable for VersionedTransaction {
667    type Encoded = EncodedTransaction;
668    fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
669        match encoding {
670            UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
671                bs58::encode(bincode::serialize(self).unwrap()).into_string(),
672            ),
673            UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
674                bs58::encode(bincode::serialize(self).unwrap()).into_string(),
675                TransactionBinaryEncoding::Base58,
676            ),
677            UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
678                BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
679                TransactionBinaryEncoding::Base64,
680            ),
681            UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
682                EncodedTransaction::Json(UiTransaction {
683                    signatures: self.signatures.iter().map(ToString::to_string).collect(),
684                    message: match &self.message {
685                        VersionedMessage::Legacy(message) => {
686                            message.encode(UiTransactionEncoding::JsonParsed)
687                        }
688                        VersionedMessage::V0(message) => {
689                            message.encode(UiTransactionEncoding::JsonParsed)
690                        }
691                    },
692                })
693            }
694        }
695    }
696}
697
698impl Encodable for Transaction {
699    type Encoded = EncodedTransaction;
700    fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
701        match encoding {
702            UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
703                bs58::encode(bincode::serialize(self).unwrap()).into_string(),
704            ),
705            UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
706                bs58::encode(bincode::serialize(self).unwrap()).into_string(),
707                TransactionBinaryEncoding::Base58,
708            ),
709            UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
710                BASE64_STANDARD.encode(bincode::serialize(self).unwrap()),
711                TransactionBinaryEncoding::Base64,
712            ),
713            UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
714                EncodedTransaction::Json(UiTransaction {
715                    signatures: self.signatures.iter().map(ToString::to_string).collect(),
716                    message: self.message.encode(encoding),
717                })
718            }
719        }
720    }
721}
722
723impl JsonAccounts for Transaction {
724    type Encoded = EncodedTransaction;
725    fn build_json_accounts(&self) -> Self::Encoded {
726        EncodedTransaction::Accounts(UiAccountsList {
727            signatures: self.signatures.iter().map(ToString::to_string).collect(),
728            account_keys: parse_legacy_message_accounts(&self.message),
729        })
730    }
731}
732
733impl Encodable for Message {
734    type Encoded = UiMessage;
735    fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
736        if encoding == UiTransactionEncoding::JsonParsed {
737            let account_keys = AccountKeys::new(&self.account_keys, None);
738            UiMessage::Parsed(UiParsedMessage {
739                account_keys: parse_legacy_message_accounts(self),
740                recent_blockhash: self.recent_blockhash.to_string(),
741                instructions: self
742                    .instructions
743                    .iter()
744                    .map(|instruction| parse_ui_instruction(instruction, &account_keys, None))
745                    .collect(),
746                address_table_lookups: None,
747            })
748        } else {
749            UiMessage::Raw(UiRawMessage {
750                header: self.header,
751                account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
752                recent_blockhash: self.recent_blockhash.to_string(),
753                instructions: self
754                    .instructions
755                    .iter()
756                    .map(|ix| UiCompiledInstruction::from(ix, None))
757                    .collect(),
758                address_table_lookups: None,
759            })
760        }
761    }
762}
763
764impl Encodable for v0::Message {
765    type Encoded = UiMessage;
766    fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
767        if encoding == UiTransactionEncoding::JsonParsed {
768            let account_keys = AccountKeys::new(&self.account_keys, None);
769            let loaded_addresses = LoadedAddresses::default();
770            let loaded_message =
771                LoadedMessage::new_borrowed(self, &loaded_addresses, &HashSet::new());
772            UiMessage::Parsed(UiParsedMessage {
773                account_keys: parse_v0_message_accounts(&loaded_message),
774                recent_blockhash: self.recent_blockhash.to_string(),
775                instructions: self
776                    .instructions
777                    .iter()
778                    .map(|instruction| parse_ui_instruction(instruction, &account_keys, None))
779                    .collect(),
780                address_table_lookups: None,
781            })
782        } else {
783            UiMessage::Raw(UiRawMessage {
784                header: self.header,
785                account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
786                recent_blockhash: self.recent_blockhash.to_string(),
787                instructions: self
788                    .instructions
789                    .iter()
790                    .map(|ix| UiCompiledInstruction::from(ix, None))
791                    .collect(),
792                address_table_lookups: None,
793            })
794        }
795    }
796}
797
798impl EncodableWithMeta for v0::Message {
799    type Encoded = UiMessage;
800    fn encode_with_meta(
801        &self,
802        encoding: UiTransactionEncoding,
803        meta: &TransactionStatusMeta,
804    ) -> Self::Encoded {
805        if encoding == UiTransactionEncoding::JsonParsed {
806            let reserved_account_keys = ReservedAccountKeys::new_all_activated();
807            let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
808            let loaded_message = LoadedMessage::new_borrowed(
809                self,
810                &meta.loaded_addresses,
811                &reserved_account_keys.active,
812            );
813            UiMessage::Parsed(UiParsedMessage {
814                account_keys: parse_v0_message_accounts(&loaded_message),
815                recent_blockhash: self.recent_blockhash.to_string(),
816                instructions: self
817                    .instructions
818                    .iter()
819                    .map(|instruction| parse_ui_instruction(instruction, &account_keys, None))
820                    .collect(),
821                address_table_lookups: Some(
822                    self.address_table_lookups.iter().map(Into::into).collect(),
823                ),
824            })
825        } else {
826            self.json_encode()
827        }
828    }
829    fn json_encode(&self) -> Self::Encoded {
830        UiMessage::Raw(UiRawMessage {
831            header: self.header,
832            account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
833            recent_blockhash: self.recent_blockhash.to_string(),
834            instructions: self
835                .instructions
836                .iter()
837                .map(|ix| UiCompiledInstruction::from(ix, None))
838                .collect(),
839            address_table_lookups: Some(
840                self.address_table_lookups.iter().map(Into::into).collect(),
841            ),
842        })
843    }
844}
845
846// A serialized `Vec<TransactionByAddrInfo>` is stored in the `tx-by-addr` table.  The row keys are
847// the one's compliment of the slot so that rows may be listed in reverse order
848#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
849pub struct TransactionByAddrInfo {
850    pub signature: Signature,          // The transaction signature
851    pub err: Option<TransactionError>, // None if the transaction executed successfully
852    pub index: u32,                    // Where the transaction is located in the block
853    pub memo: Option<String>,          // Transaction memo
854    pub block_time: Option<UnixTimestamp>,
855}
856
857#[cfg(test)]
858mod test {
859    use super::*;
860
861    #[test]
862    fn test_ui_transaction_status_meta_ctors_serialization() {
863        let meta = TransactionStatusMeta {
864            status: Ok(()),
865            fee: 1234,
866            pre_balances: vec![1, 2, 3],
867            post_balances: vec![4, 5, 6],
868            inner_instructions: None,
869            log_messages: None,
870            pre_token_balances: None,
871            post_token_balances: None,
872            rewards: None,
873            loaded_addresses: LoadedAddresses {
874                writable: vec![],
875                readonly: vec![],
876            },
877            return_data: None,
878            compute_units_consumed: None,
879        };
880        let expected_json_output_value: serde_json::Value = serde_json::from_str(
881            "{\
882            \"err\":null,\
883            \"status\":{\"Ok\":null},\
884            \"fee\":1234,\
885            \"preBalances\":[1,2,3],\
886            \"postBalances\":[4,5,6],\
887            \"innerInstructions\":null,\
888            \"logMessages\":null,\
889            \"preTokenBalances\":null,\
890            \"postTokenBalances\":null,\
891            \"rewards\":null,\
892            \"loadedAddresses\":{\
893                \"readonly\": [],\
894                \"writable\": []\
895            }\
896        }",
897        )
898        .unwrap();
899        let ui_meta_from: UiTransactionStatusMeta = meta.clone().into();
900        assert_eq!(
901            serde_json::to_value(ui_meta_from).unwrap(),
902            expected_json_output_value
903        );
904
905        let expected_json_output_value: serde_json::Value = serde_json::from_str(
906            "{\
907            \"err\":null,\
908            \"status\":{\"Ok\":null},\
909            \"fee\":1234,\
910            \"preBalances\":[1,2,3],\
911            \"postBalances\":[4,5,6],\
912            \"innerInstructions\":null,\
913            \"logMessages\":null,\
914            \"preTokenBalances\":null,\
915            \"postTokenBalances\":null,\
916            \"rewards\":null\
917        }",
918        )
919        .unwrap();
920        let ui_meta_parse_with_rewards = parse_ui_transaction_status_meta(meta.clone(), &[], true);
921        assert_eq!(
922            serde_json::to_value(ui_meta_parse_with_rewards).unwrap(),
923            expected_json_output_value
924        );
925
926        let ui_meta_parse_no_rewards = parse_ui_transaction_status_meta(meta, &[], false);
927        assert_eq!(
928            serde_json::to_value(ui_meta_parse_no_rewards).unwrap(),
929            expected_json_output_value
930        );
931    }
932}