1#[cfg(feature = "frozen-abi")]
2use solana_frozen_abi_macro::{frozen_abi, AbiEnumVisitor, AbiExample};
3use {
4 crate::{
5 compiled_instruction::CompiledInstruction, legacy::Message as LegacyMessage,
6 v0::MessageAddressTableLookup, MessageHeader,
7 },
8 solana_address::Address,
9 solana_hash::Hash,
10 solana_sanitize::{Sanitize, SanitizeError},
11 std::collections::HashSet,
12};
13#[cfg(feature = "wincode")]
14use {
15 crate::{
16 legacy::MessageUninitBuilder as LegacyMessageUninitBuilder, MessageHeaderUninitBuilder,
17 },
18 core::mem::MaybeUninit,
19 wincode::{
20 io::{Reader, Writer},
21 ReadResult, SchemaRead, SchemaWrite, WriteResult,
22 },
23};
24#[cfg(feature = "serde")]
25use {
26 serde::{
27 de::{self, Deserializer, SeqAccess, Unexpected, Visitor},
28 ser::{SerializeTuple, Serializer},
29 },
30 serde_derive::{Deserialize, Serialize},
31 std::fmt,
32};
33
34mod sanitized;
35pub mod v0;
36
37pub use sanitized::*;
38
39pub const MESSAGE_VERSION_PREFIX: u8 = 0x80;
41
42#[cfg_attr(
51 feature = "frozen-abi",
52 frozen_abi(digest = "Hndd1SDxQ5qNZvzHo77dpW6uD5c1DJNVjtg8tE6hc432"),
53 derive(AbiEnumVisitor, AbiExample)
54)]
55#[derive(Debug, PartialEq, Eq, Clone)]
56pub enum VersionedMessage {
57 Legacy(LegacyMessage),
58 V0(v0::Message),
59}
60
61impl VersionedMessage {
62 pub fn sanitize(&self) -> Result<(), SanitizeError> {
63 match self {
64 Self::Legacy(message) => message.sanitize(),
65 Self::V0(message) => message.sanitize(),
66 }
67 }
68
69 pub fn header(&self) -> &MessageHeader {
70 match self {
71 Self::Legacy(message) => &message.header,
72 Self::V0(message) => &message.header,
73 }
74 }
75
76 pub fn static_account_keys(&self) -> &[Address] {
77 match self {
78 Self::Legacy(message) => &message.account_keys,
79 Self::V0(message) => &message.account_keys,
80 }
81 }
82
83 pub fn address_table_lookups(&self) -> Option<&[MessageAddressTableLookup]> {
84 match self {
85 Self::Legacy(_) => None,
86 Self::V0(message) => Some(&message.address_table_lookups),
87 }
88 }
89
90 pub fn is_signer(&self, index: usize) -> bool {
93 index < usize::from(self.header().num_required_signatures)
94 }
95
96 pub fn is_maybe_writable(
101 &self,
102 index: usize,
103 reserved_account_keys: Option<&HashSet<Address>>,
104 ) -> bool {
105 match self {
106 Self::Legacy(message) => message.is_maybe_writable(index, reserved_account_keys),
107 Self::V0(message) => message.is_maybe_writable(index, reserved_account_keys),
108 }
109 }
110
111 fn is_instruction_account(&self, key_index: usize) -> bool {
114 if let Ok(key_index) = u8::try_from(key_index) {
115 self.instructions()
116 .iter()
117 .any(|ix| ix.accounts.contains(&key_index))
118 } else {
119 false
120 }
121 }
122
123 pub fn is_invoked(&self, key_index: usize) -> bool {
124 match self {
125 Self::Legacy(message) => message.is_key_called_as_program(key_index),
126 Self::V0(message) => message.is_key_called_as_program(key_index),
127 }
128 }
129
130 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
133 !self.is_invoked(key_index) || self.is_instruction_account(key_index)
134 }
135
136 pub fn recent_blockhash(&self) -> &Hash {
137 match self {
138 Self::Legacy(message) => &message.recent_blockhash,
139 Self::V0(message) => &message.recent_blockhash,
140 }
141 }
142
143 pub fn set_recent_blockhash(&mut self, recent_blockhash: Hash) {
144 match self {
145 Self::Legacy(message) => message.recent_blockhash = recent_blockhash,
146 Self::V0(message) => message.recent_blockhash = recent_blockhash,
147 }
148 }
149
150 pub fn instructions(&self) -> &[CompiledInstruction] {
153 match self {
154 Self::Legacy(message) => &message.instructions,
155 Self::V0(message) => &message.instructions,
156 }
157 }
158
159 #[cfg(feature = "bincode")]
160 pub fn serialize(&self) -> Vec<u8> {
161 bincode::serialize(self).unwrap()
162 }
163
164 #[cfg(all(feature = "bincode", feature = "blake3"))]
165 pub fn hash(&self) -> Hash {
167 let message_bytes = self.serialize();
168 Self::hash_raw_message(&message_bytes)
169 }
170
171 #[cfg(feature = "blake3")]
172 pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
174 use blake3::traits::digest::Digest;
175 let mut hasher = blake3::Hasher::new();
176 hasher.update(b"solana-tx-message-v1");
177 hasher.update(message_bytes);
178 let hash_bytes: [u8; solana_hash::HASH_BYTES] = hasher.finalize().into();
179 hash_bytes.into()
180 }
181}
182
183impl Default for VersionedMessage {
184 fn default() -> Self {
185 Self::Legacy(LegacyMessage::default())
186 }
187}
188
189#[cfg(feature = "serde")]
190impl serde::Serialize for VersionedMessage {
191 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
192 where
193 S: Serializer,
194 {
195 match self {
196 Self::Legacy(message) => {
197 let mut seq = serializer.serialize_tuple(1)?;
198 seq.serialize_element(message)?;
199 seq.end()
200 }
201 Self::V0(message) => {
202 let mut seq = serializer.serialize_tuple(2)?;
203 seq.serialize_element(&MESSAGE_VERSION_PREFIX)?;
204 seq.serialize_element(message)?;
205 seq.end()
206 }
207 }
208 }
209}
210
211#[cfg(feature = "serde")]
212enum MessagePrefix {
213 Legacy(u8),
214 Versioned(u8),
215}
216
217#[cfg(feature = "serde")]
218impl<'de> serde::Deserialize<'de> for MessagePrefix {
219 fn deserialize<D>(deserializer: D) -> Result<MessagePrefix, D::Error>
220 where
221 D: Deserializer<'de>,
222 {
223 struct PrefixVisitor;
224
225 impl Visitor<'_> for PrefixVisitor {
226 type Value = MessagePrefix;
227
228 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
229 formatter.write_str("message prefix byte")
230 }
231
232 fn visit_u64<E: de::Error>(self, value: u64) -> Result<MessagePrefix, E> {
237 if value > u8::MAX as u64 {
238 Err(de::Error::invalid_type(Unexpected::Unsigned(value), &self))?;
239 }
240
241 let byte = value as u8;
242 if byte & MESSAGE_VERSION_PREFIX != 0 {
243 Ok(MessagePrefix::Versioned(byte & !MESSAGE_VERSION_PREFIX))
244 } else {
245 Ok(MessagePrefix::Legacy(byte))
246 }
247 }
248 }
249
250 deserializer.deserialize_u8(PrefixVisitor)
251 }
252}
253
254#[cfg(feature = "serde")]
255impl<'de> serde::Deserialize<'de> for VersionedMessage {
256 fn deserialize<D>(deserializer: D) -> Result<VersionedMessage, D::Error>
257 where
258 D: Deserializer<'de>,
259 {
260 struct MessageVisitor;
261
262 impl<'de> Visitor<'de> for MessageVisitor {
263 type Value = VersionedMessage;
264
265 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
266 formatter.write_str("message bytes")
267 }
268
269 fn visit_seq<A>(self, mut seq: A) -> Result<VersionedMessage, A::Error>
270 where
271 A: SeqAccess<'de>,
272 {
273 let prefix: MessagePrefix = seq
274 .next_element()?
275 .ok_or_else(|| de::Error::invalid_length(0, &self))?;
276
277 match prefix {
278 MessagePrefix::Legacy(num_required_signatures) => {
279 #[derive(Serialize, Deserialize)]
281 struct RemainingLegacyMessage {
282 pub num_readonly_signed_accounts: u8,
283 pub num_readonly_unsigned_accounts: u8,
284 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
285 pub account_keys: Vec<Address>,
286 pub recent_blockhash: Hash,
287 #[cfg_attr(feature = "serde", serde(with = "solana_short_vec"))]
288 pub instructions: Vec<CompiledInstruction>,
289 }
290
291 let message: RemainingLegacyMessage =
292 seq.next_element()?.ok_or_else(|| {
293 de::Error::invalid_length(1, &self)
295 })?;
296
297 Ok(VersionedMessage::Legacy(LegacyMessage {
298 header: MessageHeader {
299 num_required_signatures,
300 num_readonly_signed_accounts: message.num_readonly_signed_accounts,
301 num_readonly_unsigned_accounts: message
302 .num_readonly_unsigned_accounts,
303 },
304 account_keys: message.account_keys,
305 recent_blockhash: message.recent_blockhash,
306 instructions: message.instructions,
307 }))
308 }
309 MessagePrefix::Versioned(version) => {
310 match version {
311 0 => {
312 Ok(VersionedMessage::V0(seq.next_element()?.ok_or_else(
313 || {
314 de::Error::invalid_length(1, &self)
316 },
317 )?))
318 }
319 127 => {
320 Err(de::Error::custom("off-chain messages are not accepted"))
325 }
326 _ => Err(de::Error::invalid_value(
327 de::Unexpected::Unsigned(version as u64),
328 &"a valid transaction message version",
329 )),
330 }
331 }
332 }
333 }
334 }
335
336 deserializer.deserialize_tuple(2, MessageVisitor)
337 }
338}
339
340#[cfg(feature = "wincode")]
341impl SchemaWrite for VersionedMessage {
342 type Src = Self;
343
344 #[inline(always)]
345 fn size_of(src: &Self::Src) -> WriteResult<usize> {
346 match src {
347 VersionedMessage::Legacy(message) => LegacyMessage::size_of(message),
348 #[expect(clippy::arithmetic_side_effects)]
350 VersionedMessage::V0(message) => Ok(1 + v0::Message::size_of(message)?),
351 }
352 }
353
354 #[inline(always)]
355 fn write(writer: &mut impl Writer, src: &Self::Src) -> WriteResult<()> {
356 match src {
357 VersionedMessage::Legacy(message) => LegacyMessage::write(writer, message),
358 VersionedMessage::V0(message) => {
359 u8::write(writer, &MESSAGE_VERSION_PREFIX)?;
360 v0::Message::write(writer, message)
361 }
362 }
363 }
364}
365
366#[cfg(feature = "wincode")]
367impl<'de> SchemaRead<'de> for VersionedMessage {
368 type Dst = Self;
369
370 fn read(reader: &mut impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
371 let variant = u8::get(reader)?;
376
377 if variant & MESSAGE_VERSION_PREFIX != 0 {
378 use wincode::error::invalid_tag_encoding;
379
380 let version = variant & !MESSAGE_VERSION_PREFIX;
381 return match version {
382 0 => {
383 let msg = v0::Message::get(reader)?;
384 dst.write(VersionedMessage::V0(msg));
385 Ok(())
386 }
387 _ => Err(invalid_tag_encoding(version as usize)),
388 };
389 }
390
391 let mut msg = MaybeUninit::<LegacyMessage>::uninit();
392 let mut msg_builder = LegacyMessageUninitBuilder::from_maybe_uninit_mut(&mut msg);
393 let mut header_builder =
398 MessageHeaderUninitBuilder::from_maybe_uninit_mut(msg_builder.uninit_header_mut());
399 header_builder.write_num_required_signatures(variant);
400 header_builder.read_num_readonly_signed_accounts(reader)?;
401 header_builder.read_num_readonly_unsigned_accounts(reader)?;
402 header_builder.finish();
403 unsafe { msg_builder.assume_init_header() };
404
405 msg_builder.read_account_keys(reader)?;
406 msg_builder.read_recent_blockhash(reader)?;
407 msg_builder.read_instructions(reader)?;
408 msg_builder.finish();
409
410 let msg = unsafe { msg.assume_init() };
411 dst.write(VersionedMessage::Legacy(msg));
412
413 Ok(())
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use {
420 super::*,
421 crate::v0::MessageAddressTableLookup,
422 solana_instruction::{AccountMeta, Instruction},
423 };
424
425 #[test]
426 fn test_legacy_message_serialization() {
427 let program_id0 = Address::new_unique();
428 let program_id1 = Address::new_unique();
429 let id0 = Address::new_unique();
430 let id1 = Address::new_unique();
431 let id2 = Address::new_unique();
432 let id3 = Address::new_unique();
433 let instructions = vec![
434 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
435 Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
436 Instruction::new_with_bincode(
437 program_id1,
438 &0,
439 vec![AccountMeta::new_readonly(id2, false)],
440 ),
441 Instruction::new_with_bincode(
442 program_id1,
443 &0,
444 vec![AccountMeta::new_readonly(id3, true)],
445 ),
446 ];
447
448 let mut message = LegacyMessage::new(&instructions, Some(&id1));
449 message.recent_blockhash = Hash::new_unique();
450 let wrapped_message = VersionedMessage::Legacy(message.clone());
451
452 {
454 let bytes = bincode::serialize(&message).unwrap();
455 assert_eq!(bytes, bincode::serialize(&wrapped_message).unwrap());
456
457 let message_from_bytes: LegacyMessage = bincode::deserialize(&bytes).unwrap();
458 let wrapped_message_from_bytes: VersionedMessage =
459 bincode::deserialize(&bytes).unwrap();
460
461 assert_eq!(message, message_from_bytes);
462 assert_eq!(wrapped_message, wrapped_message_from_bytes);
463 }
464
465 {
467 let string = serde_json::to_string(&message).unwrap();
468 let message_from_string: LegacyMessage = serde_json::from_str(&string).unwrap();
469 assert_eq!(message, message_from_string);
470 }
471 }
472
473 #[test]
474 fn test_versioned_message_serialization() {
475 let message = VersionedMessage::V0(v0::Message {
476 header: MessageHeader {
477 num_required_signatures: 1,
478 num_readonly_signed_accounts: 0,
479 num_readonly_unsigned_accounts: 0,
480 },
481 recent_blockhash: Hash::new_unique(),
482 account_keys: vec![Address::new_unique()],
483 address_table_lookups: vec![
484 MessageAddressTableLookup {
485 account_key: Address::new_unique(),
486 writable_indexes: vec![1],
487 readonly_indexes: vec![0],
488 },
489 MessageAddressTableLookup {
490 account_key: Address::new_unique(),
491 writable_indexes: vec![0],
492 readonly_indexes: vec![1],
493 },
494 ],
495 instructions: vec![CompiledInstruction {
496 program_id_index: 1,
497 accounts: vec![0, 2, 3, 4],
498 data: vec![],
499 }],
500 });
501
502 let bytes = bincode::serialize(&message).unwrap();
503 let message_from_bytes: VersionedMessage = bincode::deserialize(&bytes).unwrap();
504 assert_eq!(message, message_from_bytes);
505
506 let string = serde_json::to_string(&message).unwrap();
507 let message_from_string: VersionedMessage = serde_json::from_str(&string).unwrap();
508 assert_eq!(message, message_from_string);
509 }
510}