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 pub fn is_instruction_account(&self, key_index: usize) -> bool {
228 if let Ok(key_index) = u8::try_from(key_index) {
229 self.instructions()
230 .iter()
231 .any(|ix| ix.accounts.contains(&key_index))
232 } else {
233 false
234 }
235 }
236
237 pub fn is_invoked(&self, key_index: usize) -> bool {
240 match self {
241 Self::Legacy(message) => message.is_key_called_as_program(key_index),
242 Self::V0(message) => message.is_key_called_as_program(key_index),
243 Self::V1(message) => message.is_key_called_as_program(key_index),
244 }
245 }
246
247 pub fn is_writable(&self, index: usize) -> bool {
250 match self {
251 Self::Legacy(message) => message.is_writable(index),
252 Self::V0(message) => message.is_writable(index),
253 Self::V1(message) => message.is_writable(index),
254 }
255 }
256
257 pub fn is_signer(&self, index: usize) -> bool {
260 index < usize::from(self.header().num_required_signatures)
261 }
262
263 fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
265 match &self {
266 SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
267 _ => None,
268 }
269 }
270
271 pub fn num_readonly_accounts(&self) -> usize {
273 let loaded_readonly_addresses = self
274 .loaded_lookup_table_addresses()
275 .map(|keys| keys.readonly.len())
276 .unwrap_or_default();
277 loaded_readonly_addresses
278 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
279 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
280 }
281
282 pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction<'_>> {
284 let account_keys = self.account_keys();
285 self.program_instructions_iter()
286 .map(|(program_id, instruction)| {
287 let accounts = instruction
288 .accounts
289 .iter()
290 .map(|account_index| {
291 let account_index = *account_index as usize;
292 BorrowedAccountMeta {
293 is_signer: self.is_signer(account_index),
294 is_writable: self.is_writable(account_index),
295 pubkey: account_keys.get(account_index).unwrap(),
296 }
297 })
298 .collect();
299
300 BorrowedInstruction {
301 accounts,
302 data: &instruction.data,
303 program_id,
304 }
305 })
306 .collect()
307 }
308
309 pub fn is_upgradeable_loader_present(&self) -> bool {
311 match self {
312 Self::Legacy(message) => message.is_upgradeable_loader_present(),
313 Self::V0(message) => message.is_upgradeable_loader_present(),
314 Self::V1(message) => message.is_upgradeable_loader_present(),
315 }
316 }
317
318 pub fn get_ix_signers(&self, ix_index: usize) -> impl Iterator<Item = &Address> {
320 self.instructions()
321 .get(ix_index)
322 .into_iter()
323 .flat_map(|ix| {
324 ix.accounts
325 .iter()
326 .copied()
327 .map(usize::from)
328 .filter(|index| self.is_signer(*index))
329 .filter_map(|signer_index| self.account_keys().get(signer_index))
330 })
331 }
332
333 pub fn get_durable_nonce(&self) -> Option<&Address> {
335 self.instructions()
336 .get(NONCED_TX_MARKER_IX_INDEX as usize)
337 .filter(
338 |ix| match self.account_keys().get(ix.program_id_index as usize) {
339 Some(program_id) => solana_sdk_ids::system_program::check_id(program_id),
340 _ => false,
341 },
342 )
343 .filter(|ix| crate::inline_nonce::is_advance_nonce_instruction_data(&ix.data))
344 .and_then(|ix| {
345 ix.accounts.first().and_then(|idx| {
346 let idx = *idx as usize;
347 if !self.is_writable(idx) {
348 None
349 } else {
350 self.account_keys().get(idx)
351 }
352 })
353 })
354 }
355
356 pub fn num_total_signatures(&self) -> u64 {
360 self.get_signature_details().total_signatures()
361 }
362
363 pub fn num_write_locks(&self) -> u64 {
366 self.account_keys()
367 .len()
368 .saturating_sub(self.num_readonly_accounts()) as u64
369 }
370
371 pub fn get_signature_details(&self) -> TransactionSignatureDetails {
373 let mut transaction_signature_details = TransactionSignatureDetails {
374 num_transaction_signatures: u64::from(self.header().num_required_signatures),
375 ..TransactionSignatureDetails::default()
376 };
377
378 for (program_id, instruction) in self.program_instructions_iter() {
380 if secp256k1_program::check_id(program_id) {
381 if let Some(num_verifies) = instruction.data.first() {
382 transaction_signature_details.num_secp256k1_instruction_signatures =
383 transaction_signature_details
384 .num_secp256k1_instruction_signatures
385 .saturating_add(u64::from(*num_verifies));
386 }
387 } else if ed25519_program::check_id(program_id) {
388 if let Some(num_verifies) = instruction.data.first() {
389 transaction_signature_details.num_ed25519_instruction_signatures =
390 transaction_signature_details
391 .num_ed25519_instruction_signatures
392 .saturating_add(u64::from(*num_verifies));
393 }
394 } else if secp256r1_program::check_id(program_id) {
395 if let Some(num_verifies) = instruction.data.first() {
396 transaction_signature_details.num_secp256r1_instruction_signatures =
397 transaction_signature_details
398 .num_secp256r1_instruction_signatures
399 .saturating_add(u64::from(*num_verifies));
400 }
401 }
402 }
403
404 transaction_signature_details
405 }
406}
407
408#[derive(Clone, Debug, Default)]
411pub struct TransactionSignatureDetails {
412 num_transaction_signatures: u64,
413 num_secp256k1_instruction_signatures: u64,
414 num_ed25519_instruction_signatures: u64,
415 num_secp256r1_instruction_signatures: u64,
416}
417
418impl TransactionSignatureDetails {
419 pub const fn new(
420 num_transaction_signatures: u64,
421 num_secp256k1_instruction_signatures: u64,
422 num_ed25519_instruction_signatures: u64,
423 num_secp256r1_instruction_signatures: u64,
424 ) -> Self {
425 Self {
426 num_transaction_signatures,
427 num_secp256k1_instruction_signatures,
428 num_ed25519_instruction_signatures,
429 num_secp256r1_instruction_signatures,
430 }
431 }
432
433 pub fn total_signatures(&self) -> u64 {
435 self.num_transaction_signatures
436 .saturating_add(self.num_secp256k1_instruction_signatures)
437 .saturating_add(self.num_ed25519_instruction_signatures)
438 .saturating_add(self.num_secp256r1_instruction_signatures)
439 }
440
441 pub fn num_transaction_signatures(&self) -> u64 {
443 self.num_transaction_signatures
444 }
445
446 pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
448 self.num_secp256k1_instruction_signatures
449 }
450
451 pub fn num_ed25519_instruction_signatures(&self) -> u64 {
453 self.num_ed25519_instruction_signatures
454 }
455
456 pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
458 self.num_secp256r1_instruction_signatures
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use {super::*, crate::v0, alloc::vec, std::collections::HashSet};
465
466 #[test]
467 fn test_try_from_legacy_message() {
468 let legacy_message_with_no_signers = legacy::Message {
469 account_keys: vec![Address::new_unique()],
470 ..legacy::Message::default()
471 };
472
473 assert_eq!(
474 SanitizedMessage::try_from_legacy_message(
475 legacy_message_with_no_signers,
476 &HashSet::default(),
477 )
478 .err(),
479 Some(SanitizeMessageError::IndexOutOfBounds),
480 );
481 }
482
483 #[test]
484 fn test_num_readonly_accounts() {
485 let key0 = Address::new_unique();
486 let key1 = Address::new_unique();
487 let key2 = Address::new_unique();
488 let key3 = Address::new_unique();
489 let key4 = Address::new_unique();
490 let key5 = Address::new_unique();
491
492 let legacy_message = SanitizedMessage::try_from_legacy_message(
493 legacy::Message {
494 header: MessageHeader {
495 num_required_signatures: 2,
496 num_readonly_signed_accounts: 1,
497 num_readonly_unsigned_accounts: 1,
498 },
499 account_keys: vec![key0, key1, key2, key3],
500 ..legacy::Message::default()
501 },
502 &HashSet::default(),
503 )
504 .unwrap();
505
506 assert_eq!(legacy_message.num_readonly_accounts(), 2);
507
508 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
509 v0::Message {
510 header: MessageHeader {
511 num_required_signatures: 2,
512 num_readonly_signed_accounts: 1,
513 num_readonly_unsigned_accounts: 1,
514 },
515 account_keys: vec![key0, key1, key2, key3],
516 ..v0::Message::default()
517 },
518 LoadedAddresses {
519 writable: vec![key4],
520 readonly: vec![key5],
521 },
522 &HashSet::default(),
523 ));
524
525 assert_eq!(v0_message.num_readonly_accounts(), 3);
526 }
527
528 #[test]
529 fn test_get_ix_signers() {
530 let signer0 = Address::new_unique();
531 let signer1 = Address::new_unique();
532 let non_signer = Address::new_unique();
533 let loader_key = Address::new_unique();
534 let instructions = vec![
535 CompiledInstruction::new(3, &(), vec![2, 0]),
536 CompiledInstruction::new(3, &(), vec![0, 1]),
537 CompiledInstruction::new(3, &(), vec![0, 0]),
538 ];
539
540 let message = SanitizedMessage::try_from_legacy_message(
541 legacy::Message::new_with_compiled_instructions(
542 2,
543 1,
544 2,
545 vec![signer0, signer1, non_signer, loader_key],
546 Hash::default(),
547 instructions,
548 ),
549 &HashSet::default(),
550 )
551 .unwrap();
552
553 assert_eq!(
554 message.get_ix_signers(0).collect::<HashSet<_>>(),
555 HashSet::from_iter([&signer0])
556 );
557 assert_eq!(
558 message.get_ix_signers(1).collect::<HashSet<_>>(),
559 HashSet::from_iter([&signer0, &signer1])
560 );
561 assert_eq!(
562 message.get_ix_signers(2).collect::<HashSet<_>>(),
563 HashSet::from_iter([&signer0])
564 );
565 assert_eq!(
566 message.get_ix_signers(3).collect::<HashSet<_>>(),
567 HashSet::default()
568 );
569 }
570
571 #[test]
572 #[allow(clippy::get_first)]
573 fn test_is_writable_account_cache() {
574 let key0 = Address::new_unique();
575 let key1 = Address::new_unique();
576 let key2 = Address::new_unique();
577 let key3 = Address::new_unique();
578 let key4 = Address::new_unique();
579 let key5 = Address::new_unique();
580
581 let legacy_message = SanitizedMessage::try_from_legacy_message(
582 legacy::Message {
583 header: MessageHeader {
584 num_required_signatures: 2,
585 num_readonly_signed_accounts: 1,
586 num_readonly_unsigned_accounts: 1,
587 },
588 account_keys: vec![key0, key1, key2, key3],
589 ..legacy::Message::default()
590 },
591 &HashSet::default(),
592 )
593 .unwrap();
594 match legacy_message {
595 SanitizedMessage::Legacy(message) => {
596 assert_eq!(
597 message.is_writable_account_cache.len(),
598 message.account_keys().len()
599 );
600 assert!(message.is_writable_account_cache.get(0).unwrap());
601 assert!(!message.is_writable_account_cache.get(1).unwrap());
602 assert!(message.is_writable_account_cache.get(2).unwrap());
603 assert!(!message.is_writable_account_cache.get(3).unwrap());
604 }
605 _ => {
606 panic!("Expect to be SanitizedMessage::LegacyMessage")
607 }
608 }
609
610 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
611 v0::Message {
612 header: MessageHeader {
613 num_required_signatures: 2,
614 num_readonly_signed_accounts: 1,
615 num_readonly_unsigned_accounts: 1,
616 },
617 account_keys: vec![key0, key1, key2, key3],
618 ..v0::Message::default()
619 },
620 LoadedAddresses {
621 writable: vec![key4],
622 readonly: vec![key5],
623 },
624 &HashSet::default(),
625 ));
626 match v0_message {
627 SanitizedMessage::V0(message) => {
628 assert_eq!(
629 message.is_writable_account_cache.len(),
630 message.account_keys().len()
631 );
632 assert!(message.is_writable_account_cache.get(0).unwrap());
633 assert!(!message.is_writable_account_cache.get(1).unwrap());
634 assert!(message.is_writable_account_cache.get(2).unwrap());
635 assert!(!message.is_writable_account_cache.get(3).unwrap());
636 assert!(message.is_writable_account_cache.get(4).unwrap());
637 assert!(!message.is_writable_account_cache.get(5).unwrap());
638 }
639 _ => {
640 panic!("Expect to be SanitizedMessage::V0")
641 }
642 }
643 }
644
645 #[test]
646 fn test_get_signature_details() {
647 let key0 = Address::new_unique();
648 let key1 = Address::new_unique();
649 let loader_key = Address::new_unique();
650
651 let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]);
652 let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]);
653 let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]);
654
655 let message = SanitizedMessage::try_from_legacy_message(
656 legacy::Message::new_with_compiled_instructions(
657 2,
658 1,
659 2,
660 vec![
661 key0,
662 key1,
663 loader_key,
664 secp256k1_program::id(),
665 ed25519_program::id(),
666 ],
667 Hash::default(),
668 vec![
669 loader_instr,
670 mock_secp256k1_instr.clone(),
671 mock_ed25519_instr,
672 mock_secp256k1_instr,
673 ],
674 ),
675 &HashSet::new(),
676 )
677 .unwrap();
678
679 let signature_details = message.get_signature_details();
680 assert_eq!(2, signature_details.num_transaction_signatures);
682 assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
684 assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
686 }
687
688 #[test]
689 fn test_static_account_keys() {
690 let keys = vec![
691 Address::new_unique(),
692 Address::new_unique(),
693 Address::new_unique(),
694 ];
695
696 let header = MessageHeader {
697 num_required_signatures: 2,
698 num_readonly_signed_accounts: 1,
699 num_readonly_unsigned_accounts: 1,
700 };
701
702 let legacy_message = SanitizedMessage::try_from_legacy_message(
703 legacy::Message {
704 header,
705 account_keys: keys.clone(),
706 ..legacy::Message::default()
707 },
708 &HashSet::default(),
709 )
710 .unwrap();
711 assert_eq!(legacy_message.static_account_keys(), &keys);
712
713 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
714 v0::Message {
715 header,
716 account_keys: keys.clone(),
717 ..v0::Message::default()
718 },
719 LoadedAddresses {
720 writable: vec![],
721 readonly: vec![],
722 },
723 &HashSet::default(),
724 ));
725 assert_eq!(v0_message.static_account_keys(), &keys);
726
727 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
728 v0::Message {
729 header,
730 account_keys: keys.clone(),
731 ..v0::Message::default()
732 },
733 LoadedAddresses {
734 writable: vec![Address::new_unique()],
735 readonly: vec![Address::new_unique()],
736 },
737 &HashSet::default(),
738 ));
739 assert_eq!(v0_message.static_account_keys(), &keys);
740 }
741}