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