solana_message/
sanitized.rs

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
17// inlined to avoid solana_nonce dep
18const 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    /// 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 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    /// Inspect all message keys for the bpf upgradeable loader
61    pub fn is_upgradeable_loader_present(&self) -> bool {
62        self.message.is_upgradeable_loader_present()
63    }
64
65    /// Returns the full list of account keys.
66    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/// Sanitized message of a transaction.
76#[derive(Debug, Clone, Eq, PartialEq)]
77pub enum SanitizedMessage {
78    /// Sanitized legacy message
79    Legacy(LegacyMessage<'static>),
80    /// Sanitized version #0 message with dynamically loaded addresses
81    V0(v0::LoadedMessage<'static>),
82}
83
84impl SanitizedMessage {
85    /// Create a sanitized message from a sanitized versioned message.
86    /// If the input message uses address tables, attempt to look up the
87    /// address for each table index.
88    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    /// Create a sanitized legacy message
110    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    /// Return true if this message contains duplicate account keys
122    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    /// Message header which identifies the number of signer and writable or
130    /// readonly accounts
131    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    /// Returns a legacy message if this sanitized message wraps one
139    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    /// Returns the fee payer for the transaction
148    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    /// The hash of a recent block, used for timing out a transaction
155    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    /// Program instructions that will be executed in sequence and committed in
163    /// one atomic transaction if all succeed.
164    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    /// Program instructions iterator which includes each instruction's program
172    /// id.
173    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    /// Return the list of statically included account keys.
187    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    /// Returns the list of account keys that are loaded for this message.
195    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    /// Returns the list of account keys used for account lookup tables.
203    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    /// Returns true if the account at the specified index is an input to some
211    /// program instruction in this message.
212    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    /// Returns true if the account at the specified index is invoked as a
223    /// program in this message.
224    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    /// Returns true if the account at the specified index is writable by the
232    /// instructions in this message.
233    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    /// Returns true if the account at the specified index signed this
241    /// message.
242    pub fn is_signer(&self, index: usize) -> bool {
243        index < usize::from(self.header().num_required_signatures)
244    }
245
246    /// Return the resolved addresses for this message if it has any.
247    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    /// Return the number of readonly accounts loaded by this message.
255    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    /// Decompile message instructions without cloning account keys
266    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    /// Inspect all message keys for the bpf upgradeable loader
293    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    /// Get a list of signers for the instruction at the given index
301    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    /// If the message uses a durable nonce, return the pubkey of the nonce account
316    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    /// Returns the total number of signatures in the message.
339    /// This includes required transaction signatures as well as any
340    /// pre-compile signatures that are attached in instructions.
341    pub fn num_total_signatures(&self) -> u64 {
342        self.get_signature_details().total_signatures()
343    }
344
345    /// Returns the number of requested write-locks in this message.
346    /// This does not consider if write-locks are demoted.
347    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    /// return detailed signature counts
354    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        // counting the number of pre-processor operations separately
361        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/// Transaction signature details including the number of transaction signatures
391/// and precompile signatures.
392#[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    /// return total number of signature, treating pre-processor operations as signature
416    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    /// return the number of transaction signatures
424    pub fn num_transaction_signatures(&self) -> u64 {
425        self.num_transaction_signatures
426    }
427
428    /// return the number of secp256k1 instruction signatures
429    pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
430        self.num_secp256k1_instruction_signatures
431    }
432
433    /// return the number of ed25519 instruction signatures
434    pub fn num_ed25519_instruction_signatures(&self) -> u64 {
435        self.num_ed25519_instruction_signatures
436    }
437
438    /// return the number of secp256r1 instruction signatures
439    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        // expect 2 required transaction signatures
663        assert_eq!(2, signature_details.num_transaction_signatures);
664        // expect 2 secp256k1 instruction signatures - 1 for each mock_secp2561k1_instr
665        assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
666        // expect 5 ed25519 instruction signatures from mock_ed25519_instr
667        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}