Skip to main content

solana_primitives/types/
message.rs

1use crate::types::{CompiledInstruction, MessageAddressTableLookup, Pubkey};
2use borsh::{BorshDeserialize, BorshSerialize};
3
4use serde::{Deserialize, Serialize};
5
6/// Serialize the common message body (header + account keys + blockhash + instructions).
7/// Shared by Legacy, Message, and V0 message types.
8fn serialize_message_body(
9    header: &MessageHeader,
10    account_keys: &[Pubkey],
11    recent_blockhash: &[u8; 32],
12    instructions: &[CompiledInstruction],
13) -> Result<Vec<u8>, String> {
14    let mut bytes = Vec::new();
15
16    // 1. Header (3 bytes)
17    bytes.push(header.num_required_signatures);
18    bytes.push(header.num_readonly_signed_accounts);
19    bytes.push(header.num_readonly_unsigned_accounts);
20
21    // 2. Account keys
22    let len = crate::encode_length_to_compact_u16_bytes(account_keys.len())?;
23    bytes.extend_from_slice(&len);
24    for pubkey in account_keys {
25        bytes.extend_from_slice(pubkey.as_bytes());
26    }
27
28    // 3. Recent blockhash (32 bytes)
29    bytes.extend_from_slice(recent_blockhash);
30
31    // 4. Instructions
32    let len = crate::encode_length_to_compact_u16_bytes(instructions.len())?;
33    bytes.extend_from_slice(&len);
34    for ix in instructions {
35        bytes.push(ix.program_id_index);
36
37        let len = crate::encode_length_to_compact_u16_bytes(ix.accounts.len())?;
38        bytes.extend_from_slice(&len);
39        bytes.extend_from_slice(&ix.accounts);
40
41        let len = crate::encode_length_to_compact_u16_bytes(ix.data.len())?;
42        bytes.extend_from_slice(&len);
43        bytes.extend_from_slice(&ix.data);
44    }
45
46    Ok(bytes)
47}
48
49/// The message header, identifying signed and read-only `account_keys`.
50#[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
51pub struct MessageHeader {
52    /// The number of signatures required for this message to be considered valid.
53    pub num_required_signatures: u8,
54    /// The last `num_readonly_signed_accounts` of the signed keys are read-only accounts.
55    pub num_readonly_signed_accounts: u8,
56    /// The last `num_readonly_unsigned_accounts` of the unsigned keys are read-only accounts.
57    pub num_readonly_unsigned_accounts: u8,
58}
59
60/// Legacy message format (pre-versioned transactions)
61#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
62pub struct LegacyMessage {
63    /// The message header, identifying signed and read-only `account_keys`.
64    pub header: MessageHeader,
65    /// List of account public keys
66    pub account_keys: Vec<Pubkey>,
67    /// The blockhash of a recent block.
68    pub recent_blockhash: [u8; 32],
69    /// Instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
70    pub instructions: Vec<CompiledInstruction>,
71}
72
73impl LegacyMessage {
74    pub fn serialize_for_signing(&self) -> Result<Vec<u8>, String> {
75        serialize_message_body(
76            &self.header,
77            &self.account_keys,
78            &self.recent_blockhash,
79            &self.instructions,
80        )
81    }
82}
83
84/// Versioned message format V0
85#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
86pub struct VersionedMessageV0 {
87    /// The message header, identifying signed and read-only `account_keys`.
88    pub header: MessageHeader,
89    /// List of account public keys
90    pub account_keys: Vec<Pubkey>,
91    /// The blockhash of a recent block.
92    pub recent_blockhash: [u8; 32],
93    /// Instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
94    pub instructions: Vec<CompiledInstruction>,
95    /// List of address lookup table references
96    pub address_table_lookups: Vec<MessageAddressTableLookup>,
97}
98
99impl VersionedMessageV0 {
100    /// Serialize the V0 message to wire bytes for signing.
101    ///
102    /// Format: `[0x80]` version prefix + header + account keys + blockhash + instructions + address table lookups
103    pub fn serialize_for_signing(&self) -> Result<Vec<u8>, String> {
104        let mut bytes = Vec::new();
105
106        // V0 version prefix
107        bytes.push(0x80);
108
109        // Message body (same as legacy)
110        let body = serialize_message_body(
111            &self.header,
112            &self.account_keys,
113            &self.recent_blockhash,
114            &self.instructions,
115        )?;
116        bytes.extend_from_slice(&body);
117
118        // Address table lookups
119        let lookup_len =
120            crate::encode_length_to_compact_u16_bytes(self.address_table_lookups.len())?;
121        bytes.extend_from_slice(&lookup_len);
122
123        for lookup in &self.address_table_lookups {
124            bytes.extend_from_slice(lookup.account_key.as_bytes());
125
126            let writable_len =
127                crate::encode_length_to_compact_u16_bytes(lookup.writable_indexes.len())?;
128            bytes.extend_from_slice(&writable_len);
129            bytes.extend_from_slice(&lookup.writable_indexes);
130
131            let readonly_len =
132                crate::encode_length_to_compact_u16_bytes(lookup.readonly_indexes.len())?;
133            bytes.extend_from_slice(&readonly_len);
134            bytes.extend_from_slice(&lookup.readonly_indexes);
135        }
136
137        Ok(bytes)
138    }
139}
140
141/// Versioned message format
142#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
143pub enum VersionedMessage {
144    /// Legacy message format (pre-versioned transactions)
145    Legacy(LegacyMessage),
146    /// Versioned message format V0
147    V0(VersionedMessageV0),
148}
149
150/// A Solana transaction message
151#[derive(Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
152pub struct Message {
153    /// The message header, identifying signed and read-only `account_keys`.
154    pub header: MessageHeader,
155    /// List of account public keys
156    pub account_keys: Vec<Pubkey>,
157    /// The blockhash of a recent block.
158    pub recent_blockhash: [u8; 32],
159    /// Instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
160    pub instructions: Vec<CompiledInstruction>,
161}
162
163impl Message {
164    /// Create a new message
165    pub fn new(
166        header: MessageHeader,
167        account_keys: Vec<Pubkey>,
168        recent_blockhash: [u8; 32],
169        instructions: Vec<CompiledInstruction>,
170    ) -> Self {
171        Self {
172            header,
173            account_keys,
174            recent_blockhash,
175            instructions,
176        }
177    }
178
179    /// Get the number of required signatures
180    pub fn num_required_signatures(&self) -> u8 {
181        self.header.num_required_signatures
182    }
183
184    /// Get the number of read-only signed accounts
185    pub fn num_readonly_signed_accounts(&self) -> u8 {
186        self.header.num_readonly_signed_accounts
187    }
188
189    /// Get the number of read-only unsigned accounts
190    pub fn num_readonly_unsigned_accounts(&self) -> u8 {
191        self.header.num_readonly_unsigned_accounts
192    }
193
194    /// Serializes the message into the byte format required for signing
195    /// and for the legacy transaction wire format.
196    pub fn serialize_for_signing(&self) -> Result<Vec<u8>, String> {
197        serialize_message_body(
198            &self.header,
199            &self.account_keys,
200            &self.recent_blockhash,
201            &self.instructions,
202        )
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::types::{CompiledInstruction, Pubkey};
210
211    #[test]
212    fn test_message() {
213        let header = MessageHeader {
214            num_required_signatures: 1,
215            num_readonly_signed_accounts: 0,
216            num_readonly_unsigned_accounts: 1,
217        };
218        let account_keys = vec![Pubkey::new([0; 32]), Pubkey::new([1; 32])];
219        let recent_blockhash = [0u8; 32];
220        let instructions = vec![CompiledInstruction {
221            program_id_index: 1,
222            accounts: vec![0],
223            data: vec![],
224        }];
225
226        let message = Message::new(header, account_keys, recent_blockhash, instructions);
227
228        assert_eq!(message.num_required_signatures(), 1);
229        assert_eq!(message.num_readonly_signed_accounts(), 0);
230        assert_eq!(message.num_readonly_unsigned_accounts(), 1);
231    }
232
233    #[test]
234    fn test_versioned_message() {
235        let header = MessageHeader {
236            num_required_signatures: 1,
237            num_readonly_signed_accounts: 0,
238            num_readonly_unsigned_accounts: 1,
239        };
240        let account_keys = vec![Pubkey::new([0; 32]), Pubkey::new([1; 32])];
241        let recent_blockhash = [0u8; 32];
242        let instructions = vec![CompiledInstruction {
243            program_id_index: 1,
244            accounts: vec![0],
245            data: vec![],
246        }];
247
248        // Create a V0 message with address table lookups
249        let address_table_lookups = vec![MessageAddressTableLookup::new(
250            Pubkey::new([2; 32]),
251            vec![0, 1], // writable indexes
252            vec![2],    // readonly indexes
253        )];
254
255        let v0_message = VersionedMessageV0 {
256            header: header.clone(),
257            account_keys: account_keys.clone(),
258            recent_blockhash,
259            instructions: instructions.clone(),
260            address_table_lookups,
261        };
262
263        // Create a versioned message
264        let versioned_message = VersionedMessage::V0(v0_message);
265
266        // Verify the contents
267        match versioned_message {
268            VersionedMessage::Legacy(_) => panic!("Expected V0 message"),
269            VersionedMessage::V0(msg) => {
270                assert_eq!(msg.header.num_required_signatures, 1);
271                assert_eq!(msg.account_keys.len(), 2);
272                assert_eq!(msg.instructions.len(), 1);
273                assert_eq!(msg.address_table_lookups.len(), 1);
274                assert_eq!(
275                    msg.address_table_lookups[0].account_key,
276                    Pubkey::new([2; 32])
277                );
278                assert_eq!(msg.address_table_lookups[0].writable_indexes, vec![0, 1]);
279                assert_eq!(msg.address_table_lookups[0].readonly_indexes, vec![2]);
280            }
281        }
282    }
283}