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