Skip to main content

solana_svm_transaction/
svm_message.rs

1use {
2    crate::{
3        instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
4    },
5    core::fmt::Debug,
6    solana_hash::Hash,
7    solana_message::AccountKeys,
8    solana_pubkey::Pubkey,
9    solana_sdk_ids::{ed25519_program, secp256k1_program, secp256r1_program, system_program},
10};
11
12mod sanitized_message;
13mod sanitized_transaction;
14// inlined to avoid solana-nonce dep
15#[cfg(test)]
16static_assertions::const_assert_eq!(
17    NONCED_TX_MARKER_IX_INDEX,
18    solana_nonce::NONCED_TX_MARKER_IX_INDEX
19);
20const NONCED_TX_MARKER_IX_INDEX: u8 = 0;
21
22// - Debug to support legacy logging
23pub trait SVMMessage: Debug {
24    /// Return the number of transaction-level signatures in the message.
25    fn num_transaction_signatures(&self) -> u64;
26    /// Return the number of ed25519 precompile signatures in the message.
27    fn num_ed25519_signatures(&self) -> u64 {
28        default_precompile_signature_count(&ed25519_program::ID, self.program_instructions_iter())
29    }
30    /// Return the number of secp256k1 precompile signatures in the message.
31    fn num_secp256k1_signatures(&self) -> u64 {
32        default_precompile_signature_count(&secp256k1_program::ID, self.program_instructions_iter())
33    }
34    /// Return the number of secp256r1 precompile signatures in the message.
35    fn num_secp256r1_signatures(&self) -> u64 {
36        default_precompile_signature_count(&secp256r1_program::ID, self.program_instructions_iter())
37    }
38
39    /// Returns the number of requested write-locks in this message.
40    /// This does not consider if write-locks are demoted.
41    fn num_write_locks(&self) -> u64;
42
43    /// Return the recent blockhash.
44    fn recent_blockhash(&self) -> &Hash;
45
46    /// Return the number of instructions in the message.
47    fn num_instructions(&self) -> usize;
48
49    /// Return an iterator over the instructions in the message.
50    fn instructions_iter(&self) -> impl Iterator<Item = SVMInstruction<'_>>;
51
52    /// Return an iterator over the instructions in the message, paired with
53    /// the pubkey of the program.
54    fn program_instructions_iter(
55        &self,
56    ) -> impl Iterator<Item = (&Pubkey, SVMInstruction<'_>)> + Clone;
57
58    /// Return the list of static account keys.
59    fn static_account_keys(&self) -> &[Pubkey];
60
61    /// Return the account keys.
62    fn account_keys(&self) -> AccountKeys<'_>;
63
64    /// Return the fee-payer
65    fn fee_payer(&self) -> &Pubkey;
66
67    /// Returns `true` if the account at `index` is writable.
68    fn is_writable(&self, index: usize) -> bool;
69
70    /// Returns `true` if the account at `index` is signer.
71    fn is_signer(&self, index: usize) -> bool;
72
73    /// Returns true if the account at the specified index is invoked as a
74    /// program in top-level instructions of this message.
75    fn is_invoked(&self, key_index: usize) -> bool;
76
77    /// Returns true if the account at the specified index is an input to some
78    /// program instruction in this message.
79    fn is_instruction_account(&self, key_index: usize) -> bool {
80        if let Ok(key_index) = u8::try_from(key_index) {
81            self.instructions_iter()
82                .any(|ix| ix.accounts.contains(&key_index))
83        } else {
84            false
85        }
86    }
87
88    /// If the message uses a durable nonce, return the pubkey of the nonce account
89    fn get_durable_nonce(&self, require_static_nonce_account: bool) -> Option<&Pubkey> {
90        let account_keys = self.account_keys();
91        self.instructions_iter()
92            .nth(usize::from(NONCED_TX_MARKER_IX_INDEX))
93            .filter(
94                |ix| match account_keys.get(usize::from(ix.program_id_index)) {
95                    Some(program_id) => system_program::check_id(program_id),
96                    _ => false,
97                },
98            )
99            .filter(|ix| {
100                /// Serialized value of [`SystemInstruction::AdvanceNonceAccount`].
101                const SERIALIZED_ADVANCE_NONCE_ACCOUNT: [u8; 4] = 4u32.to_le_bytes();
102                const SERIALIZED_SIZE: usize = SERIALIZED_ADVANCE_NONCE_ACCOUNT.len();
103
104                ix.data
105                    .get(..SERIALIZED_SIZE)
106                    .map(|data| data == SERIALIZED_ADVANCE_NONCE_ACCOUNT)
107                    .unwrap_or(false)
108            })
109            .and_then(|ix| {
110                ix.accounts.first().and_then(|idx| {
111                    let index = usize::from(*idx);
112                    if (require_static_nonce_account && index >= self.static_account_keys().len())
113                        || !self.is_writable(index)
114                    {
115                        None
116                    } else {
117                        account_keys.get(index)
118                    }
119                })
120            })
121    }
122
123    /// For the instruction at `index`, return an iterator over input accounts
124    /// that are signers.
125    fn get_ix_signers(&self, index: usize) -> impl Iterator<Item = &Pubkey> {
126        self.instructions_iter()
127            .nth(index)
128            .into_iter()
129            .flat_map(|ix| {
130                ix.accounts
131                    .iter()
132                    .copied()
133                    .map(usize::from)
134                    .filter(|index| self.is_signer(*index))
135                    .filter_map(|signer_index| self.account_keys().get(signer_index))
136            })
137    }
138
139    /// Get the number of lookup tables.
140    fn num_lookup_tables(&self) -> usize;
141
142    /// Get message address table lookups used in the message
143    fn message_address_table_lookups(
144        &self,
145    ) -> impl Iterator<Item = SVMMessageAddressTableLookup<'_>>;
146}
147
148fn default_precompile_signature_count<'a>(
149    precompile: &Pubkey,
150    instructions: impl Iterator<Item = (&'a Pubkey, SVMInstruction<'a>)>,
151) -> u64 {
152    instructions
153        .filter(|(program_id, _)| *program_id == precompile)
154        .map(|(_, ix)| u64::from(ix.data.first().copied().unwrap_or(0)))
155        .sum()
156}