solana_block_decoder/block/
encoded_block.rs

1use {
2    solana_clock::{
3        Slot,
4        UnixTimestamp,
5    },
6    solana_transaction::{
7        versioned::{
8            TransactionVersion,
9            VersionedTransaction,
10        },
11    },
12    solana_transaction_status_client_types::{
13        TransactionBinaryEncoding,
14        Rewards,
15        UiTransaction,
16        UiAccountsList,
17        UiTransactionStatusMeta,
18    },
19    serde_derive::{Serialize,Deserialize},
20    base64::{Engine, prelude::BASE64_STANDARD},
21};
22
23#[derive(Debug, PartialEq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct EncodedConfirmedBlock {
26    pub previous_blockhash: String,
27    pub blockhash: String,
28    pub parent_slot: Slot,
29    pub transactions: Vec<EncodedTransactionWithStatusMeta>,
30    pub rewards: Rewards,
31    pub num_partitions: Option<u64>,
32    pub block_time: Option<UnixTimestamp>,
33    pub block_height: Option<u64>,
34}
35
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct EncodedTransactionWithStatusMeta {
39    pub transaction: EncodedTransaction,
40    pub meta: Option<UiTransactionStatusMeta>,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub version: Option<TransactionVersion>,
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase", untagged)]
47pub enum EncodedTransaction {
48    LegacyBinary(String), // Old way of expressing base-58, retained for RPC backwards compatibility
49    Binary(String, TransactionBinaryEncoding),
50    Json(UiTransaction),
51    Accounts(UiAccountsList),
52}
53
54impl EncodedTransaction {
55    pub fn decode(&self) -> Option<VersionedTransaction> {
56        let (blob, encoding) = match self {
57            Self::Json(_) | Self::Accounts(_) => return None,
58            Self::LegacyBinary(blob) => (blob, TransactionBinaryEncoding::Base58),
59            Self::Binary(blob, encoding) => (blob, *encoding),
60        };
61
62        let transaction: Option<VersionedTransaction> = match encoding {
63            TransactionBinaryEncoding::Base58 => bs58::decode(blob)
64                .into_vec()
65                .ok()
66                .and_then(|bytes| bincode::deserialize(&bytes).ok()),
67            TransactionBinaryEncoding::Base64 => BASE64_STANDARD.decode(blob)
68                .ok()
69                .and_then(|bytes| bincode::deserialize(&bytes).ok()),
70        };
71
72        transaction.filter(|transaction| {
73            transaction
74                .sanitize(
75                    // true, // require_static_program_ids
76                )
77                .is_ok()
78        })
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use {super::*, serde_json::json};
85
86    #[test]
87    fn test_decode_invalid_transaction() {
88        // This transaction will not pass sanitization
89        let unsanitary_transaction = EncodedTransaction::Binary(
90            "ju9xZWuDBX4pRxX2oZkTjxU5jB4SSTgEGhX8bQ8PURNzyzqKMPPpNvWihx8zUe\
91             FfrbVNoAaEsNKZvGzAnTDy5bhNT9kt6KFCTBixpvrLCzg4M5UdFUQYrn1gdgjX\
92             pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
93             hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
94                .to_string(),
95            TransactionBinaryEncoding::Base58,
96        );
97        assert!(unsanitary_transaction.decode().is_none());
98    }
99
100}