solana_program/message/
sanitized.rs

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    /// Legacy message
30    pub message: Cow<'a, legacy::Message>,
31    /// List of boolean with same length as account_keys(), each boolean value indicates if
32    /// corresponding account key is writable or not.
33    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    /// Inspect all message keys for the bpf upgradeable loader
63    pub fn is_upgradeable_loader_present(&self) -> bool {
64        self.message.is_upgradeable_loader_present()
65    }
66
67    /// Returns the full list of account keys.
68    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/// Sanitized message of a transaction.
78#[derive(Debug, Clone, Eq, PartialEq)]
79pub enum SanitizedMessage {
80    /// Sanitized legacy message
81    Legacy(LegacyMessage<'static>),
82    /// Sanitized version #0 message with dynamically loaded addresses
83    V0(v0::LoadedMessage<'static>),
84}
85
86impl SanitizedMessage {
87    /// Create a sanitized message from a sanitized versioned message.
88    /// If the input message uses address tables, attempt to look up the
89    /// address for each table index.
90    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    /// Create a sanitized legacy message
112    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    /// Return true if this message contains duplicate account keys
124    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    /// Message header which identifies the number of signer and writable or
132    /// readonly accounts
133    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    /// Returns a legacy message if this sanitized message wraps one
141    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    /// Returns the fee payer for the transaction
150    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    /// The hash of a recent block, used for timing out a transaction
157    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    /// Program instructions that will be executed in sequence and committed in
165    /// one atomic transaction if all succeed.
166    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    /// Program instructions iterator which includes each instruction's program
174    /// id.
175    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    /// Returns the list of account keys that are loaded for this message.
189    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    /// Returns the list of account keys used for account lookup tables.
197    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    /// Returns true if the account at the specified index is an input to some
205    /// program instruction in this message.
206    #[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    /// Returns true if the account at the specified index is an input to some
212    /// program instruction in this message.
213    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    /// Returns true if the account at the specified index is invoked as a
224    /// program in this message.
225    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    /// Returns true if the account at the specified index is not invoked as a
233    /// program or, if invoked, is passed to a program.
234    #[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    /// Returns true if the account at the specified index is writable by the
243    /// instructions in this message.
244    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    /// Returns true if the account at the specified index signed this
252    /// message.
253    pub fn is_signer(&self, index: usize) -> bool {
254        index < usize::from(self.header().num_required_signatures)
255    }
256
257    /// Return the resolved addresses for this message if it has any.
258    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    /// Return the number of readonly accounts loaded by this message.
266    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    /// Decompile message instructions without cloning account keys
277    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    /// Inspect all message keys for the bpf upgradeable loader
304    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    /// Get a list of signers for the instruction at the given index
312    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    /// If the message uses a durable nonce, return the pubkey of the nonce account
327    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 /* serialized size of AdvanceNonceAccount */),
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    /// Returns the total number of signatures in the message.
363    /// This includes required transaction signatures as well as any
364    /// pre-compile signatures that are attached in instructions.
365    pub fn num_total_signatures(&self) -> u64 {
366        self.get_signature_details().total_signatures()
367    }
368
369    /// Returns the number of requested write-locks in this message.
370    /// This does not consider if write-locks are demoted.
371    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    /// return detailed signature counts
378    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        // counting the number of pre-processor operations separately
385        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/// Transaction signature details including the number of transaction signatures
408/// and precompile signatures.
409#[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    /// return total number of signature, treating pre-processor operations as signature
430    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    /// return the number of transaction signatures
437    pub fn num_transaction_signatures(&self) -> u64 {
438        self.num_transaction_signatures
439    }
440
441    /// return the number of secp256k1 instruction signatures
442    pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
443        self.num_secp256k1_instruction_signatures
444    }
445
446    /// return the number of ed25519 instruction signatures
447    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        // expect 2 required transaction signatures
700        assert_eq!(2, signature_details.num_transaction_signatures);
701        // expect 2 secp256k1 instruction signatures - 1 for each mock_secp2561k1_instr
702        assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
703        // expect 5 ed25519 instruction signatures from mock_ed25519_instr
704        assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
705    }
706}