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