solana_program_fork_cleon_00/message/
sanitized.rs

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    /// Legacy message
28    pub message: Cow<'a, legacy::Message>,
29    /// List of boolean with same length as account_keys(), each boolean value indicates if
30    /// corresponding account key is writable or not.
31    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    /// Inspect all message keys for the bpf upgradeable loader
57    pub fn is_upgradeable_loader_present(&self) -> bool {
58        self.message.is_upgradeable_loader_present()
59    }
60
61    /// Returns the full list of account keys.
62    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/// Sanitized message of a transaction.
72#[derive(Debug, Clone, Eq, PartialEq)]
73pub enum SanitizedMessage {
74    /// Sanitized legacy message
75    Legacy(LegacyMessage<'static>),
76    /// Sanitized version #0 message with dynamically loaded addresses
77    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    /// Create a sanitized message from a sanitized versioned message.
112    /// If the input message uses address tables, attempt to look up the
113    /// address for each table index.
114    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    /// Return true if this message contains duplicate account keys
131    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    /// Message header which identifies the number of signer and writable or
139    /// readonly accounts
140    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    /// Returns a legacy message if this sanitized message wraps one
148    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    /// Returns the fee payer for the transaction
157    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    /// The hash of a recent block, used for timing out a transaction
164    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    /// Program instructions that will be executed in sequence and committed in
172    /// one atomic transaction if all succeed.
173    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    /// Program instructions iterator which includes each instruction's program
181    /// id.
182    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    /// Returns the list of account keys that are loaded for this message.
196    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    /// Returns the list of account keys used for account lookup tables.
204    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    /// Returns true if the account at the specified index is an input to some
212    /// program instruction in this message.
213    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    /// 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    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    /// Returns true if the account at the specified index is writable by the
239    /// instructions in this message.
240    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    /// Returns true if the account at the specified index signed this
248    /// message.
249    pub fn is_signer(&self, index: usize) -> bool {
250        index < usize::from(self.header().num_required_signatures)
251    }
252
253    /// Return the resolved addresses for this message if it has any.
254    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    /// Return the number of readonly accounts loaded by this message.
262    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    /// Decompile message instructions without cloning account keys
273    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    /// Inspect all message keys for the bpf upgradeable loader
300    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    /// Get a list of signers for the instruction at the given index
308    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    /// If the message uses a durable nonce, return the pubkey of the nonce account
323    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 /* serialized size of AdvanceNonceAccount */),
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    /// Returns the number of requested write-locks in this message.
355    /// This does not consider if write-locks are demoted.
356    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    /// return detailed signature counts
363    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        // counting the number of pre-processor operations separately
370        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)]
401/// Transaction signature details including the number of transaction signatures
402/// and precompile signatures.
403pub 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    /// return total number of signature, treating pre-processor operations as signature
412    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    /// return the number of transaction signatures
420    pub fn num_transaction_signatures(&self) -> u64 {
421        self.num_transaction_signatures
422    }
423
424    /// return the number of secp256k1 instruction signatures
425    pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
426        self.num_secp256k1_instruction_signatures
427    }
428
429     /// return the number of secp256k1 instruction signatures
430     pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
431        self.num_secp256r1_instruction_signatures
432    }
433
434    /// return the number of ed25519 instruction signatures
435    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        // expect 2 required transaction signatures
666        assert_eq!(2, signature_details.num_transaction_signatures);
667        // expect 2 secp256k1 instruction signatures - 1 for each mock_secp2561k1_instr
668        assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
669        // expect 5 ed25519 instruction signatures from mock_ed25519_instr
670        assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
671    }
672}