1use {
2 crate::{
3 ed25519_program,
4 hash::Hash,
5 instruction::CompiledInstruction,
6 message::{
7 legacy,
8 v0::{self, LoadedAddresses},
9 AccountKeys, AddressLoader, AddressLoaderError, MessageHeader,
10 SanitizedVersionedMessage, VersionedMessage,
11 },
12 nonce::NONCED_TX_MARKER_IX_INDEX,
13 program_utils::limited_deserialize,
14 pubkey::Pubkey,
15 sanitize::{Sanitize, SanitizeError},
16 secp256k1_program,
17 secp256r1_program,
18 solana_program::{system_instruction::SystemInstruction, system_program},
19 sysvar::instructions::{BorrowedAccountMeta, BorrowedInstruction},
20 },
21 std::{borrow::Cow, convert::TryFrom},
22 thiserror::Error,
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<'a> LegacyMessage<'a> {
35 pub fn new(message: legacy::Message) -> Self {
36 let is_writable_account_cache = message
37 .account_keys
38 .iter()
39 .enumerate()
40 .map(|(i, _key)| message.is_writable(i))
41 .collect::<Vec<_>>();
42 Self {
43 message: Cow::Owned(message),
44 is_writable_account_cache,
45 }
46 }
47
48 pub fn has_duplicates(&self) -> bool {
49 self.message.has_duplicates()
50 }
51
52 pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
53 self.message.is_key_called_as_program(key_index)
54 }
55
56 pub fn is_upgradeable_loader_present(&self) -> bool {
58 self.message.is_upgradeable_loader_present()
59 }
60
61 pub fn account_keys(&self) -> AccountKeys {
63 AccountKeys::new(&self.message.account_keys, None)
64 }
65
66 pub fn is_writable(&self, index: usize) -> bool {
67 *self.is_writable_account_cache.get(index).unwrap_or(&false)
68 }
69}
70
71#[derive(Debug, Clone, Eq, PartialEq)]
73pub enum SanitizedMessage {
74 Legacy(LegacyMessage<'static>),
76 V0(v0::LoadedMessage<'static>),
78}
79
80#[derive(PartialEq, Debug, Error, Eq, Clone)]
81pub enum SanitizeMessageError {
82 #[error("index out of bounds")]
83 IndexOutOfBounds,
84 #[error("value out of bounds")]
85 ValueOutOfBounds,
86 #[error("invalid value")]
87 InvalidValue,
88 #[error("{0}")]
89 AddressLoaderError(#[from] AddressLoaderError),
90}
91
92impl From<SanitizeError> for SanitizeMessageError {
93 fn from(err: SanitizeError) -> Self {
94 match err {
95 SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
96 SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
97 SanitizeError::InvalidValue => Self::InvalidValue,
98 }
99 }
100}
101
102impl TryFrom<legacy::Message> for SanitizedMessage {
103 type Error = SanitizeMessageError;
104 fn try_from(message: legacy::Message) -> Result<Self, Self::Error> {
105 message.sanitize()?;
106 Ok(Self::Legacy(LegacyMessage::new(message)))
107 }
108}
109
110impl SanitizedMessage {
111 pub fn try_new(
115 sanitized_msg: SanitizedVersionedMessage,
116 address_loader: impl AddressLoader,
117 ) -> Result<Self, SanitizeMessageError> {
118 Ok(match sanitized_msg.message {
119 VersionedMessage::Legacy(message) => {
120 SanitizedMessage::Legacy(LegacyMessage::new(message))
121 }
122 VersionedMessage::V0(message) => {
123 let loaded_addresses =
124 address_loader.load_addresses(&message.address_table_lookups)?;
125 SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
126 }
127 })
128 }
129
130 pub fn has_duplicates(&self) -> bool {
132 match self {
133 SanitizedMessage::Legacy(message) => message.has_duplicates(),
134 SanitizedMessage::V0(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 }
145 }
146
147 pub fn legacy_message(&self) -> Option<&legacy::Message> {
149 if let Self::Legacy(legacy_message) = &self {
150 Some(&legacy_message.message)
151 } else {
152 None
153 }
154 }
155
156 pub fn fee_payer(&self) -> &Pubkey {
158 self.account_keys()
159 .get(0)
160 .expect("sanitized message always has non-program fee payer at index 0")
161 }
162
163 pub fn recent_blockhash(&self) -> &Hash {
165 match self {
166 Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
167 Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
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 }
178 }
179
180 pub fn program_instructions_iter(
183 &self,
184 ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
185 self.instructions().iter().map(move |ix| {
186 (
187 self.account_keys()
188 .get(usize::from(ix.program_id_index))
189 .expect("program id index is sanitized"),
190 ix,
191 )
192 })
193 }
194
195 pub fn account_keys(&self) -> AccountKeys {
197 match self {
198 Self::Legacy(message) => message.account_keys(),
199 Self::V0(message) => message.account_keys(),
200 }
201 }
202
203 pub fn message_address_table_lookups(&self) -> &[v0::MessageAddressTableLookup] {
205 match self {
206 Self::Legacy(_message) => &[],
207 Self::V0(message) => &message.message.address_table_lookups,
208 }
209 }
210
211 fn is_key_passed_to_program(&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 pub fn is_non_loader_key(&self, key_index: usize) -> bool {
235 !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
236 }
237
238 pub fn is_writable(&self, index: usize) -> bool {
241 match self {
242 Self::Legacy(message) => message.is_writable(index),
243 Self::V0(message) => message.is_writable(index),
244 }
245 }
246
247 pub fn is_signer(&self, index: usize) -> bool {
250 index < usize::from(self.header().num_required_signatures)
251 }
252
253 fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
255 match &self {
256 SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
257 _ => None,
258 }
259 }
260
261 pub fn num_readonly_accounts(&self) -> usize {
263 let loaded_readonly_addresses = self
264 .loaded_lookup_table_addresses()
265 .map(|keys| keys.readonly.len())
266 .unwrap_or_default();
267 loaded_readonly_addresses
268 .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
269 .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
270 }
271
272 pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction> {
274 let account_keys = self.account_keys();
275 self.program_instructions_iter()
276 .map(|(program_id, instruction)| {
277 let accounts = instruction
278 .accounts
279 .iter()
280 .map(|account_index| {
281 let account_index = *account_index as usize;
282 BorrowedAccountMeta {
283 is_signer: self.is_signer(account_index),
284 is_writable: self.is_writable(account_index),
285 pubkey: account_keys.get(account_index).unwrap(),
286 }
287 })
288 .collect();
289
290 BorrowedInstruction {
291 accounts,
292 data: &instruction.data,
293 program_id,
294 }
295 })
296 .collect()
297 }
298
299 pub fn is_upgradeable_loader_present(&self) -> bool {
301 match self {
302 Self::Legacy(message) => message.is_upgradeable_loader_present(),
303 Self::V0(message) => message.is_upgradeable_loader_present(),
304 }
305 }
306
307 pub fn get_ix_signers(&self, ix_index: usize) -> impl Iterator<Item = &Pubkey> {
309 self.instructions()
310 .get(ix_index)
311 .into_iter()
312 .flat_map(|ix| {
313 ix.accounts
314 .iter()
315 .copied()
316 .map(usize::from)
317 .filter(|index| self.is_signer(*index))
318 .filter_map(|signer_index| self.account_keys().get(signer_index))
319 })
320 }
321
322 pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
324 self.instructions()
325 .get(NONCED_TX_MARKER_IX_INDEX as usize)
326 .filter(
327 |ix| match self.account_keys().get(ix.program_id_index as usize) {
328 Some(program_id) => system_program::check_id(program_id),
329 _ => false,
330 },
331 )
332 .filter(|ix| {
333 matches!(
334 limited_deserialize(&ix.data, 4 ),
335 Ok(SystemInstruction::AdvanceNonceAccount)
336 )
337 })
338 .and_then(|ix| {
339 ix.accounts.first().and_then(|idx| {
340 let idx = *idx as usize;
341 if !self.is_writable(idx) {
342 None
343 } else {
344 self.account_keys().get(idx)
345 }
346 })
347 })
348 }
349
350 pub fn num_signatures(&self) -> u64 {
351 self.get_signature_details().total_signatures()
352 }
353
354 pub fn num_write_locks(&self) -> u64 {
357 self.account_keys()
358 .len()
359 .saturating_sub(self.num_readonly_accounts()) as u64
360 }
361
362 pub fn get_signature_details(&self) -> TransactionSignatureDetails {
364 let mut transaction_signature_details = TransactionSignatureDetails {
365 num_transaction_signatures: u64::from(self.header().num_required_signatures),
366 ..TransactionSignatureDetails::default()
367 };
368
369 for (program_id, instruction) in self.program_instructions_iter() {
371 if secp256k1_program::check_id(program_id) {
372 if let Some(num_verifies) = instruction.data.first() {
373 transaction_signature_details.num_secp256k1_instruction_signatures =
374 transaction_signature_details
375 .num_secp256k1_instruction_signatures
376 .saturating_add(u64::from(*num_verifies));
377 }
378 } else if ed25519_program::check_id(program_id) {
379 if let Some(num_verifies) = instruction.data.first() {
380 transaction_signature_details.num_ed25519_instruction_signatures =
381 transaction_signature_details
382 .num_ed25519_instruction_signatures
383 .saturating_add(u64::from(*num_verifies));
384 }
385 } else if secp256r1_program::check_id(program_id) {
386 if let Some(num_verifies) = instruction.data.first() {
387 transaction_signature_details.num_secp256r1_instruction_signatures =
388 transaction_signature_details
389 .num_secp256r1_instruction_signatures
390 .saturating_add(u64::from(*num_verifies));
391 }
392 }
393
394 }
395
396 transaction_signature_details
397 }
398}
399
400#[derive(Default)]
401pub struct TransactionSignatureDetails {
404 num_transaction_signatures: u64,
405 num_secp256k1_instruction_signatures: u64,
406 num_secp256r1_instruction_signatures: u64,
407 num_ed25519_instruction_signatures: u64,
408}
409
410impl TransactionSignatureDetails {
411 pub(crate) fn total_signatures(&self) -> u64 {
413 self.num_transaction_signatures
414 .saturating_add(self.num_secp256k1_instruction_signatures)
415 .saturating_add(self.num_ed25519_instruction_signatures)
416 .saturating_add(self.num_secp256r1_instruction_signatures)
417 }
418
419 pub fn num_transaction_signatures(&self) -> u64 {
421 self.num_transaction_signatures
422 }
423
424 pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
426 self.num_secp256k1_instruction_signatures
427 }
428
429 pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
431 self.num_secp256r1_instruction_signatures
432 }
433
434 pub fn num_ed25519_instruction_signatures(&self) -> u64 {
436 self.num_ed25519_instruction_signatures
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use {super::*, crate::message::v0, std::collections::HashSet};
443
444 #[test]
445 fn test_try_from_message() {
446 let legacy_message_with_no_signers = legacy::Message {
447 account_keys: vec![Pubkey::new_unique()],
448 ..legacy::Message::default()
449 };
450
451 assert_eq!(
452 SanitizedMessage::try_from(legacy_message_with_no_signers).err(),
453 Some(SanitizeMessageError::IndexOutOfBounds),
454 );
455 }
456
457 #[test]
458 fn test_is_non_loader_key() {
459 let key0 = Pubkey::new_unique();
460 let key1 = Pubkey::new_unique();
461 let loader_key = Pubkey::new_unique();
462 let instructions = vec![
463 CompiledInstruction::new(1, &(), vec![0]),
464 CompiledInstruction::new(2, &(), vec![0, 1]),
465 ];
466
467 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
468 1,
469 0,
470 2,
471 vec![key0, key1, loader_key],
472 Hash::default(),
473 instructions,
474 ))
475 .unwrap();
476
477 assert!(message.is_non_loader_key(0));
478 assert!(message.is_non_loader_key(1));
479 assert!(!message.is_non_loader_key(2));
480 }
481
482 #[test]
483 fn test_num_readonly_accounts() {
484 let key0 = Pubkey::new_unique();
485 let key1 = Pubkey::new_unique();
486 let key2 = Pubkey::new_unique();
487 let key3 = Pubkey::new_unique();
488 let key4 = Pubkey::new_unique();
489 let key5 = Pubkey::new_unique();
490
491 let legacy_message = SanitizedMessage::try_from(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 .unwrap();
501
502 assert_eq!(legacy_message.num_readonly_accounts(), 2);
503
504 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
505 v0::Message {
506 header: MessageHeader {
507 num_required_signatures: 2,
508 num_readonly_signed_accounts: 1,
509 num_readonly_unsigned_accounts: 1,
510 },
511 account_keys: vec![key0, key1, key2, key3],
512 ..v0::Message::default()
513 },
514 LoadedAddresses {
515 writable: vec![key4],
516 readonly: vec![key5],
517 },
518 ));
519
520 assert_eq!(v0_message.num_readonly_accounts(), 3);
521 }
522
523 #[test]
524 fn test_get_ix_signers() {
525 let signer0 = Pubkey::new_unique();
526 let signer1 = Pubkey::new_unique();
527 let non_signer = Pubkey::new_unique();
528 let loader_key = Pubkey::new_unique();
529 let instructions = vec![
530 CompiledInstruction::new(3, &(), vec![2, 0]),
531 CompiledInstruction::new(3, &(), vec![0, 1]),
532 CompiledInstruction::new(3, &(), vec![0, 0]),
533 ];
534
535 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
536 2,
537 1,
538 2,
539 vec![signer0, signer1, non_signer, loader_key],
540 Hash::default(),
541 instructions,
542 ))
543 .unwrap();
544
545 assert_eq!(
546 message.get_ix_signers(0).collect::<HashSet<_>>(),
547 HashSet::from_iter([&signer0])
548 );
549 assert_eq!(
550 message.get_ix_signers(1).collect::<HashSet<_>>(),
551 HashSet::from_iter([&signer0, &signer1])
552 );
553 assert_eq!(
554 message.get_ix_signers(2).collect::<HashSet<_>>(),
555 HashSet::from_iter([&signer0])
556 );
557 assert_eq!(
558 message.get_ix_signers(3).collect::<HashSet<_>>(),
559 HashSet::default()
560 );
561 }
562
563 #[test]
564 #[allow(clippy::get_first)]
565 fn test_is_writable_account_cache() {
566 let key0 = Pubkey::new_unique();
567 let key1 = Pubkey::new_unique();
568 let key2 = Pubkey::new_unique();
569 let key3 = Pubkey::new_unique();
570 let key4 = Pubkey::new_unique();
571 let key5 = Pubkey::new_unique();
572
573 let legacy_message = SanitizedMessage::try_from(legacy::Message {
574 header: MessageHeader {
575 num_required_signatures: 2,
576 num_readonly_signed_accounts: 1,
577 num_readonly_unsigned_accounts: 1,
578 },
579 account_keys: vec![key0, key1, key2, key3],
580 ..legacy::Message::default()
581 })
582 .unwrap();
583 match legacy_message {
584 SanitizedMessage::Legacy(message) => {
585 assert_eq!(
586 message.is_writable_account_cache.len(),
587 message.account_keys().len()
588 );
589 assert!(message.is_writable_account_cache.get(0).unwrap());
590 assert!(!message.is_writable_account_cache.get(1).unwrap());
591 assert!(message.is_writable_account_cache.get(2).unwrap());
592 assert!(!message.is_writable_account_cache.get(3).unwrap());
593 }
594 _ => {
595 panic!("Expect to be SanitizedMessage::LegacyMessage")
596 }
597 }
598
599 let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
600 v0::Message {
601 header: MessageHeader {
602 num_required_signatures: 2,
603 num_readonly_signed_accounts: 1,
604 num_readonly_unsigned_accounts: 1,
605 },
606 account_keys: vec![key0, key1, key2, key3],
607 ..v0::Message::default()
608 },
609 LoadedAddresses {
610 writable: vec![key4],
611 readonly: vec![key5],
612 },
613 ));
614 match v0_message {
615 SanitizedMessage::V0(message) => {
616 assert_eq!(
617 message.is_writable_account_cache.len(),
618 message.account_keys().len()
619 );
620 assert!(message.is_writable_account_cache.get(0).unwrap());
621 assert!(!message.is_writable_account_cache.get(1).unwrap());
622 assert!(message.is_writable_account_cache.get(2).unwrap());
623 assert!(!message.is_writable_account_cache.get(3).unwrap());
624 assert!(message.is_writable_account_cache.get(4).unwrap());
625 assert!(!message.is_writable_account_cache.get(5).unwrap());
626 }
627 _ => {
628 panic!("Expect to be SanitizedMessage::V0")
629 }
630 }
631 }
632
633 #[test]
634 fn test_get_signature_details() {
635 let key0 = Pubkey::new_unique();
636 let key1 = Pubkey::new_unique();
637 let loader_key = Pubkey::new_unique();
638
639 let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]);
640 let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]);
641 let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]);
642
643 let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
644 2,
645 1,
646 2,
647 vec![
648 key0,
649 key1,
650 loader_key,
651 secp256k1_program::id(),
652 ed25519_program::id(),
653 ],
654 Hash::default(),
655 vec![
656 loader_instr,
657 mock_secp256k1_instr.clone(),
658 mock_ed25519_instr,
659 mock_secp256k1_instr,
660 ],
661 ))
662 .unwrap();
663
664 let signature_details = message.get_signature_details();
665 assert_eq!(2, signature_details.num_transaction_signatures);
667 assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
669 assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
671 }
672}