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