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}