solana_program/message/
legacy.rs

1//! The original and current Solana message format.
2//!
3//! This crate defines two versions of `Message` in their own modules:
4//! [`legacy`] and [`v0`]. `legacy` is the current version as of Solana 1.10.0.
5//! `v0` is a [future message format] that encodes more account keys into a
6//! transaction than the legacy format.
7//!
8//! [`legacy`]: crate::message::legacy
9//! [`v0`]: crate::message::v0
10//! [future message format]: https://docs.solanalabs.com/proposals/versioned-transactions
11
12#![allow(clippy::arithmetic_side_effects)]
13
14#[cfg(target_arch = "wasm32")]
15use crate::wasm_bindgen;
16#[allow(deprecated)]
17pub use builtins::{BUILTIN_PROGRAMS_KEYS, MAYBE_BUILTIN_KEY_OR_SYSVAR};
18use {
19    crate::{
20        bpf_loader, bpf_loader_deprecated, bpf_loader_upgradeable,
21        hash::Hash,
22        instruction::{CompiledInstruction, Instruction},
23        message::{compiled_keys::CompiledKeys, MessageHeader},
24        pubkey::Pubkey,
25        system_instruction, system_program, sysvar,
26    },
27    solana_sanitize::{Sanitize, SanitizeError},
28    solana_short_vec as short_vec,
29    std::{collections::HashSet, convert::TryFrom, str::FromStr},
30};
31
32#[deprecated(
33    since = "2.0.0",
34    note = "please use `solana_sdk::reserved_account_keys::ReservedAccountKeys` instead"
35)]
36#[allow(deprecated)]
37mod builtins {
38    use {super::*, lazy_static::lazy_static};
39
40    lazy_static! {
41        pub static ref BUILTIN_PROGRAMS_KEYS: [Pubkey; 10] = {
42            let parse = |s| Pubkey::from_str(s).unwrap();
43            [
44                parse("Config1111111111111111111111111111111111111"),
45                parse("Feature111111111111111111111111111111111111"),
46                parse("NativeLoader1111111111111111111111111111111"),
47                parse("Stake11111111111111111111111111111111111111"),
48                parse("StakeConfig11111111111111111111111111111111"),
49                parse("Vote111111111111111111111111111111111111111"),
50                system_program::id(),
51                bpf_loader::id(),
52                bpf_loader_deprecated::id(),
53                bpf_loader_upgradeable::id(),
54            ]
55        };
56    }
57
58    lazy_static! {
59        // Each element of a key is a u8. We use key[0] as an index into this table of 256 boolean
60        // elements, to store whether or not the first element of any key is present in the static
61        // lists of built-in-program keys or system ids. By using this lookup table, we can very
62        // quickly determine that a key under consideration cannot be in either of these lists (if
63        // the value is "false"), or might be in one of these lists (if the value is "true")
64        pub static ref MAYBE_BUILTIN_KEY_OR_SYSVAR: [bool; 256] = {
65            let mut temp_table: [bool; 256] = [false; 256];
66            BUILTIN_PROGRAMS_KEYS.iter().for_each(|key| temp_table[key.as_ref()[0] as usize] = true);
67            sysvar::ALL_IDS.iter().for_each(|key| temp_table[key.as_ref()[0] as usize] = true);
68            temp_table
69        };
70    }
71}
72
73#[deprecated(
74    since = "2.0.0",
75    note = "please use `solana_sdk::reserved_account_keys::ReservedAccountKeys::is_reserved` instead"
76)]
77#[allow(deprecated)]
78pub fn is_builtin_key_or_sysvar(key: &Pubkey) -> bool {
79    if MAYBE_BUILTIN_KEY_OR_SYSVAR[key.as_ref()[0] as usize] {
80        return sysvar::is_sysvar_id(key) || BUILTIN_PROGRAMS_KEYS.contains(key);
81    }
82    false
83}
84
85fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
86    keys.iter().position(|k| k == key).unwrap() as u8
87}
88
89fn compile_instruction(ix: &Instruction, keys: &[Pubkey]) -> CompiledInstruction {
90    let accounts: Vec<_> = ix
91        .accounts
92        .iter()
93        .map(|account_meta| position(keys, &account_meta.pubkey))
94        .collect();
95
96    CompiledInstruction {
97        program_id_index: position(keys, &ix.program_id),
98        data: ix.data.clone(),
99        accounts,
100    }
101}
102
103fn compile_instructions(ixs: &[Instruction], keys: &[Pubkey]) -> Vec<CompiledInstruction> {
104    ixs.iter().map(|ix| compile_instruction(ix, keys)).collect()
105}
106
107/// A Solana transaction message (legacy).
108///
109/// See the [`message`] module documentation for further description.
110///
111/// [`message`]: crate::message
112///
113/// Some constructors accept an optional `payer`, the account responsible for
114/// paying the cost of executing a transaction. In most cases, callers should
115/// specify the payer explicitly in these constructors. In some cases though,
116/// the caller is not _required_ to specify the payer, but is still allowed to:
117/// in the `Message` structure, the first account is always the fee-payer, so if
118/// the caller has knowledge that the first account of the constructed
119/// transaction's `Message` is both a signer and the expected fee-payer, then
120/// redundantly specifying the fee-payer is not strictly required.
121// NOTE: Serialization-related changes must be paired with the custom serialization
122// for versioned messages in the `RemainingLegacyMessage` struct.
123#[cfg(not(target_arch = "wasm32"))]
124#[cfg_attr(
125    feature = "frozen-abi",
126    frozen_abi(digest = "4kL6EbLGU25m5eMk4H1cW9YGhA5LejHSgj2w2fhY1NGp"),
127    derive(AbiExample)
128)]
129#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
130#[serde(rename_all = "camelCase")]
131pub struct Message {
132    /// The message header, identifying signed and read-only `account_keys`.
133    // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
134    pub header: MessageHeader,
135
136    /// All the account keys used by this transaction.
137    #[serde(with = "short_vec")]
138    pub account_keys: Vec<Pubkey>,
139
140    /// The id of a recent ledger entry.
141    pub recent_blockhash: Hash,
142
143    /// Programs that will be executed in sequence and committed in one atomic transaction if all
144    /// succeed.
145    #[serde(with = "short_vec")]
146    pub instructions: Vec<CompiledInstruction>,
147}
148
149/// wasm-bindgen version of the Message struct.
150/// This duplication is required until https://github.com/rustwasm/wasm-bindgen/issues/3671
151/// is fixed. This must not diverge from the regular non-wasm Message struct.
152#[cfg(target_arch = "wasm32")]
153#[wasm_bindgen]
154#[cfg_attr(
155    feature = "frozen-abi",
156    frozen_abi(digest = "4kL6EbLGU25m5eMk4H1cW9YGhA5LejHSgj2w2fhY1NGp"),
157    derive(AbiExample)
158)]
159#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
160#[serde(rename_all = "camelCase")]
161pub struct Message {
162    #[wasm_bindgen(skip)]
163    pub header: MessageHeader,
164
165    #[wasm_bindgen(skip)]
166    #[serde(with = "short_vec")]
167    pub account_keys: Vec<Pubkey>,
168
169    /// The id of a recent ledger entry.
170    pub recent_blockhash: Hash,
171
172    #[wasm_bindgen(skip)]
173    #[serde(with = "short_vec")]
174    pub instructions: Vec<CompiledInstruction>,
175}
176
177impl Sanitize for Message {
178    fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
179        // signing area and read-only non-signing area should not overlap
180        if self.header.num_required_signatures as usize
181            + self.header.num_readonly_unsigned_accounts as usize
182            > self.account_keys.len()
183        {
184            return Err(SanitizeError::IndexOutOfBounds);
185        }
186
187        // there should be at least 1 RW fee-payer account.
188        if self.header.num_readonly_signed_accounts >= self.header.num_required_signatures {
189            return Err(SanitizeError::IndexOutOfBounds);
190        }
191
192        for ci in &self.instructions {
193            if ci.program_id_index as usize >= self.account_keys.len() {
194                return Err(SanitizeError::IndexOutOfBounds);
195            }
196            // A program cannot be a payer.
197            if ci.program_id_index == 0 {
198                return Err(SanitizeError::IndexOutOfBounds);
199            }
200            for ai in &ci.accounts {
201                if *ai as usize >= self.account_keys.len() {
202                    return Err(SanitizeError::IndexOutOfBounds);
203                }
204            }
205        }
206        self.account_keys.sanitize()?;
207        self.recent_blockhash.sanitize()?;
208        self.instructions.sanitize()?;
209        Ok(())
210    }
211}
212
213impl Message {
214    /// Create a new `Message`.
215    ///
216    /// # Examples
217    ///
218    /// This example uses the [`solana_sdk`], [`solana_rpc_client`] and [`anyhow`] crates.
219    ///
220    /// [`solana_sdk`]: https://docs.rs/solana-sdk
221    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
222    /// [`anyhow`]: https://docs.rs/anyhow
223    ///
224    /// ```
225    /// # use solana_program::example_mocks::solana_sdk;
226    /// # use solana_program::example_mocks::solana_rpc_client;
227    /// use anyhow::Result;
228    /// use borsh::{BorshSerialize, BorshDeserialize};
229    /// use solana_rpc_client::rpc_client::RpcClient;
230    /// use solana_sdk::{
231    ///     instruction::Instruction,
232    ///     message::Message,
233    ///     pubkey::Pubkey,
234    ///     signature::{Keypair, Signer},
235    ///     transaction::Transaction,
236    /// };
237    ///
238    /// // A custom program instruction. This would typically be defined in
239    /// // another crate so it can be shared between the on-chain program and
240    /// // the client.
241    /// #[derive(BorshSerialize, BorshDeserialize)]
242    /// # #[borsh(crate = "borsh")]
243    /// enum BankInstruction {
244    ///     Initialize,
245    ///     Deposit { lamports: u64 },
246    ///     Withdraw { lamports: u64 },
247    /// }
248    ///
249    /// fn send_initialize_tx(
250    ///     client: &RpcClient,
251    ///     program_id: Pubkey,
252    ///     payer: &Keypair
253    /// ) -> Result<()> {
254    ///
255    ///     let bank_instruction = BankInstruction::Initialize;
256    ///
257    ///     let instruction = Instruction::new_with_borsh(
258    ///         program_id,
259    ///         &bank_instruction,
260    ///         vec![],
261    ///     );
262    ///
263    ///     let message = Message::new(
264    ///         &[instruction],
265    ///         Some(&payer.pubkey()),
266    ///     );
267    ///
268    ///     let blockhash = client.get_latest_blockhash()?;
269    ///     let mut tx = Transaction::new(&[payer], message, blockhash);
270    ///     client.send_and_confirm_transaction(&tx)?;
271    ///
272    ///     Ok(())
273    /// }
274    /// #
275    /// # let client = RpcClient::new(String::new());
276    /// # let program_id = Pubkey::new_unique();
277    /// # let payer = Keypair::new();
278    /// # send_initialize_tx(&client, program_id, &payer)?;
279    /// #
280    /// # Ok::<(), anyhow::Error>(())
281    /// ```
282    pub fn new(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
283        Self::new_with_blockhash(instructions, payer, &Hash::default())
284    }
285
286    /// Create a new message while setting the blockhash.
287    ///
288    /// # Examples
289    ///
290    /// This example uses the [`solana_sdk`], [`solana_rpc_client`] and [`anyhow`] crates.
291    ///
292    /// [`solana_sdk`]: https://docs.rs/solana-sdk
293    /// [`solana_rpc_client`]: https://docs.rs/solana-rpc-client
294    /// [`anyhow`]: https://docs.rs/anyhow
295    ///
296    /// ```
297    /// # use solana_program::example_mocks::solana_sdk;
298    /// # use solana_program::example_mocks::solana_rpc_client;
299    /// use anyhow::Result;
300    /// use borsh::{BorshSerialize, BorshDeserialize};
301    /// use solana_rpc_client::rpc_client::RpcClient;
302    /// use solana_sdk::{
303    ///     instruction::Instruction,
304    ///     message::Message,
305    ///     pubkey::Pubkey,
306    ///     signature::{Keypair, Signer},
307    ///     transaction::Transaction,
308    /// };
309    ///
310    /// // A custom program instruction. This would typically be defined in
311    /// // another crate so it can be shared between the on-chain program and
312    /// // the client.
313    /// #[derive(BorshSerialize, BorshDeserialize)]
314    /// # #[borsh(crate = "borsh")]
315    /// enum BankInstruction {
316    ///     Initialize,
317    ///     Deposit { lamports: u64 },
318    ///     Withdraw { lamports: u64 },
319    /// }
320    ///
321    /// fn send_initialize_tx(
322    ///     client: &RpcClient,
323    ///     program_id: Pubkey,
324    ///     payer: &Keypair
325    /// ) -> Result<()> {
326    ///
327    ///     let bank_instruction = BankInstruction::Initialize;
328    ///
329    ///     let instruction = Instruction::new_with_borsh(
330    ///         program_id,
331    ///         &bank_instruction,
332    ///         vec![],
333    ///     );
334    ///
335    ///     let blockhash = client.get_latest_blockhash()?;
336    ///
337    ///     let message = Message::new_with_blockhash(
338    ///         &[instruction],
339    ///         Some(&payer.pubkey()),
340    ///         &blockhash,
341    ///     );
342    ///
343    ///     let mut tx = Transaction::new_unsigned(message);
344    ///     tx.sign(&[payer], tx.message.recent_blockhash);
345    ///     client.send_and_confirm_transaction(&tx)?;
346    ///
347    ///     Ok(())
348    /// }
349    /// #
350    /// # let client = RpcClient::new(String::new());
351    /// # let program_id = Pubkey::new_unique();
352    /// # let payer = Keypair::new();
353    /// # send_initialize_tx(&client, program_id, &payer)?;
354    /// #
355    /// # Ok::<(), anyhow::Error>(())
356    /// ```
357    pub fn new_with_blockhash(
358        instructions: &[Instruction],
359        payer: Option<&Pubkey>,
360        blockhash: &Hash,
361    ) -> Self {
362        let compiled_keys = CompiledKeys::compile(instructions, payer.cloned());
363        let (header, account_keys) = compiled_keys
364            .try_into_message_components()
365            .expect("overflow when compiling message keys");
366        let instructions = compile_instructions(instructions, &account_keys);
367        Self::new_with_compiled_instructions(
368            header.num_required_signatures,
369            header.num_readonly_signed_accounts,
370            header.num_readonly_unsigned_accounts,
371            account_keys,
372            *blockhash,
373            instructions,
374        )
375    }
376
377    /// Create a new message for a [nonced transaction].
378    ///
379    /// [nonced transaction]: https://docs.solanalabs.com/implemented-proposals/durable-tx-nonces
380    ///
381    /// In this type of transaction, the blockhash is replaced with a _durable
382    /// transaction nonce_, allowing for extended time to pass between the
383    /// transaction's signing and submission to the blockchain.
384    ///
385    /// # Examples
386    ///
387    /// This example uses the [`solana_sdk`], [`solana_rpc_client`] and [`anyhow`] crates.
388    ///
389    /// [`solana_sdk`]: https://docs.rs/solana-sdk
390    /// [`solana_rpc_client`]: https://docs.rs/solana-client
391    /// [`anyhow`]: https://docs.rs/anyhow
392    ///
393    /// ```
394    /// # use solana_program::example_mocks::solana_sdk;
395    /// # use solana_program::example_mocks::solana_rpc_client;
396    /// use anyhow::Result;
397    /// use borsh::{BorshSerialize, BorshDeserialize};
398    /// use solana_rpc_client::rpc_client::RpcClient;
399    /// use solana_sdk::{
400    ///     hash::Hash,
401    ///     instruction::Instruction,
402    ///     message::Message,
403    ///     nonce,
404    ///     pubkey::Pubkey,
405    ///     signature::{Keypair, Signer},
406    ///     system_instruction,
407    ///     transaction::Transaction,
408    /// };
409    ///
410    /// // A custom program instruction. This would typically be defined in
411    /// // another crate so it can be shared between the on-chain program and
412    /// // the client.
413    /// #[derive(BorshSerialize, BorshDeserialize)]
414    /// # #[borsh(crate = "borsh")]
415    /// enum BankInstruction {
416    ///     Initialize,
417    ///     Deposit { lamports: u64 },
418    ///     Withdraw { lamports: u64 },
419    /// }
420    ///
421    /// // Create a nonced transaction for later signing and submission,
422    /// // returning it and the nonce account's pubkey.
423    /// fn create_offline_initialize_tx(
424    ///     client: &RpcClient,
425    ///     program_id: Pubkey,
426    ///     payer: &Keypair
427    /// ) -> Result<(Transaction, Pubkey)> {
428    ///
429    ///     let bank_instruction = BankInstruction::Initialize;
430    ///     let bank_instruction = Instruction::new_with_borsh(
431    ///         program_id,
432    ///         &bank_instruction,
433    ///         vec![],
434    ///     );
435    ///
436    ///     // This will create a nonce account and assign authority to the
437    ///     // payer so they can sign to advance the nonce and withdraw its rent.
438    ///     let nonce_account = make_nonce_account(client, payer)?;
439    ///
440    ///     let mut message = Message::new_with_nonce(
441    ///         vec![bank_instruction],
442    ///         Some(&payer.pubkey()),
443    ///         &nonce_account,
444    ///         &payer.pubkey()
445    ///     );
446    ///
447    ///     // This transaction will need to be signed later, using the blockhash
448    ///     // stored in the nonce account.
449    ///     let tx = Transaction::new_unsigned(message);
450    ///
451    ///     Ok((tx, nonce_account))
452    /// }
453    ///
454    /// fn make_nonce_account(client: &RpcClient, payer: &Keypair)
455    ///     -> Result<Pubkey>
456    /// {
457    ///     let nonce_account_address = Keypair::new();
458    ///     let nonce_account_size = nonce::State::size();
459    ///     let nonce_rent = client.get_minimum_balance_for_rent_exemption(nonce_account_size)?;
460    ///
461    ///     // Assigning the nonce authority to the payer so they can sign for the withdrawal,
462    ///     // and we can throw away the nonce address secret key.
463    ///     let create_nonce_instr = system_instruction::create_nonce_account(
464    ///         &payer.pubkey(),
465    ///         &nonce_account_address.pubkey(),
466    ///         &payer.pubkey(),
467    ///         nonce_rent,
468    ///     );
469    ///
470    ///     let mut nonce_tx = Transaction::new_with_payer(&create_nonce_instr, Some(&payer.pubkey()));
471    ///     let blockhash = client.get_latest_blockhash()?;
472    ///     nonce_tx.sign(&[&payer, &nonce_account_address], blockhash);
473    ///     client.send_and_confirm_transaction(&nonce_tx)?;
474    ///
475    ///     Ok(nonce_account_address.pubkey())
476    /// }
477    /// #
478    /// # let client = RpcClient::new(String::new());
479    /// # let program_id = Pubkey::new_unique();
480    /// # let payer = Keypair::new();
481    /// # create_offline_initialize_tx(&client, program_id, &payer)?;
482    /// # Ok::<(), anyhow::Error>(())
483    /// ```
484    pub fn new_with_nonce(
485        mut instructions: Vec<Instruction>,
486        payer: Option<&Pubkey>,
487        nonce_account_pubkey: &Pubkey,
488        nonce_authority_pubkey: &Pubkey,
489    ) -> Self {
490        let nonce_ix =
491            system_instruction::advance_nonce_account(nonce_account_pubkey, nonce_authority_pubkey);
492        instructions.insert(0, nonce_ix);
493        Self::new(&instructions, payer)
494    }
495
496    pub fn new_with_compiled_instructions(
497        num_required_signatures: u8,
498        num_readonly_signed_accounts: u8,
499        num_readonly_unsigned_accounts: u8,
500        account_keys: Vec<Pubkey>,
501        recent_blockhash: Hash,
502        instructions: Vec<CompiledInstruction>,
503    ) -> Self {
504        Self {
505            header: MessageHeader {
506                num_required_signatures,
507                num_readonly_signed_accounts,
508                num_readonly_unsigned_accounts,
509            },
510            account_keys,
511            recent_blockhash,
512            instructions,
513        }
514    }
515
516    /// Compute the blake3 hash of this transaction's message.
517    #[cfg(not(target_os = "solana"))]
518    pub fn hash(&self) -> Hash {
519        let message_bytes = self.serialize();
520        Self::hash_raw_message(&message_bytes)
521    }
522
523    /// Compute the blake3 hash of a raw transaction message.
524    #[cfg(not(target_os = "solana"))]
525    pub fn hash_raw_message(message_bytes: &[u8]) -> Hash {
526        use {blake3::traits::digest::Digest, solana_hash::HASH_BYTES};
527        let mut hasher = blake3::Hasher::new();
528        hasher.update(b"solana-tx-message-v1");
529        hasher.update(message_bytes);
530        let hash_bytes: [u8; HASH_BYTES] = hasher.finalize().into();
531        hash_bytes.into()
532    }
533
534    pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
535        compile_instruction(ix, &self.account_keys)
536    }
537
538    pub fn serialize(&self) -> Vec<u8> {
539        bincode::serialize(self).unwrap()
540    }
541
542    pub fn program_id(&self, instruction_index: usize) -> Option<&Pubkey> {
543        Some(
544            &self.account_keys[self.instructions.get(instruction_index)?.program_id_index as usize],
545        )
546    }
547
548    pub fn program_index(&self, instruction_index: usize) -> Option<usize> {
549        Some(self.instructions.get(instruction_index)?.program_id_index as usize)
550    }
551
552    pub fn program_ids(&self) -> Vec<&Pubkey> {
553        self.instructions
554            .iter()
555            .map(|ix| &self.account_keys[ix.program_id_index as usize])
556            .collect()
557    }
558
559    #[deprecated(since = "2.0.0", note = "Please use `is_instruction_account` instead")]
560    pub fn is_key_passed_to_program(&self, key_index: usize) -> bool {
561        self.is_instruction_account(key_index)
562    }
563
564    /// Returns true if the account at the specified index is an account input
565    /// to some program instruction in this message.
566    pub fn is_instruction_account(&self, key_index: usize) -> bool {
567        if let Ok(key_index) = u8::try_from(key_index) {
568            self.instructions
569                .iter()
570                .any(|ix| ix.accounts.contains(&key_index))
571        } else {
572            false
573        }
574    }
575
576    pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
577        if let Ok(key_index) = u8::try_from(key_index) {
578            self.instructions
579                .iter()
580                .any(|ix| ix.program_id_index == key_index)
581        } else {
582            false
583        }
584    }
585
586    #[deprecated(
587        since = "2.0.0",
588        note = "Please use `is_key_called_as_program` and `is_instruction_account` directly"
589    )]
590    pub fn is_non_loader_key(&self, key_index: usize) -> bool {
591        !self.is_key_called_as_program(key_index) || self.is_instruction_account(key_index)
592    }
593
594    pub fn program_position(&self, index: usize) -> Option<usize> {
595        let program_ids = self.program_ids();
596        program_ids
597            .iter()
598            .position(|&&pubkey| pubkey == self.account_keys[index])
599    }
600
601    pub fn maybe_executable(&self, i: usize) -> bool {
602        self.program_position(i).is_some()
603    }
604
605    pub fn demote_program_id(&self, i: usize) -> bool {
606        self.is_key_called_as_program(i) && !self.is_upgradeable_loader_present()
607    }
608
609    /// Returns true if the account at the specified index was requested to be
610    /// writable. This method should not be used directly.
611    pub(super) fn is_writable_index(&self, i: usize) -> bool {
612        i < (self.header.num_required_signatures - self.header.num_readonly_signed_accounts)
613            as usize
614            || (i >= self.header.num_required_signatures as usize
615                && i < self.account_keys.len()
616                    - self.header.num_readonly_unsigned_accounts as usize)
617    }
618
619    /// Returns true if the account at the specified index is writable by the
620    /// instructions in this message. Since the dynamic set of reserved accounts
621    /// isn't used here to demote write locks, this shouldn't be used in the
622    /// runtime.
623    #[deprecated(since = "2.0.0", note = "Please use `is_maybe_writable` instead")]
624    #[allow(deprecated)]
625    pub fn is_writable(&self, i: usize) -> bool {
626        (self.is_writable_index(i))
627            && !is_builtin_key_or_sysvar(&self.account_keys[i])
628            && !self.demote_program_id(i)
629    }
630
631    /// Returns true if the account at the specified index is writable by the
632    /// instructions in this message. The `reserved_account_keys` param has been
633    /// optional to allow clients to approximate writability without requiring
634    /// fetching the latest set of reserved account keys. If this method is
635    /// called by the runtime, the latest set of reserved account keys must be
636    /// passed.
637    pub fn is_maybe_writable(
638        &self,
639        i: usize,
640        reserved_account_keys: Option<&HashSet<Pubkey>>,
641    ) -> bool {
642        (self.is_writable_index(i))
643            && !self.is_account_maybe_reserved(i, reserved_account_keys)
644            && !self.demote_program_id(i)
645    }
646
647    /// Returns true if the account at the specified index is in the optional
648    /// reserved account keys set.
649    fn is_account_maybe_reserved(
650        &self,
651        key_index: usize,
652        reserved_account_keys: Option<&HashSet<Pubkey>>,
653    ) -> bool {
654        let mut is_maybe_reserved = false;
655        if let Some(reserved_account_keys) = reserved_account_keys {
656            if let Some(key) = self.account_keys.get(key_index) {
657                is_maybe_reserved = reserved_account_keys.contains(key);
658            }
659        }
660        is_maybe_reserved
661    }
662
663    pub fn is_signer(&self, i: usize) -> bool {
664        i < self.header.num_required_signatures as usize
665    }
666
667    pub fn signer_keys(&self) -> Vec<&Pubkey> {
668        // Clamp in case we're working on un-`sanitize()`ed input
669        let last_key = self
670            .account_keys
671            .len()
672            .min(self.header.num_required_signatures as usize);
673        self.account_keys[..last_key].iter().collect()
674    }
675
676    /// Returns `true` if `account_keys` has any duplicate keys.
677    pub fn has_duplicates(&self) -> bool {
678        // Note: This is an O(n^2) algorithm, but requires no heap allocations. The benchmark
679        // `bench_has_duplicates` in benches/message_processor.rs shows that this implementation is
680        // ~50 times faster than using HashSet for very short slices.
681        for i in 1..self.account_keys.len() {
682            #[allow(clippy::arithmetic_side_effects)]
683            if self.account_keys[i..].contains(&self.account_keys[i - 1]) {
684                return true;
685            }
686        }
687        false
688    }
689
690    /// Returns `true` if any account is the BPF upgradeable loader.
691    pub fn is_upgradeable_loader_present(&self) -> bool {
692        self.account_keys
693            .iter()
694            .any(|&key| key == bpf_loader_upgradeable::id())
695    }
696}
697
698#[cfg(test)]
699mod tests {
700    #![allow(deprecated)]
701    use {
702        super::*,
703        crate::{hash, instruction::AccountMeta, message::MESSAGE_HEADER_LENGTH},
704        std::collections::HashSet,
705    };
706
707    #[test]
708    fn test_builtin_program_keys() {
709        let keys: HashSet<Pubkey> = BUILTIN_PROGRAMS_KEYS.iter().copied().collect();
710        assert_eq!(keys.len(), 10);
711        for k in keys {
712            let k = format!("{k}");
713            assert!(k.ends_with("11111111111111111111111"));
714        }
715    }
716
717    #[test]
718    fn test_builtin_program_keys_abi_freeze() {
719        // Once the feature is flipped on, we can't further modify
720        // BUILTIN_PROGRAMS_KEYS without the risk of breaking consensus.
721        let builtins = format!("{:?}", *BUILTIN_PROGRAMS_KEYS);
722        assert_eq!(
723            format!("{}", hash::hash(builtins.as_bytes())),
724            "ACqmMkYbo9eqK6QrRSrB3HLyR6uHhLf31SCfGUAJjiWj"
725        );
726    }
727
728    #[test]
729    // Ensure there's a way to calculate the number of required signatures.
730    fn test_message_signed_keys_len() {
731        let program_id = Pubkey::default();
732        let id0 = Pubkey::default();
733        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
734        let message = Message::new(&[ix], None);
735        assert_eq!(message.header.num_required_signatures, 0);
736
737        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
738        let message = Message::new(&[ix], Some(&id0));
739        assert_eq!(message.header.num_required_signatures, 1);
740    }
741
742    #[test]
743    fn test_message_kitchen_sink() {
744        let program_id0 = Pubkey::new_unique();
745        let program_id1 = Pubkey::new_unique();
746        let id0 = Pubkey::default();
747        let id1 = Pubkey::new_unique();
748        let message = Message::new(
749            &[
750                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
751                Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id1, true)]),
752                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, false)]),
753            ],
754            Some(&id1),
755        );
756        assert_eq!(
757            message.instructions[0],
758            CompiledInstruction::new(2, &0, vec![1])
759        );
760        assert_eq!(
761            message.instructions[1],
762            CompiledInstruction::new(3, &0, vec![0])
763        );
764        assert_eq!(
765            message.instructions[2],
766            CompiledInstruction::new(2, &0, vec![0])
767        );
768    }
769
770    #[test]
771    fn test_message_payer_first() {
772        let program_id = Pubkey::default();
773        let payer = Pubkey::new_unique();
774        let id0 = Pubkey::default();
775
776        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, false)]);
777        let message = Message::new(&[ix], Some(&payer));
778        assert_eq!(message.header.num_required_signatures, 1);
779
780        let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
781        let message = Message::new(&[ix], Some(&payer));
782        assert_eq!(message.header.num_required_signatures, 2);
783
784        let ix = Instruction::new_with_bincode(
785            program_id,
786            &0,
787            vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
788        );
789        let message = Message::new(&[ix], Some(&payer));
790        assert_eq!(message.header.num_required_signatures, 2);
791    }
792
793    #[test]
794    fn test_program_position() {
795        let program_id0 = Pubkey::default();
796        let program_id1 = Pubkey::new_unique();
797        let id = Pubkey::new_unique();
798        let message = Message::new(
799            &[
800                Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id, false)]),
801                Instruction::new_with_bincode(program_id1, &0, vec![AccountMeta::new(id, true)]),
802            ],
803            Some(&id),
804        );
805        assert_eq!(message.program_position(0), None);
806        assert_eq!(message.program_position(1), Some(0));
807        assert_eq!(message.program_position(2), Some(1));
808    }
809
810    #[test]
811    fn test_is_writable() {
812        let key0 = Pubkey::new_unique();
813        let key1 = Pubkey::new_unique();
814        let key2 = Pubkey::new_unique();
815        let key3 = Pubkey::new_unique();
816        let key4 = Pubkey::new_unique();
817        let key5 = Pubkey::new_unique();
818
819        let message = Message {
820            header: MessageHeader {
821                num_required_signatures: 3,
822                num_readonly_signed_accounts: 2,
823                num_readonly_unsigned_accounts: 1,
824            },
825            account_keys: vec![key0, key1, key2, key3, key4, key5],
826            recent_blockhash: Hash::default(),
827            instructions: vec![],
828        };
829        assert!(message.is_writable(0));
830        assert!(!message.is_writable(1));
831        assert!(!message.is_writable(2));
832        assert!(message.is_writable(3));
833        assert!(message.is_writable(4));
834        assert!(!message.is_writable(5));
835    }
836
837    #[test]
838    fn test_is_maybe_writable() {
839        let key0 = Pubkey::new_unique();
840        let key1 = Pubkey::new_unique();
841        let key2 = Pubkey::new_unique();
842        let key3 = Pubkey::new_unique();
843        let key4 = Pubkey::new_unique();
844        let key5 = Pubkey::new_unique();
845
846        let message = Message {
847            header: MessageHeader {
848                num_required_signatures: 3,
849                num_readonly_signed_accounts: 2,
850                num_readonly_unsigned_accounts: 1,
851            },
852            account_keys: vec![key0, key1, key2, key3, key4, key5],
853            recent_blockhash: Hash::default(),
854            instructions: vec![],
855        };
856
857        let reserved_account_keys = HashSet::from([key3]);
858
859        assert!(message.is_maybe_writable(0, Some(&reserved_account_keys)));
860        assert!(!message.is_maybe_writable(1, Some(&reserved_account_keys)));
861        assert!(!message.is_maybe_writable(2, Some(&reserved_account_keys)));
862        assert!(!message.is_maybe_writable(3, Some(&reserved_account_keys)));
863        assert!(message.is_maybe_writable(3, None));
864        assert!(message.is_maybe_writable(4, Some(&reserved_account_keys)));
865        assert!(!message.is_maybe_writable(5, Some(&reserved_account_keys)));
866        assert!(!message.is_maybe_writable(6, Some(&reserved_account_keys)));
867    }
868
869    #[test]
870    fn test_is_account_maybe_reserved() {
871        let key0 = Pubkey::new_unique();
872        let key1 = Pubkey::new_unique();
873
874        let message = Message {
875            account_keys: vec![key0, key1],
876            ..Message::default()
877        };
878
879        let reserved_account_keys = HashSet::from([key1]);
880
881        assert!(!message.is_account_maybe_reserved(0, Some(&reserved_account_keys)));
882        assert!(message.is_account_maybe_reserved(1, Some(&reserved_account_keys)));
883        assert!(!message.is_account_maybe_reserved(2, Some(&reserved_account_keys)));
884        assert!(!message.is_account_maybe_reserved(0, None));
885        assert!(!message.is_account_maybe_reserved(1, None));
886        assert!(!message.is_account_maybe_reserved(2, None));
887    }
888
889    #[test]
890    fn test_program_ids() {
891        let key0 = Pubkey::new_unique();
892        let key1 = Pubkey::new_unique();
893        let loader2 = Pubkey::new_unique();
894        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
895        let message = Message::new_with_compiled_instructions(
896            1,
897            0,
898            2,
899            vec![key0, key1, loader2],
900            Hash::default(),
901            instructions,
902        );
903        assert_eq!(message.program_ids(), vec![&loader2]);
904    }
905
906    #[test]
907    fn test_is_instruction_account() {
908        let key0 = Pubkey::new_unique();
909        let key1 = Pubkey::new_unique();
910        let loader2 = Pubkey::new_unique();
911        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
912        let message = Message::new_with_compiled_instructions(
913            1,
914            0,
915            2,
916            vec![key0, key1, loader2],
917            Hash::default(),
918            instructions,
919        );
920
921        assert!(message.is_instruction_account(0));
922        assert!(message.is_instruction_account(1));
923        assert!(!message.is_instruction_account(2));
924    }
925
926    #[test]
927    fn test_is_non_loader_key() {
928        #![allow(deprecated)]
929        let key0 = Pubkey::new_unique();
930        let key1 = Pubkey::new_unique();
931        let loader2 = Pubkey::new_unique();
932        let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
933        let message = Message::new_with_compiled_instructions(
934            1,
935            0,
936            2,
937            vec![key0, key1, loader2],
938            Hash::default(),
939            instructions,
940        );
941        assert!(message.is_non_loader_key(0));
942        assert!(message.is_non_loader_key(1));
943        assert!(!message.is_non_loader_key(2));
944    }
945
946    #[test]
947    fn test_message_header_len_constant() {
948        assert_eq!(
949            bincode::serialized_size(&MessageHeader::default()).unwrap() as usize,
950            MESSAGE_HEADER_LENGTH
951        );
952    }
953
954    #[test]
955    fn test_message_hash() {
956        // when this test fails, it's most likely due to a new serialized format of a message.
957        // in this case, the domain prefix `solana-tx-message-v1` should be updated.
958        let program_id0 = Pubkey::from_str("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM").unwrap();
959        let program_id1 = Pubkey::from_str("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh").unwrap();
960        let id0 = Pubkey::from_str("CiDwVBFgWV9E5MvXWoLgnEgn2hK7rJikbvfWavzAQz3").unwrap();
961        let id1 = Pubkey::from_str("GcdayuLaLyrdmUu324nahyv33G5poQdLUEZ1nEytDeP").unwrap();
962        let id2 = Pubkey::from_str("LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj").unwrap();
963        let id3 = Pubkey::from_str("QRSsyMWN1yHT9ir42bgNZUNZ4PdEhcSWCrL2AryKpy5").unwrap();
964        let instructions = vec![
965            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id0, false)]),
966            Instruction::new_with_bincode(program_id0, &0, vec![AccountMeta::new(id1, true)]),
967            Instruction::new_with_bincode(
968                program_id1,
969                &0,
970                vec![AccountMeta::new_readonly(id2, false)],
971            ),
972            Instruction::new_with_bincode(
973                program_id1,
974                &0,
975                vec![AccountMeta::new_readonly(id3, true)],
976            ),
977        ];
978
979        let message = Message::new(&instructions, Some(&id1));
980        assert_eq!(
981            message.hash(),
982            Hash::from_str("7VWCF4quo2CcWQFNUayZiorxpiR5ix8YzLebrXKf3fMF").unwrap()
983        )
984    }
985}