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