Skip to main content

solana_message/
sanitized.rs

1use {
2    crate::{
3        compiled_instruction::CompiledInstruction,
4        legacy,
5        v0::{self, LoadedAddresses},
6        v1::CachedMessage,
7        AccountKeys, AddressLoader, MessageHeader, SanitizedVersionedMessage, VersionedMessage,
8    },
9    alloc::{borrow::Cow, vec::Vec},
10    solana_address::Address,
11    solana_hash::Hash,
12    solana_instruction::{BorrowedAccountMeta, BorrowedInstruction},
13    solana_sanitize::Sanitize,
14    solana_sdk_ids::{ed25519_program, secp256k1_program, secp256r1_program},
15    solana_transaction_error::SanitizeMessageError,
16    std::collections::HashSet,
17};
18
19#[derive(Debug, Clone, Eq, PartialEq)]
20pub struct LegacyMessage<'a> {
21    /// Legacy message
22    pub message: Cow<'a, legacy::Message>,
23    /// List of boolean with same length as account_keys(), each boolean value indicates if
24    /// corresponding account key is writable or not.
25    pub is_writable_account_cache: Vec<bool>,
26}
27
28impl LegacyMessage<'_> {
29    pub fn new(message: legacy::Message, reserved_account_keys: &HashSet<Address>) -> Self {
30        let is_writable_account_cache = message
31            .account_keys
32            .iter()
33            .enumerate()
34            .map(|(i, _key)| {
35                message.is_writable_index(i)
36                    && !reserved_account_keys.contains(&message.account_keys[i])
37                    && !message.demote_program_id(i)
38            })
39            .collect::<Vec<_>>();
40        Self {
41            message: Cow::Owned(message),
42            is_writable_account_cache,
43        }
44    }
45
46    pub fn has_duplicates(&self) -> bool {
47        self.message.has_duplicates()
48    }
49
50    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
51        self.message.is_key_called_as_program(key_index)
52    }
53
54    /// Inspect all message keys for the bpf upgradeable loader
55    pub fn is_upgradeable_loader_present(&self) -> bool {
56        self.message.is_upgradeable_loader_present()
57    }
58
59    /// Returns the full list of account keys.
60    pub fn account_keys(&self) -> AccountKeys<'_> {
61        AccountKeys::new(&self.message.account_keys, None)
62    }
63
64    pub fn is_writable(&self, index: usize) -> bool {
65        *self.is_writable_account_cache.get(index).unwrap_or(&false)
66    }
67}
68
69/// Sanitized message of a transaction.
70#[derive(Debug, Clone, Eq, PartialEq)]
71pub enum SanitizedMessage {
72    /// Sanitized legacy message
73    Legacy(LegacyMessage<'static>),
74    /// Sanitized version #0 message with dynamically loaded addresses
75    V0(v0::LoadedMessage<'static>),
76    /// Sanitized version #1 message (4KB transactions, no address lookup tables)
77    V1(CachedMessage<'static>),
78}
79
80impl SanitizedMessage {
81    /// Create a sanitized message from a sanitized versioned message.
82    /// If the input message uses address tables, attempt to look up the
83    /// address for each table index.
84    pub fn try_new(
85        sanitized_msg: SanitizedVersionedMessage,
86        address_loader: impl AddressLoader,
87        reserved_account_keys: &HashSet<Address>,
88    ) -> Result<Self, SanitizeMessageError> {
89        Ok(match sanitized_msg.message {
90            VersionedMessage::Legacy(message) => {
91                SanitizedMessage::Legacy(LegacyMessage::new(message, reserved_account_keys))
92            }
93            VersionedMessage::V0(message) => {
94                let loaded_addresses =
95                    address_loader.load_addresses(&message.address_table_lookups)?;
96                SanitizedMessage::V0(v0::LoadedMessage::new(
97                    message,
98                    loaded_addresses,
99                    reserved_account_keys,
100                ))
101            }
102            VersionedMessage::V1(message) => {
103                SanitizedMessage::V1(CachedMessage::new(message, reserved_account_keys))
104            }
105        })
106    }
107
108    /// Create a sanitized legacy message
109    pub fn try_from_legacy_message(
110        message: legacy::Message,
111        reserved_account_keys: &HashSet<Address>,
112    ) -> Result<Self, SanitizeMessageError> {
113        message.sanitize()?;
114        Ok(Self::Legacy(LegacyMessage::new(
115            message,
116            reserved_account_keys,
117        )))
118    }
119
120    /// Return true if this message contains duplicate account keys
121    pub fn has_duplicates(&self) -> bool {
122        match self {
123            SanitizedMessage::Legacy(message) => message.has_duplicates(),
124            SanitizedMessage::V0(message) => message.has_duplicates(),
125            SanitizedMessage::V1(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            Self::V1(cached_msg) => &cached_msg.message.header,
136        }
137    }
138
139    /// Returns a legacy message if this sanitized message wraps one
140    pub fn legacy_message(&self) -> Option<&legacy::Message> {
141        if let Self::Legacy(legacy_message) = &self {
142            Some(&legacy_message.message)
143        } else {
144            None
145        }
146    }
147
148    /// Returns the fee payer for the transaction
149    pub fn fee_payer(&self) -> &Address {
150        self.account_keys()
151            .get(0)
152            .expect("sanitized messages always have a fee payer at index 0")
153    }
154
155    /// The hash of a recent block, used for timing out a transaction
156    pub fn recent_blockhash(&self) -> &Hash {
157        match self {
158            Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
159            Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
160            Self::V1(cached_msg) => &cached_msg.message.lifetime_specifier,
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            Self::V1(cached_msg) => &cached_msg.message.instructions,
171        }
172    }
173
174    /// Program instructions iterator which includes each instruction's program
175    /// id.
176    pub fn program_instructions_iter(
177        &self,
178    ) -> impl Iterator<Item = (&Address, &CompiledInstruction)> + Clone {
179        self.instructions().iter().map(move |ix| {
180            (
181                self.account_keys()
182                    .get(usize::from(ix.program_id_index))
183                    .expect("program id index is sanitized"),
184                ix,
185            )
186        })
187    }
188
189    /// Return the list of statically included account keys.
190    pub fn static_account_keys(&self) -> &[Address] {
191        match self {
192            Self::Legacy(legacy_message) => &legacy_message.message.account_keys,
193            Self::V0(loaded_msg) => &loaded_msg.message.account_keys,
194            Self::V1(cached_msg) => &cached_msg.message.account_keys,
195        }
196    }
197
198    /// Returns the list of account keys that are loaded for this message.
199    pub fn account_keys(&self) -> AccountKeys<'_> {
200        match self {
201            Self::Legacy(message) => message.account_keys(),
202            Self::V0(message) => message.account_keys(),
203            Self::V1(message) => message.account_keys(),
204        }
205    }
206
207    /// Returns the list of account keys used for account lookup tables.
208    pub fn message_address_table_lookups(&self) -> &[v0::MessageAddressTableLookup] {
209        match self {
210            Self::V0(message) => &message.message.address_table_lookups,
211            // Legacy and V1 messages do not have address table lookups.
212            _ => &[],
213        }
214    }
215
216    /// Returns true if the account at the specified index is invoked as a
217    /// program in this message.
218    pub fn is_invoked(&self, key_index: usize) -> bool {
219        match self {
220            Self::Legacy(message) => message.is_key_called_as_program(key_index),
221            Self::V0(message) => message.is_key_called_as_program(key_index),
222            Self::V1(message) => message.is_key_called_as_program(key_index),
223        }
224    }
225
226    /// Returns true if the account at the specified index is writable by the
227    /// instructions in this message.
228    pub fn is_writable(&self, index: usize) -> bool {
229        match self {
230            Self::Legacy(message) => message.is_writable(index),
231            Self::V0(message) => message.is_writable(index),
232            Self::V1(message) => message.is_writable(index),
233        }
234    }
235
236    /// Returns true if the account at the specified index signed this
237    /// message.
238    pub fn is_signer(&self, index: usize) -> bool {
239        index < usize::from(self.header().num_required_signatures)
240    }
241
242    /// Return the resolved addresses for this message if it has any.
243    fn loaded_lookup_table_addresses(&self) -> Option<&LoadedAddresses> {
244        match &self {
245            SanitizedMessage::V0(message) => Some(&message.loaded_addresses),
246            _ => None,
247        }
248    }
249
250    /// Return the number of readonly accounts loaded by this message.
251    pub fn num_readonly_accounts(&self) -> usize {
252        let loaded_readonly_addresses = self
253            .loaded_lookup_table_addresses()
254            .map(|keys| keys.readonly.len())
255            .unwrap_or_default();
256        loaded_readonly_addresses
257            .saturating_add(usize::from(self.header().num_readonly_signed_accounts))
258            .saturating_add(usize::from(self.header().num_readonly_unsigned_accounts))
259    }
260
261    /// Decompile message instructions without cloning account keys
262    pub fn decompile_instructions(&self) -> Vec<BorrowedInstruction<'_>> {
263        let account_keys = self.account_keys();
264        self.program_instructions_iter()
265            .map(|(program_id, instruction)| {
266                let accounts = instruction
267                    .accounts
268                    .iter()
269                    .map(|account_index| {
270                        let account_index = *account_index as usize;
271                        BorrowedAccountMeta {
272                            is_signer: self.is_signer(account_index),
273                            is_writable: self.is_writable(account_index),
274                            pubkey: account_keys.get(account_index).unwrap(),
275                        }
276                    })
277                    .collect();
278
279                BorrowedInstruction {
280                    accounts,
281                    data: &instruction.data,
282                    program_id,
283                }
284            })
285            .collect()
286    }
287
288    /// Inspect all message keys for the bpf upgradeable loader
289    pub fn is_upgradeable_loader_present(&self) -> bool {
290        match self {
291            Self::Legacy(message) => message.is_upgradeable_loader_present(),
292            Self::V0(message) => message.is_upgradeable_loader_present(),
293            Self::V1(message) => message.is_upgradeable_loader_present(),
294        }
295    }
296
297    /// Returns the total number of signatures in the message.
298    /// This includes required transaction signatures as well as any
299    /// pre-compile signatures that are attached in instructions.
300    pub fn num_total_signatures(&self) -> u64 {
301        self.get_signature_details().total_signatures()
302    }
303
304    /// Returns the number of requested write-locks in this message.
305    /// This does not consider if write-locks are demoted.
306    pub fn num_write_locks(&self) -> u64 {
307        self.account_keys()
308            .len()
309            .saturating_sub(self.num_readonly_accounts()) as u64
310    }
311
312    /// return detailed signature counts
313    pub fn get_signature_details(&self) -> TransactionSignatureDetails {
314        let mut transaction_signature_details = TransactionSignatureDetails {
315            num_transaction_signatures: u64::from(self.header().num_required_signatures),
316            ..TransactionSignatureDetails::default()
317        };
318
319        // counting the number of pre-processor operations separately
320        for (program_id, instruction) in self.program_instructions_iter() {
321            if secp256k1_program::check_id(program_id) {
322                if let Some(num_verifies) = instruction.data.first() {
323                    transaction_signature_details.num_secp256k1_instruction_signatures =
324                        transaction_signature_details
325                            .num_secp256k1_instruction_signatures
326                            .saturating_add(u64::from(*num_verifies));
327                }
328            } else if ed25519_program::check_id(program_id) {
329                if let Some(num_verifies) = instruction.data.first() {
330                    transaction_signature_details.num_ed25519_instruction_signatures =
331                        transaction_signature_details
332                            .num_ed25519_instruction_signatures
333                            .saturating_add(u64::from(*num_verifies));
334                }
335            } else if secp256r1_program::check_id(program_id) {
336                if let Some(num_verifies) = instruction.data.first() {
337                    transaction_signature_details.num_secp256r1_instruction_signatures =
338                        transaction_signature_details
339                            .num_secp256r1_instruction_signatures
340                            .saturating_add(u64::from(*num_verifies));
341                }
342            }
343        }
344
345        transaction_signature_details
346    }
347}
348
349/// Transaction signature details including the number of transaction signatures
350/// and precompile signatures.
351#[derive(Clone, Debug, Default)]
352pub struct TransactionSignatureDetails {
353    num_transaction_signatures: u64,
354    num_secp256k1_instruction_signatures: u64,
355    num_ed25519_instruction_signatures: u64,
356    num_secp256r1_instruction_signatures: u64,
357}
358
359impl TransactionSignatureDetails {
360    pub const fn new(
361        num_transaction_signatures: u64,
362        num_secp256k1_instruction_signatures: u64,
363        num_ed25519_instruction_signatures: u64,
364        num_secp256r1_instruction_signatures: u64,
365    ) -> Self {
366        Self {
367            num_transaction_signatures,
368            num_secp256k1_instruction_signatures,
369            num_ed25519_instruction_signatures,
370            num_secp256r1_instruction_signatures,
371        }
372    }
373
374    /// return total number of signature, treating pre-processor operations as signature
375    pub fn total_signatures(&self) -> u64 {
376        self.num_transaction_signatures
377            .saturating_add(self.num_secp256k1_instruction_signatures)
378            .saturating_add(self.num_ed25519_instruction_signatures)
379            .saturating_add(self.num_secp256r1_instruction_signatures)
380    }
381
382    /// return the number of transaction signatures
383    pub fn num_transaction_signatures(&self) -> u64 {
384        self.num_transaction_signatures
385    }
386
387    /// return the number of secp256k1 instruction signatures
388    pub fn num_secp256k1_instruction_signatures(&self) -> u64 {
389        self.num_secp256k1_instruction_signatures
390    }
391
392    /// return the number of ed25519 instruction signatures
393    pub fn num_ed25519_instruction_signatures(&self) -> u64 {
394        self.num_ed25519_instruction_signatures
395    }
396
397    /// return the number of secp256r1 instruction signatures
398    pub fn num_secp256r1_instruction_signatures(&self) -> u64 {
399        self.num_secp256r1_instruction_signatures
400    }
401}
402
403#[cfg(test)]
404mod tests {
405    use {super::*, crate::v0, alloc::vec, std::collections::HashSet};
406
407    #[test]
408    fn test_try_from_legacy_message() {
409        let legacy_message_with_no_signers = legacy::Message {
410            account_keys: vec![Address::new_unique()],
411            ..legacy::Message::default()
412        };
413
414        assert_eq!(
415            SanitizedMessage::try_from_legacy_message(
416                legacy_message_with_no_signers,
417                &HashSet::default(),
418            )
419            .err(),
420            Some(SanitizeMessageError::IndexOutOfBounds),
421        );
422    }
423
424    #[test]
425    fn test_num_readonly_accounts() {
426        let key0 = Address::new_unique();
427        let key1 = Address::new_unique();
428        let key2 = Address::new_unique();
429        let key3 = Address::new_unique();
430        let key4 = Address::new_unique();
431        let key5 = Address::new_unique();
432
433        let legacy_message = SanitizedMessage::try_from_legacy_message(
434            legacy::Message {
435                header: MessageHeader {
436                    num_required_signatures: 2,
437                    num_readonly_signed_accounts: 1,
438                    num_readonly_unsigned_accounts: 1,
439                },
440                account_keys: vec![key0, key1, key2, key3],
441                ..legacy::Message::default()
442            },
443            &HashSet::default(),
444        )
445        .unwrap();
446
447        assert_eq!(legacy_message.num_readonly_accounts(), 2);
448
449        let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
450            v0::Message {
451                header: MessageHeader {
452                    num_required_signatures: 2,
453                    num_readonly_signed_accounts: 1,
454                    num_readonly_unsigned_accounts: 1,
455                },
456                account_keys: vec![key0, key1, key2, key3],
457                ..v0::Message::default()
458            },
459            LoadedAddresses {
460                writable: vec![key4],
461                readonly: vec![key5],
462            },
463            &HashSet::default(),
464        ));
465
466        assert_eq!(v0_message.num_readonly_accounts(), 3);
467    }
468
469    #[test]
470    #[allow(clippy::get_first)]
471    fn test_is_writable_account_cache() {
472        let key0 = Address::new_unique();
473        let key1 = Address::new_unique();
474        let key2 = Address::new_unique();
475        let key3 = Address::new_unique();
476        let key4 = Address::new_unique();
477        let key5 = Address::new_unique();
478
479        let legacy_message = SanitizedMessage::try_from_legacy_message(
480            legacy::Message {
481                header: MessageHeader {
482                    num_required_signatures: 2,
483                    num_readonly_signed_accounts: 1,
484                    num_readonly_unsigned_accounts: 1,
485                },
486                account_keys: vec![key0, key1, key2, key3],
487                ..legacy::Message::default()
488            },
489            &HashSet::default(),
490        )
491        .unwrap();
492        match legacy_message {
493            SanitizedMessage::Legacy(message) => {
494                assert_eq!(
495                    message.is_writable_account_cache.len(),
496                    message.account_keys().len()
497                );
498                assert!(message.is_writable_account_cache.get(0).unwrap());
499                assert!(!message.is_writable_account_cache.get(1).unwrap());
500                assert!(message.is_writable_account_cache.get(2).unwrap());
501                assert!(!message.is_writable_account_cache.get(3).unwrap());
502            }
503            _ => {
504                panic!("Expect to be SanitizedMessage::LegacyMessage")
505            }
506        }
507
508        let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
509            v0::Message {
510                header: MessageHeader {
511                    num_required_signatures: 2,
512                    num_readonly_signed_accounts: 1,
513                    num_readonly_unsigned_accounts: 1,
514                },
515                account_keys: vec![key0, key1, key2, key3],
516                ..v0::Message::default()
517            },
518            LoadedAddresses {
519                writable: vec![key4],
520                readonly: vec![key5],
521            },
522            &HashSet::default(),
523        ));
524        match v0_message {
525            SanitizedMessage::V0(message) => {
526                assert_eq!(
527                    message.is_writable_account_cache.len(),
528                    message.account_keys().len()
529                );
530                assert!(message.is_writable_account_cache.get(0).unwrap());
531                assert!(!message.is_writable_account_cache.get(1).unwrap());
532                assert!(message.is_writable_account_cache.get(2).unwrap());
533                assert!(!message.is_writable_account_cache.get(3).unwrap());
534                assert!(message.is_writable_account_cache.get(4).unwrap());
535                assert!(!message.is_writable_account_cache.get(5).unwrap());
536            }
537            _ => {
538                panic!("Expect to be SanitizedMessage::V0")
539            }
540        }
541    }
542
543    #[test]
544    fn test_get_signature_details() {
545        let key0 = Address::new_unique();
546        let key1 = Address::new_unique();
547        let loader_key = Address::new_unique();
548
549        let loader_instr = CompiledInstruction::new(2, &(), vec![0, 1]);
550        let mock_secp256k1_instr = CompiledInstruction::new(3, &[1u8; 10], vec![]);
551        let mock_ed25519_instr = CompiledInstruction::new(4, &[5u8; 10], vec![]);
552
553        let message = SanitizedMessage::try_from_legacy_message(
554            legacy::Message::new_with_compiled_instructions(
555                2,
556                1,
557                2,
558                vec![
559                    key0,
560                    key1,
561                    loader_key,
562                    secp256k1_program::id(),
563                    ed25519_program::id(),
564                ],
565                Hash::default(),
566                vec![
567                    loader_instr,
568                    mock_secp256k1_instr.clone(),
569                    mock_ed25519_instr,
570                    mock_secp256k1_instr,
571                ],
572            ),
573            &HashSet::new(),
574        )
575        .unwrap();
576
577        let signature_details = message.get_signature_details();
578        // expect 2 required transaction signatures
579        assert_eq!(2, signature_details.num_transaction_signatures);
580        // expect 2 secp256k1 instruction signatures - 1 for each mock_secp2561k1_instr
581        assert_eq!(2, signature_details.num_secp256k1_instruction_signatures);
582        // expect 5 ed25519 instruction signatures from mock_ed25519_instr
583        assert_eq!(5, signature_details.num_ed25519_instruction_signatures);
584    }
585
586    #[test]
587    fn test_static_account_keys() {
588        let keys = vec![
589            Address::new_unique(),
590            Address::new_unique(),
591            Address::new_unique(),
592        ];
593
594        let header = MessageHeader {
595            num_required_signatures: 2,
596            num_readonly_signed_accounts: 1,
597            num_readonly_unsigned_accounts: 1,
598        };
599
600        let legacy_message = SanitizedMessage::try_from_legacy_message(
601            legacy::Message {
602                header,
603                account_keys: keys.clone(),
604                ..legacy::Message::default()
605            },
606            &HashSet::default(),
607        )
608        .unwrap();
609        assert_eq!(legacy_message.static_account_keys(), &keys);
610
611        let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
612            v0::Message {
613                header,
614                account_keys: keys.clone(),
615                ..v0::Message::default()
616            },
617            LoadedAddresses {
618                writable: vec![],
619                readonly: vec![],
620            },
621            &HashSet::default(),
622        ));
623        assert_eq!(v0_message.static_account_keys(), &keys);
624
625        let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
626            v0::Message {
627                header,
628                account_keys: keys.clone(),
629                ..v0::Message::default()
630            },
631            LoadedAddresses {
632                writable: vec![Address::new_unique()],
633                readonly: vec![Address::new_unique()],
634            },
635            &HashSet::default(),
636        ));
637        assert_eq!(v0_message.static_account_keys(), &keys);
638    }
639}