1use {
2 crate::{
3 compiled_instruction::CompiledInstruction,
4 legacy,
5 v0::{self, LoadedAddresses},
6 v1::CachedMessage,
7 AccountKeys, AddressLoader, MessageHeader, SanitizedVersionedMessage, VersionedMessage,
8 },
9 alloc::{borrow::Cow, vec::Vec},
10 core::convert::TryFrom,
11 solana_address::Address,
12 solana_hash::Hash,
13 solana_instruction::{BorrowedAccountMeta, BorrowedInstruction},
14 solana_sanitize::Sanitize,
15 solana_sdk_ids::{ed25519_program, secp256k1_program, secp256r1_program},
16 solana_transaction_error::SanitizeMessageError,
17 std::collections::HashSet,
18};
19
20const NONCED_TX_MARKER_IX_INDEX: u8 = 0;
22#[cfg(test)]
23static_assertions::const_assert_eq!(
24 NONCED_TX_MARKER_IX_INDEX,
25 solana_nonce::NONCED_TX_MARKER_IX_INDEX
26);
27
28#[derive(Debug, Clone, Eq, PartialEq)]
29pub struct LegacyMessage<'a> {
30 pub message: Cow<'a, legacy::Message>,
32 pub is_writable_account_cache: Vec<bool>,
35}
36
37impl LegacyMessage<'_> {
38 pub fn new(message: legacy::Message, reserved_account_keys: &HashSet<Address>) -> Self {
39 let is_writable_account_cache = message
40 .account_keys
41 .iter()
42 .enumerate()
43 .map(|(i, _key)| {
44 message.is_writable_index(i)
45 && !reserved_account_keys.contains(&message.account_keys[i])
46 && !message.demote_program_id(i)
47 })
48 .collect::<Vec<_>>();
49 Self {
50 message: Cow::Owned(message),
51 is_writable_account_cache,
52 }
53 }
54
55 pub fn has_duplicates(&self) -> bool {
56 self.message.has_duplicates()
57 }
58
59 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
60 self.message.is_key_called_as_program(key_index)
61 }
62
63 pub fn is_upgradeable_loader_present(&self) -> bool {
65 self.message.is_upgradeable_loader_present()
66 }
67
68 pub fn account_keys(&self) -> AccountKeys<'_> {
70 AccountKeys::new(&self.message.account_keys, None)
71 }
72
73 pub fn is_writable(&self, index: usize) -> bool {
74 *self.is_writable_account_cache.get(index).unwrap_or(&false)
75 }
76}
77
78#[derive(Debug, Clone, Eq, PartialEq)]
80pub enum SanitizedMessage {
81 Legacy(LegacyMessage<'static>),
83 V0(v0::LoadedMessage<'static>),
85 V1(CachedMessage<'static>),
87}
88
89impl SanitizedMessage {
90 pub fn try_new(
94 sanitized_msg: SanitizedVersionedMessage,
95 address_loader: impl AddressLoader,
96 reserved_account_keys: &HashSet<Address>,
97 ) -> Result<Self, SanitizeMessageError> {
98 Ok(match sanitized_msg.message {
99 VersionedMessage::Legacy(message) => {
100 SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
101 }
102 VersionedMessage::V0(message) => {
103 let loaded_addresses =
104 address_loader.load_addresses(&message.address_table_lookups)?;
105 SanitizedMessage::V0(v0::LoadedMessage::new(
106 message,
107 loaded_addresses,
108 reserved_account_keys,
109 ))
110 }
111 VersionedMessage::V1(message) => {
112 SanitizedMessage::V1(CachedMessage::new(message, reserved_account_keys))
113 }
114 })
115 }
116
117 pub fn try_from_legacy_message(
119 message: legacy::Message,
120 reserved_account_keys: &HashSet<Address>,
121 ) -> Result<Self, SanitizeMessageError> {
122 message.sanitize()?;
123 Ok(Self::Legacy(LegacyMessage::new(
124 message,
125 reserved_account_keys,
126 )))
127 }
128
129 pub fn has_duplicates(&self) -> bool {
131 match self {
132 SanitizedMessage::Legacy(message) => message.has_duplicates(),
133 SanitizedMessage::V0(message) => message.has_duplicates(),
134 SanitizedMessage::V1(message) => message.has_duplicates(),
135 }
136 }
137
138 pub fn header(&self) -> &MessageHeader {
141 match self {
142 Self::Legacy(legacy_message) => &legacy_message.message.header,
143 Self::V0(loaded_msg) => &loaded_msg.message.header,
144 Self::V1(cached_msg) => &cached_msg.message.header,
145 }
146 }
147
148 pub fn legacy_message(&self) -> Option<&legacy::Message> {
150 if let Self::Legacy(legacy_message) = &self {
151 Some(&legacy_message.message)
152 } else {
153 None
154 }
155 }
156
157 pub fn fee_payer(&self) -> &Address {
159 self.account_keys()
160 .get(0)
161 .expect("sanitized messages always have a fee payer at index 0")
162 }
163
164 pub fn recent_blockhash(&self) -> &Hash {
166 match self {
167 Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
168 Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
169 Self::V1(cached_msg) => &cached_msg.message.lifetime_specifier,
170 }
171 }
172
173 pub fn instructions(&self) -> &[CompiledInstruction] {
176 match self {
177 Self::Legacy(legacy_message) => &legacy_message.message.instructions,
178 Self::V0(loaded_msg) => &loaded_msg.message.instructions,
179 Self::V1(cached_msg) => &cached_msg.message.instructions,
180 }
181 }
182
183 pub fn program_instructions_iter(
186 &self,
187 ) -> impl Iterator<Item = (&Address, &CompiledInstruction)> + Clone {
188 self.instructions().iter().map(move |ix| {
189 (
190 self.account_keys()
191 .get(usize::from(ix.program_id_index))
192 .expect("program id index is sanitized"),
193 ix,
194 )
195 })
196 }
197
198 pub fn static_account_keys(&self) -> &[Address] {
200 match self {
201 Self::Legacy(legacy_message) => &legacy_message.message.account_keys,
202 Self::V0(loaded_msg) => &loaded_msg.message.account_keys,
203 Self::V1(cached_msg) => &cached_msg.message.account_keys,
204 }
205 }
206
207 pub fn account_keys(&self) -> AccountKeys<'_> {
209 match self {
210 Self::Legacy(message) => message.account_keys(),
211 Self::V0(message) => message.account_keys(),
212 Self::V1(message) => message.account_keys(),
213 }
214 }
215
216 pub fn message_address_table_lookups(&self) -> &[v0::MessageAddressTableLookup] {
218 match self {
219 Self::V0(message) => &message.message.address_table_lookups,
220 _ => &[],
222 }
223 }
224
225 #[deprecated(
228 since = "4.6.0",
229 note = "Use `solana_svm_transaction::svm_message::SVMStaticMessage::is_instruction_account()`"
230 )]
231 pub fn is_instruction_account(&self, key_index: usize) -> bool {
232 if let Ok(key_index) = u8::try_from(key_index) {
233 self.instructions()
234 .iter()
235 .any(|ix| ix.accounts.contains(&key_index))
236 } else {
237 false
238 }
239 }
240
241 pub fn is_invoked(&self, key_index: usize) -> bool {
244 match self {
245 Self::Legacy(message) => message.is_key_called_as_program(key_index),
246 Self::V0(message) => message.is_key_called_as_program(key_index),
247 Self::V1(message) => message.is_key_called_as_program(key_index),
248 }
249 }
250
251 pub fn is_writable(&self, index: usize) -> bool {
254 match self {
255 Self::Legacy(message) => message.is_writable(index),
256 Self::V0(message) => message.is_writable(index),
257 Self::V1(message) => message.is_writable(index),
258 }
259 }
260
261 pub fn is_signer(&self, index: usize) -> bool {
264 index < usize::from(self.header().num_required_signatures)
265 }
266
267 fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
269 match &self {
270 SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
271 _ => None,
272 }
273 }
274
275 pub fn num_readonly_accounts(&self) -> usize {
277 let loaded_readonly_addresses = self
278 .loaded_lookup_table_addresses()
279 .map(|keys| keys.readonly.len())
280 .unwrap_or_default();
281 loaded_readonly_addresses
282 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
283 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
284 }
285
286 pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction<'_>> {
288 let account_keys = self.account_keys();
289 self.program_instructions_iter()
290 .map(|(program_id, instruction)| {
291 let accounts = instruction
292 .accounts
293 .iter()
294 .map(|account_index| {
295 let account_index = *account_index as usize;
296 BorrowedAccountMeta {
297 is_signer: self.is_signer(account_index),
298 is_writable: self.is_writable(account_index),
299 pubkey: account_keys.get(account_index).unwrap(),
300 }
301 })
302 .collect();
303
304 BorrowedInstruction {
305 accounts,
306 data: &instruction.data,
307 program_id,
308 }
309 })
310 .collect()
311 }
312
313 pub fn is_upgradeable_loader_present(&self) -> bool {
315 match self {
316 Self::Legacy(message) => message.is_upgradeable_loader_present(),
317 Self::V0(message) => message.is_upgradeable_loader_present(),
318 Self::V1(message) => message.is_upgradeable_loader_present(),
319 }
320 }
321
322 #[deprecated(
324 since = "4.6.0",
325 note = "Use `solana_svm_transaction::svm_message::SVMStaticMessage::get_ix_signers()`"
326 )]
327 pub fn get_ix_signers(&self, ix_index: usize) -> impl Iterator<Item = &Address> {
328 self.instructions()
329 .get(ix_index)
330 .into_iter()
331 .flat_map(|ix| {
332 ix.accounts
333 .iter()
334 .copied()
335 .map(usize::from)
336 .filter(|index| self.is_signer(*index))
337 .filter_map(|signer_index| self.account_keys().get(signer_index))
338 })
339 }
340
341 #[deprecated(
343 since = "4.6.0",
344 note = "Use `solana_svm_transaction::svm_message::SVMMessage::get_durable_nonce()`"
345 )]
346 pub fn get_durable_nonce(&self) -> Option<&Address> {
347 self.instructions()
348 .get(NONCED_TX_MARKER_IX_INDEX as usize)
349 .filter(
350 |ix| match self.account_keys().get(ix.program_id_index as usize) {
351 Some(program_id) => solana_sdk_ids::system_program::check_id(program_id),
352 _ => false,
353 },
354 )
355 .filter(|ix| crate::inline_nonce::is_advance_nonce_instruction_data(&ix.data))
356 .and_then(|ix| {
357 ix.accounts.first().and_then(|idx| {
358 let idx = *idx as usize;
359 if !self.is_writable(idx) {
360 None
361 } else {
362 self.static_account_keys().get(idx)
363 }
364 })
365 })
366 }
367
368 pub fn num_total_signatures(&self) -> u64 {
372 self.get_signature_details().total_signatures()
373 }
374
375 pub fn num_write_locks(&self) -> u64 {
378 self.account_keys()
379 .len()
380 .saturating_sub(self.num_readonly_accounts()) as u64
381 }
382
383 pub fn get_signature_details(&self) -> TransactionSignatureDetails {
385 let mut transaction_signature_details = TransactionSignatureDetails {
386 num_transaction_signatures: u64::from(self.header().num_required_signatures),
387 ..TransactionSignatureDetails::default()
388 };
389
390 for (program_id, instruction) in self.program_instructions_iter() {
392 if secp256k1_program::check_id(program_id) {
393 if let Some(num_verifies) = instruction.data.first() {
394 transaction_signature_details.num_secp256k1_instruction_signatures =
395 transaction_signature_details
396 .num_secp256k1_instruction_signatures
397 .saturating_add(u64::from(*num_verifies));
398 }
399 } else if ed25519_program::check_id(program_id) {
400 if let Some(num_verifies) = instruction.data.first() {
401 transaction_signature_details.num_ed25519_instruction_signatures =
402 transaction_signature_details
403 .num_ed25519_instruction_signatures
404 .saturating_add(u64::from(*num_verifies));
405 }
406 } else if secp256r1_program::check_id(program_id) {
407 if let Some(num_verifies) = instruction.data.first() {
408 transaction_signature_details.num_secp256r1_instruction_signatures =
409 transaction_signature_details
410 .num_secp256r1_instruction_signatures
411 .saturating_add(u64::from(*num_verifies));
412 }
413 }
414 }
415
416 transaction_signature_details
417 }
418}
419
420#[derive(Clone, Debug, Default)]
423pub struct TransactionSignatureDetails {
424 num_transaction_signatures: u64,
425 num_secp256k1_instruction_signatures: u64,
426 num_ed25519_instruction_signatures: u64,
427 num_secp256r1_instruction_signatures: u64,
428}
429
430impl TransactionSignatureDetails {
431 pub const fn new(
432 num_transaction_signatures: u64,
433 num_secp256k1_instruction_signatures: u64,
434 num_ed25519_instruction_signatures: u64,
435 num_secp256r1_instruction_signatures: u64,
436 ) -> Self {
437 Self {
438 num_transaction_signatures,
439 num_secp256k1_instruction_signatures,
440 num_ed25519_instruction_signatures,
441 num_secp256r1_instruction_signatures,
442 }
443 }
444
445 pub fn total_signatures(&self) -> u64 {
447 self.num_transaction_signatures
448 .saturating_add(self.num_secp256k1_instruction_signatures)
449 .saturating_add(self.num_ed25519_instruction_signatures)
450 .saturating_add(self.num_secp256r1_instruction_signatures)
451 }
452
453 pub fn num_transaction_signatures(&self) -> u64 {
455 self.num_transaction_signatures
456 }
457
458 pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
460 self.num_secp256k1_instruction_signatures
461 }
462
463 pub fn num_ed25519_instruction_signatures(&self) -> u64 {
465 self.num_ed25519_instruction_signatures
466 }
467
468 pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
470 self.num_secp256r1_instruction_signatures
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use {super::*, crate::v0, alloc::vec, std::collections::HashSet};
477
478 #[test]
479 fn test_try_from_legacy_message() {
480 let legacy_message_with_no_signers = legacy::Message {
481 account_keys: vec![Address::new_unique()],
482 ..legacy::Message::default()
483 };
484
485 assert_eq!(
486 SanitizedMessage::try_from_legacy_message(
487 legacy_message_with_no_signers,
488 &HashSet::default(),
489 )
490 .err(),
491 Some(SanitizeMessageError::IndexOutOfBounds),
492 );
493 }
494
495 #[test]
496 fn test_num_readonly_accounts() {
497 let key0 = Address::new_unique();
498 let key1 = Address::new_unique();
499 let key2 = Address::new_unique();
500 let key3 = Address::new_unique();
501 let key4 = Address::new_unique();
502 let key5 = Address::new_unique();
503
504 let legacy_message = SanitizedMessage::try_from_legacy_message(
505 legacy::Message {
506 header: MessageHeader {
507 num_required_signatures: 2,
508 num_readonly_signed_accounts: 1,
509 num_readonly_unsigned_accounts: 1,
510 },
511 account_keys: vec![key0, key1, key2, key3],
512 ..legacy::Message::default()
513 },
514 &HashSet::default(),
515 )
516 .unwrap();
517
518 assert_eq!(legacy_message.num_readonly_accounts(), 2);
519
520 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
521 v0::Message {
522 header: MessageHeader {
523 num_required_signatures: 2,
524 num_readonly_signed_accounts: 1,
525 num_readonly_unsigned_accounts: 1,
526 },
527 account_keys: vec![key0, key1, key2, key3],
528 ..v0::Message::default()
529 },
530 LoadedAddresses {
531 writable: vec![key4],
532 readonly: vec![key5],
533 },
534 &HashSet::default(),
535 ));
536
537 assert_eq!(v0_message.num_readonly_accounts(), 3);
538 }
539
540 #[test]
541 #[allow(deprecated)]
542 fn test_get_ix_signers() {
543 let signer0 = Address::new_unique();
544 let signer1 = Address::new_unique();
545 let non_signer = Address::new_unique();
546 let loader_key = Address::new_unique();
547 let instructions = vec![
548 CompiledInstruction::new(3, &(), vec![2, 0]),
549 CompiledInstruction::new(3, &(), vec![0, 1]),
550 CompiledInstruction::new(3, &(), vec![0, 0]),
551 ];
552
553 let message = SanitizedMessage::try_from_legacy_message(
554 legacy::Message::new_with_compiled_instructions(
555 2,
556 1,
557 2,
558 vec![signer0, signer1, non_signer, loader_key],
559 Hash::default(),
560 instructions,
561 ),
562 &HashSet::default(),
563 )
564 .unwrap();
565
566 assert_eq!(
567 message.get_ix_signers(0).collect::<HashSet<_>>(),
568 HashSet::from_iter([&signer0])
569 );
570 assert_eq!(
571 message.get_ix_signers(1).collect::<HashSet<_>>(),
572 HashSet::from_iter([&signer0, &signer1])
573 );
574 assert_eq!(
575 message.get_ix_signers(2).collect::<HashSet<_>>(),
576 HashSet::from_iter([&signer0])
577 );
578 assert_eq!(
579 message.get_ix_signers(3).collect::<HashSet<_>>(),
580 HashSet::default()
581 );
582 }
583
584 #[test]
585 #[allow(clippy::get_first)]
586 fn test_is_writable_account_cache() {
587 let key0 = Address::new_unique();
588 let key1 = Address::new_unique();
589 let key2 = Address::new_unique();
590 let key3 = Address::new_unique();
591 let key4 = Address::new_unique();
592 let key5 = Address::new_unique();
593
594 let legacy_message = SanitizedMessage::try_from_legacy_message(
595 legacy::Message {
596 header: MessageHeader {
597 num_required_signatures: 2,
598 num_readonly_signed_accounts: 1,
599 num_readonly_unsigned_accounts: 1,
600 },
601 account_keys: vec![key0, key1, key2, key3],
602 ..legacy::Message::default()
603 },
604 &HashSet::default(),
605 )
606 .unwrap();
607 match legacy_message {
608 SanitizedMessage::Legacy(message) => {
609 assert_eq!(
610 message.is_writable_account_cache.len(),
611 message.account_keys().len()
612 );
613 assert!(message.is_writable_account_cache.get(0).unwrap());
614 assert!(!message.is_writable_account_cache.get(1).unwrap());
615 assert!(message.is_writable_account_cache.get(2).unwrap());
616 assert!(!message.is_writable_account_cache.get(3).unwrap());
617 }
618 _ => {
619 panic!("Expect to be SanitizedMessage::LegacyMessage")
620 }
621 }
622
623 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
624 v0::Message {
625 header: MessageHeader {
626 num_required_signatures: 2,
627 num_readonly_signed_accounts: 1,
628 num_readonly_unsigned_accounts: 1,
629 },
630 account_keys: vec![key0, key1, key2, key3],
631 ..v0::Message::default()
632 },
633 LoadedAddresses {
634 writable: vec![key4],
635 readonly: vec![key5],
636 },
637 &HashSet::default(),
638 ));
639 match v0_message {
640 SanitizedMessage::V0(message) => {
641 assert_eq!(
642 message.is_writable_account_cache.len(),
643 message.account_keys().len()
644 );
645 assert!(message.is_writable_account_cache.get(0).unwrap());
646 assert!(!message.is_writable_account_cache.get(1).unwrap());
647 assert!(message.is_writable_account_cache.get(2).unwrap());
648 assert!(!message.is_writable_account_cache.get(3).unwrap());
649 assert!(message.is_writable_account_cache.get(4).unwrap());
650 assert!(!message.is_writable_account_cache.get(5).unwrap());
651 }
652 _ => {
653 panic!("Expect to be SanitizedMessage::V0")
654 }
655 }
656 }
657
658 #[test]
659 fn test_get_signature_details() {
660 let key0 = Address::new_unique();
661 let key1 = Address::new_unique();
662 let loader_key = Address::new_unique();
663
664 let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]);
665 let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]);
666 let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]);
667
668 let message = SanitizedMessage::try_from_legacy_message(
669 legacy::Message::new_with_compiled_instructions(
670 2,
671 1,
672 2,
673 vec![
674 key0,
675 key1,
676 loader_key,
677 secp256k1_program::id(),
678 ed25519_program::id(),
679 ],
680 Hash::default(),
681 vec![
682 loader_instr,
683 mock_secp256k1_instr.clone(),
684 mock_ed25519_instr,
685 mock_secp256k1_instr,
686 ],
687 ),
688 &HashSet::new(),
689 )
690 .unwrap();
691
692 let signature_details = message.get_signature_details();
693 assert_eq!(2, signature_details.num_transaction_signatures);
695 assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
697 assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
699 }
700
701 #[test]
702 fn test_static_account_keys() {
703 let keys = vec![
704 Address::new_unique(),
705 Address::new_unique(),
706 Address::new_unique(),
707 ];
708
709 let header = MessageHeader {
710 num_required_signatures: 2,
711 num_readonly_signed_accounts: 1,
712 num_readonly_unsigned_accounts: 1,
713 };
714
715 let legacy_message = SanitizedMessage::try_from_legacy_message(
716 legacy::Message {
717 header,
718 account_keys: keys.clone(),
719 ..legacy::Message::default()
720 },
721 &HashSet::default(),
722 )
723 .unwrap();
724 assert_eq!(legacy_message.static_account_keys(), &keys);
725
726 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
727 v0::Message {
728 header,
729 account_keys: keys.clone(),
730 ..v0::Message::default()
731 },
732 LoadedAddresses {
733 writable: vec![],
734 readonly: vec![],
735 },
736 &HashSet::default(),
737 ));
738 assert_eq!(v0_message.static_account_keys(), &keys);
739
740 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
741 v0::Message {
742 header,
743 account_keys: keys.clone(),
744 ..v0::Message::default()
745 },
746 LoadedAddresses {
747 writable: vec![Address::new_unique()],
748 readonly: vec![Address::new_unique()],
749 },
750 &HashSet::default(),
751 ));
752 assert_eq!(v0_message.static_account_keys(), &keys);
753 }
754}