solana_program/message/
sanitized.rs

1use {
2    crate::{
3        hash::Hash,
4        instruction::CompiledInstruction,
5        message::{
6            legacy,
7            v0::{self, LoadedAddresses},
8            AccountKeys, AddressLoader, AddressLoaderError, MessageHeader,
9            SanitizedVersionedMessage, VersionedMessage,
10        },
11        nonce::NONCED_TX_MARKER_IX_INDEX,
12        program_utils::limited_deserialize,
13        pubkey::Pubkey,
14        sanitize::{Sanitize, SanitizeError},
15        solana_program::{system_instruction::SystemInstruction, system_program},
16        sysvar::instructions::{BorrowedAccountMeta, BorrowedInstruction},
17    },
18    std::{borrow::Cow, convert::TryFrom},
19    thiserror::Error,
20};
21
22#[derive(Debug, Clone)]
23pub struct LegacyMessage<'a> {
24    /// Legacy message
25    pub message: Cow<'a, legacy::Message>,
26    /// List of boolean with same length as account_keys(), each boolean value indicates if
27    /// corresponding account key is writable or not.
28    pub is_writable_account_cache: Vec<bool>,
29}
30
31impl<'a> LegacyMessage<'a> {
32    pub fn new(message: legacy::Message) -> Self {
33        let is_writable_account_cache = message
34            .account_keys
35            .iter()
36            .enumerate()
37            .map(|(i, _key)| message.is_writable(i))
38            .collect::<Vec<_>>();
39        Self {
40            message: Cow::Owned(message),
41            is_writable_account_cache,
42        }
43    }
44
45    pub fn has_duplicates(&self) -> bool {
46        self.message.has_duplicates()
47    }
48
49    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
50        self.message.is_key_called_as_program(key_index)
51    }
52
53    /// Inspect all message keys for the bpf upgradeable loader
54    pub fn is_upgradeable_loader_present(&self) -> bool {
55        self.message.is_upgradeable_loader_present()
56    }
57
58    /// Returns the full list of account keys.
59    pub fn account_keys(&self) -> AccountKeys {
60        AccountKeys::new(&self.message.account_keys, None)
61    }
62
63    pub fn is_writable(&self, index: usize) -> bool {
64        *self.is_writable_account_cache.get(index).unwrap_or(&false)
65    }
66}
67
68/// Sanitized message of a transaction.
69#[derive(Debug, Clone)]
70pub enum SanitizedMessage {
71    /// Sanitized legacy message
72    Legacy(LegacyMessage<'static>),
73    /// Sanitized version #0 message with dynamically loaded addresses
74    V0(v0::LoadedMessage<'static>),
75}
76
77#[derive(PartialEq, Debug, Error, Eq, Clone)]
78pub enum SanitizeMessageError {
79    #[error("index out of bounds")]
80    IndexOutOfBounds,
81    #[error("value out of bounds")]
82    ValueOutOfBounds,
83    #[error("invalid value")]
84    InvalidValue,
85    #[error("{0}")]
86    AddressLoaderError(#[from] AddressLoaderError),
87}
88
89impl From<SanitizeError> for SanitizeMessageError {
90    fn from(err: SanitizeError) -> Self {
91        match err {
92            SanitizeError::IndexOutOfBounds => Self::IndexOutOfBounds,
93            SanitizeError::ValueOutOfBounds => Self::ValueOutOfBounds,
94            SanitizeError::InvalidValue => Self::InvalidValue,
95        }
96    }
97}
98
99impl TryFrom<legacy::Message> for SanitizedMessage {
100    type Error = SanitizeMessageError;
101    fn try_from(message: legacy::Message) -> Result<Self, Self::Error> {
102        message.sanitize()?;
103        Ok(Self::Legacy(LegacyMessage::new(message)))
104    }
105}
106
107impl SanitizedMessage {
108    /// Create a sanitized message from a sanitized versioned message.
109    /// If the input message uses address tables, attempt to look up the
110    /// address for each table index.
111    pub fn try_new(
112        sanitized_msg: SanitizedVersionedMessage,
113        address_loader: impl AddressLoader,
114    ) -> Result<Self, SanitizeMessageError> {
115        Ok(match sanitized_msg.message {
116            VersionedMessage::Legacy(message) => {
117                SanitizedMessage::Legacy(LegacyMessage::new(message))
118            }
119            VersionedMessage::V0(message) => {
120                let loaded_addresses =
121                    address_loader.load_addresses(&message.address_table_lookups)?;
122                SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
123            }
124        })
125    }
126
127    /// Return true if this message contains duplicate account keys
128    pub fn has_duplicates(&self) -> bool {
129        match self {
130            SanitizedMessage::Legacy(message) => message.has_duplicates(),
131            SanitizedMessage::V0(message) => message.has_duplicates(),
132        }
133    }
134
135    /// Message header which identifies the number of signer and writable or
136    /// readonly accounts
137    pub fn header(&self) -> &MessageHeader {
138        match self {
139            Self::Legacy(legacy_message) => &legacy_message.message.header,
140            Self::V0(loaded_msg) => &loaded_msg.message.header,
141        }
142    }
143
144    /// Returns a legacy message if this sanitized message wraps one
145    pub fn legacy_message(&self) -> Option<&legacy::Message> {
146        if let Self::Legacy(legacy_message) = &self {
147            Some(&legacy_message.message)
148        } else {
149            None
150        }
151    }
152
153    /// Returns the fee payer for the transaction
154    pub fn fee_payer(&self) -> &Pubkey {
155        self.account_keys()
156            .get(0)
157            .expect("sanitized message always has non-program fee payer at index 0")
158    }
159
160    /// The hash of a recent block, used for timing out a transaction
161    pub fn recent_blockhash(&self) -> &Hash {
162        match self {
163            Self::Legacy(legacy_message) => &legacy_message.message.recent_blockhash,
164            Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
165        }
166    }
167
168    /// Program instructions that will be executed in sequence and committed in
169    /// one atomic transaction if all succeed.
170    pub fn instructions(&self) -> &[CompiledInstruction] {
171        match self {
172            Self::Legacy(legacy_message) => &legacy_message.message.instructions,
173            Self::V0(loaded_msg) => &loaded_msg.message.instructions,
174        }
175    }
176
177    /// Program instructions iterator which includes each instruction's program
178    /// id.
179    pub fn program_instructions_iter(
180        &self,
181    ) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
182        self.instructions().iter().map(move |ix| {
183            (
184                self.account_keys()
185                    .get(usize::from(ix.program_id_index))
186                    .expect("program id index is sanitized"),
187                ix,
188            )
189        })
190    }
191
192    /// Returns the list of account keys that are loaded for this message.
193    pub fn account_keys(&self) -> AccountKeys {
194        match self {
195            Self::Legacy(message) => message.account_keys(),
196            Self::V0(message) => message.account_keys(),
197        }
198    }
199
200    /// Returns true if the account at the specified index is an input to some
201    /// program instruction in this message.
202    fn is_key_passed_to_program(&self, key_index: usize) -> bool {
203        if let Ok(key_index) = u8::try_from(key_index) {
204            self.instructions()
205                .iter()
206                .any(|ix| ix.accounts.contains(&key_index))
207        } else {
208            false
209        }
210    }
211
212    /// Returns true if the account at the specified index is invoked as a
213    /// program in this message.
214    pub fn is_invoked(&self, key_index: usize) -> bool {
215        match self {
216            Self::Legacy(message) => message.is_key_called_as_program(key_index),
217            Self::V0(message) => message.is_key_called_as_program(key_index),
218        }
219    }
220
221    /// Returns true if the account at the specified index is not invoked as a
222    /// program or, if invoked, is passed to a program.
223    pub fn is_non_loader_key(&self, key_index: usize) -> bool {
224        !self.is_invoked(key_index) || self.is_key_passed_to_program(key_index)
225    }
226
227    /// Returns true if the account at the specified index is writable by the
228    /// instructions in this message.
229    pub fn is_writable(&self, index: usize) -> bool {
230        match self {
231            Self::Legacy(message) => message.is_writable(index),
232            Self::V0(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        }
294    }
295
296    /// Get a list of signers for the instruction at the given index
297    pub fn get_ix_signers(&self, ix_index: usize) -> impl Iterator<Item = &Pubkey> {
298        self.instructions()
299            .get(ix_index)
300            .into_iter()
301            .flat_map(|ix| {
302                ix.accounts
303                    .iter()
304                    .copied()
305                    .map(usize::from)
306                    .filter(|index| self.is_signer(*index))
307                    .filter_map(|signer_index| self.account_keys().get(signer_index))
308            })
309    }
310
311    /// If the message uses a durable nonce, return the pubkey of the nonce account
312    pub fn get_durable_nonce(&self) -> Option<&Pubkey> {
313        self.instructions()
314            .get(NONCED_TX_MARKER_IX_INDEX as usize)
315            .filter(
316                |ix| match self.account_keys().get(ix.program_id_index as usize) {
317                    Some(program_id) => system_program::check_id(program_id),
318                    _ => false,
319                },
320            )
321            .filter(|ix| {
322                matches!(
323                    limited_deserialize(&ix.data, 4 /* serialized size of AdvanceNonceAccount */),
324                    Ok(SystemInstruction::AdvanceNonceAccount)
325                )
326            })
327            .and_then(|ix| {
328                ix.accounts.first().and_then(|idx| {
329                    let idx = *idx as usize;
330                    if !self.is_writable(idx) {
331                        None
332                    } else {
333                        self.account_keys().get(idx)
334                    }
335                })
336            })
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use {super::*, crate::message::v0, std::collections::HashSet};
343
344    #[test]
345    fn test_try_from_message() {
346        let legacy_message_with_no_signers = legacy::Message {
347            account_keys: vec![Pubkey::new_unique()],
348            ..legacy::Message::default()
349        };
350
351        assert_eq!(
352            SanitizedMessage::try_from(legacy_message_with_no_signers).err(),
353            Some(SanitizeMessageError::IndexOutOfBounds),
354        );
355    }
356
357    #[test]
358    fn test_is_non_loader_key() {
359        let key0 = Pubkey::new_unique();
360        let key1 = Pubkey::new_unique();
361        let loader_key = Pubkey::new_unique();
362        let instructions = vec![
363            CompiledInstruction::new(1, &(), vec![0]),
364            CompiledInstruction::new(2, &(), vec![0, 1]),
365        ];
366
367        let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
368            1,
369            0,
370            2,
371            vec![key0, key1, loader_key],
372            Hash::default(),
373            instructions,
374        ))
375        .unwrap();
376
377        assert!(message.is_non_loader_key(0));
378        assert!(message.is_non_loader_key(1));
379        assert!(!message.is_non_loader_key(2));
380    }
381
382    #[test]
383    fn test_num_readonly_accounts() {
384        let key0 = Pubkey::new_unique();
385        let key1 = Pubkey::new_unique();
386        let key2 = Pubkey::new_unique();
387        let key3 = Pubkey::new_unique();
388        let key4 = Pubkey::new_unique();
389        let key5 = Pubkey::new_unique();
390
391        let legacy_message = SanitizedMessage::try_from(legacy::Message {
392            header: MessageHeader {
393                num_required_signatures: 2,
394                num_readonly_signed_accounts: 1,
395                num_readonly_unsigned_accounts: 1,
396            },
397            account_keys: vec![key0, key1, key2, key3],
398            ..legacy::Message::default()
399        })
400        .unwrap();
401
402        assert_eq!(legacy_message.num_readonly_accounts(), 2);
403
404        let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
405            v0::Message {
406                header: MessageHeader {
407                    num_required_signatures: 2,
408                    num_readonly_signed_accounts: 1,
409                    num_readonly_unsigned_accounts: 1,
410                },
411                account_keys: vec![key0, key1, key2, key3],
412                ..v0::Message::default()
413            },
414            LoadedAddresses {
415                writable: vec![key4],
416                readonly: vec![key5],
417            },
418        ));
419
420        assert_eq!(v0_message.num_readonly_accounts(), 3);
421    }
422
423    #[test]
424    fn test_get_ix_signers() {
425        let signer0 = Pubkey::new_unique();
426        let signer1 = Pubkey::new_unique();
427        let non_signer = Pubkey::new_unique();
428        let loader_key = Pubkey::new_unique();
429        let instructions = vec![
430            CompiledInstruction::new(3, &(), vec![2, 0]),
431            CompiledInstruction::new(3, &(), vec![0, 1]),
432            CompiledInstruction::new(3, &(), vec![0, 0]),
433        ];
434
435        let message = SanitizedMessage::try_from(legacy::Message::new_with_compiled_instructions(
436            2,
437            1,
438            2,
439            vec![signer0, signer1, non_signer, loader_key],
440            Hash::default(),
441            instructions,
442        ))
443        .unwrap();
444
445        assert_eq!(
446            message.get_ix_signers(0).collect::<HashSet<_>>(),
447            HashSet::from_iter([&signer0])
448        );
449        assert_eq!(
450            message.get_ix_signers(1).collect::<HashSet<_>>(),
451            HashSet::from_iter([&signer0, &signer1])
452        );
453        assert_eq!(
454            message.get_ix_signers(2).collect::<HashSet<_>>(),
455            HashSet::from_iter([&signer0])
456        );
457        assert_eq!(
458            message.get_ix_signers(3).collect::<HashSet<_>>(),
459            HashSet::default()
460        );
461    }
462
463    #[test]
464    fn test_is_writable_account_cache() {
465        let key0 = Pubkey::new_unique();
466        let key1 = Pubkey::new_unique();
467        let key2 = Pubkey::new_unique();
468        let key3 = Pubkey::new_unique();
469        let key4 = Pubkey::new_unique();
470        let key5 = Pubkey::new_unique();
471
472        let legacy_message = SanitizedMessage::try_from(legacy::Message {
473            header: MessageHeader {
474                num_required_signatures: 2,
475                num_readonly_signed_accounts: 1,
476                num_readonly_unsigned_accounts: 1,
477            },
478            account_keys: vec![key0, key1, key2, key3],
479            ..legacy::Message::default()
480        })
481        .unwrap();
482        match legacy_message {
483            SanitizedMessage::Legacy(message) => {
484                assert_eq!(
485                    message.is_writable_account_cache.len(),
486                    message.account_keys().len()
487                );
488                assert!(message.is_writable_account_cache.get(0).unwrap());
489                assert!(!message.is_writable_account_cache.get(1).unwrap());
490                assert!(message.is_writable_account_cache.get(2).unwrap());
491                assert!(!message.is_writable_account_cache.get(3).unwrap());
492            }
493            _ => {
494                panic!("Expect to be SanitizedMessage::LegacyMessage")
495            }
496        }
497
498        let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
499            v0::Message {
500                header: MessageHeader {
501                    num_required_signatures: 2,
502                    num_readonly_signed_accounts: 1,
503                    num_readonly_unsigned_accounts: 1,
504                },
505                account_keys: vec![key0, key1, key2, key3],
506                ..v0::Message::default()
507            },
508            LoadedAddresses {
509                writable: vec![key4],
510                readonly: vec![key5],
511            },
512        ));
513        match v0_message {
514            SanitizedMessage::V0(message) => {
515                assert_eq!(
516                    message.is_writable_account_cache.len(),
517                    message.account_keys().len()
518                );
519                assert!(message.is_writable_account_cache.get(0).unwrap());
520                assert!(!message.is_writable_account_cache.get(1).unwrap());
521                assert!(message.is_writable_account_cache.get(2).unwrap());
522                assert!(!message.is_writable_account_cache.get(3).unwrap());
523                assert!(message.is_writable_account_cache.get(4).unwrap());
524                assert!(!message.is_writable_account_cache.get(5).unwrap());
525            }
526            _ => {
527                panic!("Expect to be SanitizedMessage::V0")
528            }
529        }
530    }
531}