solana_block_decoder/message/
message.rs

1use {
2    crate::{
3        errors::{
4            decode_error::DecodeError,
5        },
6        instruction::{
7            CompiledInstruction,
8        },
9        decodable::{
10            Decodable,
11        },
12    },
13    serde_derive::{Deserialize, Serialize},
14    solana_short_vec as short_vec,
15    solana_hash::{
16        Hash,
17    },
18    solana_message::{
19        Message as SolanaMessage,
20        MessageHeader,
21    },
22    solana_pubkey::Pubkey,
23    solana_transaction_status_client_types::{
24        UiMessage,
25    },
26    std::{
27        str::FromStr,
28    },
29};
30
31#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
32#[serde(rename_all = "camelCase")]
33pub struct Message {
34    /// The message header, identifying signed and read-only `account_keys`.
35    // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
36    pub header: MessageHeader,
37
38    /// All the account keys used by this transaction.
39    #[serde(with = "short_vec")]
40    pub account_keys: Vec<Pubkey>,
41
42    /// The id of a recent ledger entry.
43    pub recent_blockhash: Hash,
44
45    /// Programs that will be executed in sequence and committed in one atomic transaction if all
46    /// succeed.
47    #[serde(with = "short_vec")]
48    pub instructions: Vec<CompiledInstruction>,
49}
50
51impl Decodable for Message {
52    type Encoded = UiMessage;
53    type Decoded = Message;
54
55    fn decode(encoded: &Self::Encoded) -> Result<Self::Decoded, DecodeError> {
56        match encoded {
57            UiMessage::Raw(raw_message) => {
58                let header = raw_message.header;
59                let account_keys: Result<Vec<Pubkey>, _> = raw_message
60                    .account_keys
61                    .iter()
62                    .map(|key_str| key_str.parse())
63                    .collect();
64                let account_keys = account_keys?;
65                let recent_blockhash = Hash::from_str(&raw_message.recent_blockhash)
66                    .map_err(|err| DecodeError::ParseHashFailed(err))?;
67                let instructions: Vec<CompiledInstruction> = raw_message
68                    .instructions
69                    .iter()
70                    // .map(|ui_instruction| (*ui_instruction).into() )
71                    .map(|ui_instruction| ui_instruction.clone().into() )
72                    .collect();
73
74                Ok(Message {
75                    header,
76                    account_keys,
77                    recent_blockhash,
78                    instructions,
79                })
80            }
81            UiMessage::Parsed(_) => {
82                Err(DecodeError::UnsupportedEncoding)
83            }
84        }
85    }
86}
87
88
89impl From<Message> for SolanaMessage {
90    fn from(msg: Message) -> Self {
91        Self {
92            header: msg.header,
93            account_keys: msg.account_keys,
94            recent_blockhash: msg.recent_blockhash,
95            instructions: msg.instructions.into_iter().map(Into::into).collect(),
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use solana_transaction_status_client_types::{UiCompiledInstruction, UiMessage, UiRawMessage, UiParsedMessage};
104    use solana_hash::Hash;
105    use solana_pubkey::Pubkey;
106    use std::str::FromStr;
107    use serde_json;
108    use bs58;
109
110    fn sample_ui_message() -> UiMessage {
111        UiMessage::Raw(UiRawMessage {
112            header: MessageHeader {
113                num_required_signatures: 1,
114                num_readonly_signed_accounts: 0,
115                num_readonly_unsigned_accounts: 1,
116            },
117            account_keys: vec![Pubkey::new_unique().to_string()],
118            recent_blockhash: Hash::new_unique().to_string(),
119            instructions: vec![UiCompiledInstruction {
120                program_id_index: 0,
121                accounts: vec![0],
122                data: bs58::encode(b"test_data").into_string(),
123                stack_height: None,
124            }],
125            address_table_lookups: None,
126        })
127    }
128
129    #[test]
130    fn test_message_decoding_valid() {
131        let ui_message = sample_ui_message();
132        let decoded_message = Message::decode(&ui_message);
133        assert!(decoded_message.is_ok(), "Expected decoding to succeed");
134    }
135
136    #[test]
137    fn test_message_decoding_invalid_pubkey() {
138        let mut ui_message = sample_ui_message();
139        if let UiMessage::Raw(ref mut raw_message) = ui_message {
140            raw_message.account_keys[0] = "invalid_pubkey".to_string();
141        }
142
143        let decoded_message = Message::decode(&ui_message);
144        assert!(decoded_message.is_err(), "Expected decoding to fail due to invalid pubkey");
145    }
146
147    #[test]
148    fn test_message_decoding_invalid_hash() {
149        let mut ui_message = sample_ui_message();
150        if let UiMessage::Raw(ref mut raw_message) = ui_message {
151            raw_message.recent_blockhash = "invalid_hash".to_string();
152        }
153
154        let decoded_message = Message::decode(&ui_message);
155        assert!(decoded_message.is_err(), "Expected decoding to fail due to invalid hash");
156    }
157
158    // #[test]
159    // fn test_message_deserialization() {
160    //     let message_json = r#"{
161    //         \"header\": { \"num_required_signatures\": 1, \"num_readonly_signed_accounts\": 0, \"num_readonly_unsigned_accounts\": 1 },
162    //         \"account_keys\": [\"11111111111111111111111111111111\"],
163    //         \"recent_blockhash\": \"11111111111111111111111111111111\",
164    //         \"instructions\": [
165    //             { \"program_id_index\": 0, \"accounts\": [0], \"data\": \"3q2+7w==\" }
166    //         ]
167    //     }"#;
168    //
169    //     let message: Result<Message, serde_json::Error> = serde_json::from_str(message_json);
170    //     assert!(message.is_ok(), "Message deserialization failed: {:?}", message.err());
171    //     let message = message.unwrap();
172    //
173    //     assert_eq!(message.header.num_required_signatures, 1);
174    //     assert_eq!(message.header.num_readonly_signed_accounts, 0);
175    //     assert_eq!(message.header.num_readonly_unsigned_accounts, 1);
176    //     assert_eq!(message.account_keys.len(), 1);
177    //     assert_eq!(message.instructions.len(), 1);
178    // }
179
180    #[test]
181    fn test_message_decoding_unsupported_encoding() {
182        let ui_message = UiMessage::Parsed(UiParsedMessage {
183            account_keys: vec![],
184            recent_blockhash: "".to_string(),
185            instructions: vec![],
186            address_table_lookups: None,
187        });
188        let decoded_message = Message::decode(&ui_message);
189        assert!(matches!(decoded_message, Err(DecodeError::UnsupportedEncoding)));
190    }
191
192    #[test]
193    fn test_message_conversion_to_solana_message() {
194        let ui_message = sample_ui_message();
195        let decoded_message = Message::decode(&ui_message).expect("Decoding failed");
196        let solana_message: SolanaMessage = decoded_message.clone().into();
197
198        assert_eq!(decoded_message.header, solana_message.header);
199        assert_eq!(decoded_message.account_keys, solana_message.account_keys);
200        assert_eq!(decoded_message.recent_blockhash, solana_message.recent_blockhash);
201        assert_eq!(decoded_message.instructions.len(), solana_message.instructions.len());
202    }
203
204    #[test]
205    fn test_message_with_multiple_account_keys() {
206        let mut ui_message = sample_ui_message();
207        if let UiMessage::Raw(ref mut raw_message) = ui_message {
208            raw_message.account_keys.push(Pubkey::new_unique().to_string());
209        }
210        let decoded_message = Message::decode(&ui_message);
211        assert!(decoded_message.is_ok(), "Expected decoding to succeed with multiple account keys");
212        assert_eq!(decoded_message.unwrap().account_keys.len(), 2);
213    }
214}