Skip to main content

solana_message/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
3#![no_std]
4//! Sequences of [`Instruction`]s executed within a single transaction.
5//!
6//! [`Instruction`]: https://docs.rs/solana-instruction/latest/solana_instruction/struct.Instruction.html
7//!
8//! In Solana, programs execute instructions, and clients submit sequences
9//! of instructions to the network to be atomically executed as [`Transaction`]s.
10//!
11//! [`Transaction`]: https://docs.rs/solana-sdk/latest/solana-sdk/transaction/struct.Transaction.html
12//!
13//! A [`Message`] is the compact internal encoding of a transaction, as
14//! transmitted across the network and stored in, and operated on, by the
15//! runtime. It contains a flat array of all accounts accessed by all
16//! instructions in the message, a [`MessageHeader`] that describes the layout
17//! of that account array, a [recent blockhash], and a compact encoding of the
18//! message's instructions.
19//!
20//! [recent blockhash]: https://solana.com/docs/core/transactions#recent-blockhash
21//!
22//! Clients most often deal with `Instruction`s and `Transaction`s, with
23//! `Message`s being created by `Transaction` constructors.
24//!
25//! To ensure reliable network delivery, serialized messages must fit into the
26//! IPv6 MTU size, conservatively assumed to be 1280 bytes. Thus constrained,
27//! care must be taken in the amount of data consumed by instructions, and the
28//! number of accounts they require to function.
29//!
30//! This module defines two versions of `Message` in their own modules:
31//! [`legacy`] and [`v0`]. `legacy` is reexported here and is the current
32//! version as of Solana 1.10.0. `v0` is a [future message format] that encodes
33//! more account keys into a transaction than the legacy format. The
34//! [`VersionedMessage`] type is a thin wrapper around either message version.
35//!
36//! [future message format]: https://docs.solanalabs.com/proposals/versioned-transactions
37//!
38//! Despite living in the `solana-program` crate, there is no way to access the
39//! runtime's messages from within a Solana program, and only the legacy message
40//! types continue to be exposed to Solana programs, for backwards compatibility
41//! reasons.
42
43extern crate alloc;
44#[cfg(any(feature = "frozen-abi", feature = "std"))]
45extern crate std;
46
47#[cfg(feature = "serde")]
48use serde_derive::{Deserialize, Serialize};
49#[cfg(feature = "frozen-abi")]
50use solana_frozen_abi_macro::{frozen_abi, AbiExample, StableAbi, StableAbiSample};
51#[cfg(feature = "std")]
52use std::collections::HashSet;
53#[cfg(feature = "wincode")]
54use wincode::{SchemaRead, SchemaWrite, UninitBuilder};
55use {
56    crate::compiled_instruction::CompiledInstruction,
57    alloc::{collections::BTreeSet, vec::Vec},
58    solana_sdk_ids::bpf_loader_upgradeable,
59};
60
61mod account_keys;
62
63#[cfg(feature = "std")]
64mod address_loader;
65pub mod compiled_instruction;
66mod compiled_keys;
67pub mod inline_nonce;
68pub mod inner_instruction;
69pub mod legacy;
70#[cfg(feature = "std")]
71mod sanitized;
72mod versions;
73#[cfg(feature = "std")]
74pub use address_loader::*;
75#[cfg(feature = "std")]
76pub use sanitized::*;
77pub use {
78    account_keys::*,
79    compiled_keys::CompileError,
80    legacy::Message,
81    solana_address::Address,
82    solana_hash::Hash,
83    solana_instruction::{AccountMeta, Instruction},
84    versions::*,
85};
86
87/// The length of a message header in bytes.
88pub const MESSAGE_HEADER_LENGTH: usize = 3;
89
90/// Describes the organization of a `Message`'s account keys.
91///
92/// Every [`Instruction`] specifies which accounts it may reference, or
93/// otherwise requires specific permissions of. Those specifications are:
94/// whether the account is read-only, or read-write; and whether the account
95/// must have signed the transaction containing the instruction.
96///
97/// Whereas individual `Instruction`s contain a list of all accounts they may
98/// access, along with their required permissions, a `Message` contains a
99/// single shared flat list of _all_ accounts required by _all_ instructions in
100/// a transaction. When building a `Message`, this flat list is created and
101/// `Instruction`s are converted to [`CompiledInstruction`]s. Those
102/// `CompiledInstruction`s then reference by index the accounts they require in
103/// the single shared account list.
104///
105/// [`Instruction`]: https://docs.rs/solana-instruction/latest/solana_instruction/struct.Instruction.html
106/// [`CompiledInstruction`]: crate::compiled_instruction::CompiledInstruction
107///
108/// The shared account list is ordered by the permissions required of the accounts:
109///
110/// - accounts that are writable and signers
111/// - accounts that are read-only and signers
112/// - accounts that are writable and not signers
113/// - accounts that are read-only and not signers
114///
115/// Given this ordering, the fields of `MessageHeader` describe which accounts
116/// in a transaction require which permissions.
117///
118/// When multiple transactions access the same read-only accounts, the runtime
119/// may process them in parallel, in a single [PoH] entry. Transactions that
120/// access the same read-write accounts are processed sequentially.
121///
122/// [PoH]: https://docs.solanalabs.com/consensus/synchronization
123#[cfg_attr(
124    feature = "frozen-abi",
125    derive(AbiExample, StableAbi, StableAbiSample),
126    frozen_abi(
127        abi_digest = "BoNk47PmBYTf1fzuJoZ8tcFm9EVnFjv8v7bDccreDvgB",
128        abi_serializer = ["bincode", "wincode"],
129        test_roundtrip = "eq_and_wire"
130    )
131)]
132#[cfg_attr(
133    feature = "serde",
134    derive(Deserialize, Serialize),
135    serde(rename_all = "camelCase")
136)]
137#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead, UninitBuilder))]
138#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
139pub struct MessageHeader {
140    /// The number of signatures required for this message to be considered
141    /// valid. The signers of those signatures must match the first
142    /// `num_required_signatures` of [`Message::account_keys`].
143    // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
144    pub num_required_signatures: u8,
145
146    /// The last `num_readonly_signed_accounts` of the signed keys are read-only
147    /// accounts.
148    pub num_readonly_signed_accounts: u8,
149
150    /// The last `num_readonly_unsigned_accounts` of the unsigned keys are
151    /// read-only accounts.
152    pub num_readonly_unsigned_accounts: u8,
153}
154
155/// The definition of address lookup table accounts.
156///
157/// As used by the `crate::v0` message format.
158#[derive(Debug, PartialEq, Eq, Clone)]
159pub struct AddressLookupTableAccount {
160    pub key: Address,
161    pub addresses: Vec<Address>,
162}
163
164/// Returns true if the account at the specified index was requested to be
165/// writable.
166///
167/// This method should not be used directly. It is used by Legacy and V1
168/// message types.
169#[inline(always)]
170fn is_writable_index(i: usize, header: MessageHeader, account_keys: &[Address]) -> bool {
171    i < (header.num_required_signatures as usize)
172        .saturating_sub(header.num_readonly_signed_accounts as usize)
173        || (i >= header.num_required_signatures as usize
174            && i < account_keys
175                .len()
176                .saturating_sub(header.num_readonly_unsigned_accounts as usize))
177}
178
179/// A set of [`Address`]es that can be checked for membership.
180pub trait AddressSet {
181    /// Returns true if the set contains the given address.
182    fn contains(&self, address: &Address) -> bool;
183}
184
185impl AddressSet for BTreeSet<Address> {
186    fn contains(&self, address: &Address) -> bool {
187        BTreeSet::contains(self, address)
188    }
189}
190
191#[cfg(feature = "std")]
192impl<S: core::hash::BuildHasher> AddressSet for HashSet<Address, S> {
193    fn contains(&self, address: &Address) -> bool {
194        HashSet::contains(self, address)
195    }
196}
197
198impl<T: AddressSet> AddressSet for &T {
199    fn contains(&self, address: &Address) -> bool {
200        T::contains(*self, address)
201    }
202}
203
204impl<T: AddressSet> AddressSet for Option<T> {
205    fn contains(&self, address: &Address) -> bool {
206        self.as_ref()
207            .is_some_and(|address_set| address_set.contains(address))
208    }
209}
210
211/// Returns true if the account at the specified index is in the reserved
212/// address set.
213#[inline(always)]
214fn is_account_maybe_reserved(
215    i: usize,
216    account_keys: &[Address],
217    reserved_addresses: Option<&impl AddressSet>,
218) -> bool {
219    account_keys
220        .get(i)
221        .is_some_and(|key| reserved_addresses.contains(key))
222}
223
224#[inline(always)]
225fn is_program_id_write_demoted(
226    i: usize,
227    account_keys: &[Address],
228    instructions: &[CompiledInstruction],
229) -> bool {
230    is_key_called_as_program(instructions, i) && !is_upgradeable_loader_present(account_keys)
231}
232
233#[inline(always)]
234fn is_key_called_as_program(instructions: &[CompiledInstruction], key_index: usize) -> bool {
235    if let Ok(key_index) = u8::try_from(key_index) {
236        instructions
237            .iter()
238            .any(|ix| ix.program_id_index == key_index)
239    } else {
240        false
241    }
242}
243
244/// Returns `true` if any account is the BPF upgradeable loader.
245#[inline(always)]
246fn is_upgradeable_loader_present(account_keys: &[Address]) -> bool {
247    account_keys
248        .iter()
249        .any(|&key| key == bpf_loader_upgradeable::id())
250}
251
252/// Returns true if the account at the specified index is writable by the
253/// instructions in this message. The `reserved_addresses` parameter is optional
254/// to allow clients to approximate writability without requiring fetching the
255/// latest set of protocol-reserved addresses. If this method is called by the
256/// runtime, the latest set of reserved addresses must be passed.
257#[inline(always)]
258fn is_maybe_writable(
259    i: usize,
260    header: MessageHeader,
261    account_keys: &[Address],
262    instructions: &[CompiledInstruction],
263    reserved_addresses: Option<&impl AddressSet>,
264) -> bool {
265    (is_writable_index(i, header, account_keys))
266        && !is_account_maybe_reserved(i, account_keys, reserved_addresses)
267        && !is_program_id_write_demoted(i, account_keys, instructions)
268}
269
270#[cfg(test)]
271mod tests {
272    use {
273        crate::{is_account_maybe_reserved, Message},
274        alloc::vec,
275        solana_address::Address,
276        std::collections::HashSet,
277    };
278
279    #[test]
280    fn test_is_account_maybe_reserved() {
281        let key0 = Address::new_unique();
282        let key1 = Address::new_unique();
283
284        let message = Message {
285            account_keys: vec![key0, key1],
286            ..Message::default()
287        };
288
289        let reserved_addresses = HashSet::from([key1]);
290
291        assert!(!is_account_maybe_reserved(
292            0,
293            &message.account_keys,
294            Some(&reserved_addresses),
295        ));
296        assert!(is_account_maybe_reserved(
297            1,
298            &message.account_keys,
299            Some(&reserved_addresses),
300        ));
301        assert!(!is_account_maybe_reserved(
302            2,
303            &message.account_keys,
304            Some(&reserved_addresses),
305        ));
306        assert!(!is_account_maybe_reserved(
307            0,
308            &message.account_keys,
309            None::<&HashSet<Address>>,
310        ));
311        assert!(!is_account_maybe_reserved(
312            1,
313            &message.account_keys,
314            None::<&HashSet<Address>>,
315        ));
316        assert!(!is_account_maybe_reserved(
317            2,
318            &message.account_keys,
319            None::<&HashSet<Address>>,
320        ));
321    }
322}