solana_transaction_status/
lib.rs

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