solana_block_decoder/transaction/
transaction.rs

1use {
2    crate::{
3        errors::{
4            decode_error::DecodeError,
5        },
6        block::{
7            encoded_block::EncodedTransaction,
8        },
9        message::{
10            message::Message,
11        },
12        decodable::{
13            Decodable,
14        },
15    },
16    serde_derive::{Deserialize, Serialize},
17    solana_short_vec as short_vec,
18    solana_signature::{
19        ParseSignatureError,
20        Signature,
21    },
22    solana_transaction_status_client_types::{
23        TransactionBinaryEncoding,
24        // UiTransaction,
25    },
26    std::{
27        str::FromStr,
28    },
29    base64::{Engine, prelude::BASE64_STANDARD},
30};
31
32#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)]
33pub struct Transaction {
34    /// A set of signatures of a serialized [`Message`], signed by the first
35    /// keys of the `Message`'s [`account_keys`], where the number of signatures
36    /// is equal to [`num_required_signatures`] of the `Message`'s
37    /// [`MessageHeader`].
38    ///
39    /// [`account_keys`]: Message::account_keys
40    /// [`MessageHeader`]: crate::message::MessageHeader
41    /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures
42    // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
43    #[serde(with = "short_vec")]
44    pub signatures: Vec<Signature>,
45
46    /// The message to sign.
47    pub message: Message,
48}
49
50impl Decodable for Transaction {
51    type Encoded = EncodedTransaction;
52    type Decoded = Transaction;
53
54    fn decode(encoded: &Self::Encoded) -> Result<Self::Decoded, DecodeError> {
55        match encoded {
56            EncodedTransaction::LegacyBinary(s) | EncodedTransaction::Binary(s, TransactionBinaryEncoding::Base58) => {
57                let data = bs58::decode(s)
58                    .into_vec()
59                    .map_err(|_| DecodeError::DeserializeFailed)?;
60                let transaction: Transaction = bincode::deserialize(&data)
61                    .map_err(|_| DecodeError::DeserializeFailed)?;
62                Ok(transaction)
63            }
64            EncodedTransaction::Binary(s, TransactionBinaryEncoding::Base64) => {
65                let data = BASE64_STANDARD.decode(s)
66                    .map_err(|_| DecodeError::DeserializeFailed)?;
67                let transaction: Transaction = bincode::deserialize(&data)
68                    .map_err(|_| DecodeError::DeserializeFailed)?;
69                Ok(transaction)
70            }
71            EncodedTransaction::Json(ui_transaction) => {
72                let message = Message::decode(&ui_transaction.message)?;
73                let signatures: Result<Vec<Signature>, ParseSignatureError> = ui_transaction.signatures.iter()
74                    .map(|s| Signature::from_str(s))
75                    .collect();
76                let signatures = match signatures {
77                    Ok(signatures) => signatures,
78                    Err(error) => return Err(DecodeError::ParseSignatureFailed(error)),
79                };
80                Ok(Transaction {
81                    signatures,
82                    message,
83                })
84            }
85            EncodedTransaction::Accounts(_) => {
86                Err(DecodeError::UnsupportedEncoding)
87            }
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use bincode;
96    use bs58;
97    use base64::{Engine, prelude::BASE64_STANDARD};
98    use solana_transaction_status::{
99        TransactionBinaryEncoding, UiTransaction, UiMessage, UiRawMessage, UiAccountsList, UiCompiledInstruction, UiAddressTableLookup,
100    };
101    // use solana_sdk::message::MessageHeader;
102    use solana_message::{
103        MessageHeader,
104    };
105    use crate::errors::decode_error::DecodeError;
106    use crate::block::encoded_block::EncodedTransaction;
107    use crate::message::message::Message;
108    use std::str::FromStr;
109    use solana_signature::Signature;
110
111    fn create_sample_message() -> Message {
112        Message::default()
113    }
114
115    fn create_sample_ui_message() -> UiMessage {
116        UiMessage::Raw(UiRawMessage {
117            header: MessageHeader {
118                num_required_signatures: 1,
119                num_readonly_signed_accounts: 0,
120                num_readonly_unsigned_accounts: 0,
121            },
122            account_keys: vec!["11111111111111111111111111111111".to_string()],
123            recent_blockhash: "11111111111111111111111111111111".to_string(),
124            instructions: vec![],
125            address_table_lookups: None,
126        })
127    }
128
129    fn create_sample_transaction() -> Transaction {
130        Transaction {
131            signatures: vec![Signature::default()],
132            message: create_sample_message(),
133        }
134    }
135
136    #[test]
137    fn test_decode_legacy_binary_success() {
138        let transaction = create_sample_transaction();
139        let serialized = bincode::serialize(&transaction).expect("Failed to serialize transaction");
140        let encoded = bs58::encode(&serialized).into_string();
141        let encoded_transaction = EncodedTransaction::LegacyBinary(encoded);
142        let decoded = Transaction::decode(&encoded_transaction);
143        assert!(decoded.is_ok());
144        assert_eq!(decoded.unwrap(), transaction);
145    }
146
147    #[test]
148    fn test_decode_base58_binary_success() {
149        let transaction = create_sample_transaction();
150        let serialized = bincode::serialize(&transaction).expect("Failed to serialize transaction");
151        let encoded = bs58::encode(&serialized).into_string();
152        let encoded_transaction = EncodedTransaction::Binary(encoded, TransactionBinaryEncoding::Base58);
153        let decoded = Transaction::decode(&encoded_transaction);
154        assert!(decoded.is_ok());
155        assert_eq!(decoded.unwrap(), transaction);
156    }
157
158    #[test]
159    fn test_decode_base64_binary_success() {
160        let transaction = create_sample_transaction();
161        let serialized = bincode::serialize(&transaction).expect("Failed to serialize transaction");
162        let encoded = BASE64_STANDARD.encode(&serialized);
163        let encoded_transaction = EncodedTransaction::Binary(encoded, TransactionBinaryEncoding::Base64);
164        let decoded = Transaction::decode(&encoded_transaction);
165        assert!(decoded.is_ok());
166        assert_eq!(decoded.unwrap(), transaction);
167    }
168
169    #[test]
170    fn test_decode_invalid_base58_binary_fails() {
171        let encoded_transaction = EncodedTransaction::Binary("invalidbase58".to_string(), TransactionBinaryEncoding::Base58);
172        let decoded = Transaction::decode(&encoded_transaction);
173        assert!(decoded.is_err());
174        assert_eq!(decoded.unwrap_err(), DecodeError::DeserializeFailed);
175    }
176
177    #[test]
178    fn test_decode_invalid_base64_binary_fails() {
179        let encoded_transaction = EncodedTransaction::Binary("invalidbase64==".to_string(), TransactionBinaryEncoding::Base64);
180        let decoded = Transaction::decode(&encoded_transaction);
181        assert!(decoded.is_err());
182        assert_eq!(decoded.unwrap_err(), DecodeError::DeserializeFailed);
183    }
184
185    #[test]
186    fn test_decode_json_success() {
187        let message = create_sample_ui_message();
188        let ui_transaction = UiTransaction {
189            signatures: vec![Signature::default().to_string()],
190            message: message.clone(),
191        };
192        let encoded_transaction = EncodedTransaction::Json(ui_transaction);
193        let decoded = Transaction::decode(&encoded_transaction);
194        assert!(decoded.is_ok(), "Transaction decoding should succeed, got {:?}", decoded);
195        if let Ok(result) = decoded {
196            assert_eq!(result.signatures.len(), 1);
197        }
198    }
199
200    #[test]
201    fn test_decode_json_invalid_signature_fails() {
202        let message = create_sample_ui_message();
203        let ui_transaction = UiTransaction {
204            signatures: vec!["invalid_signature".to_string()],
205            message,
206        };
207        let encoded_transaction = EncodedTransaction::Json(ui_transaction);
208        let decoded = Transaction::decode(&encoded_transaction);
209        assert!(matches!(decoded, Err(DecodeError::ParseSignatureFailed(_)) | Err(DecodeError::UnsupportedEncoding)),
210                "Expected ParseSignatureFailed or UnsupportedEncoding error, got {:?}", decoded);
211    }
212
213    #[test]
214    fn test_decode_accounts_encoding_fails() {
215        let encoded_transaction = EncodedTransaction::Accounts(UiAccountsList { account_keys: vec![], signatures: vec![] });
216        let decoded = Transaction::decode(&encoded_transaction);
217        assert!(decoded.is_err());
218        assert_eq!(decoded.unwrap_err(), DecodeError::UnsupportedEncoding);
219    }
220
221    #[test]
222    fn test_decode_empty_base58_fails() {
223        let encoded_transaction = EncodedTransaction::Binary("".to_string(), TransactionBinaryEncoding::Base58);
224        let decoded = Transaction::decode(&encoded_transaction);
225        assert!(decoded.is_err());
226    }
227
228    #[test]
229    fn test_decode_empty_base64_fails() {
230        let encoded_transaction = EncodedTransaction::Binary("".to_string(), TransactionBinaryEncoding::Base64);
231        let decoded = Transaction::decode(&encoded_transaction);
232        assert!(decoded.is_err());
233    }
234}