solana_transaction_status/
lib.rs

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