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, alloc::vec::Vec,
57 solana_sdk_ids::bpf_loader_upgradeable,
58};
59
60mod account_keys;
61
62#[cfg(feature = "std")]
63mod address_loader;
64pub mod compiled_instruction;
65mod compiled_keys;
66pub mod inline_nonce;
67pub mod inner_instruction;
68pub mod legacy;
69#[cfg(feature = "std")]
70mod sanitized;
71mod versions;
72#[cfg(feature = "std")]
73pub use address_loader::*;
74#[cfg(feature = "std")]
75pub use sanitized::*;
76pub use {
77 account_keys::*,
78 compiled_keys::CompileError,
79 legacy::Message,
80 solana_address::Address,
81 solana_hash::Hash,
82 solana_instruction::{AccountMeta, Instruction},
83 versions::*,
84};
85
86/// The length of a message header in bytes.
87pub const MESSAGE_HEADER_LENGTH: usize = 3;
88
89/// Describes the organization of a `Message`'s account keys.
90///
91/// Every [`Instruction`] specifies which accounts it may reference, or
92/// otherwise requires specific permissions of. Those specifications are:
93/// whether the account is read-only, or read-write; and whether the account
94/// must have signed the transaction containing the instruction.
95///
96/// Whereas individual `Instruction`s contain a list of all accounts they may
97/// access, along with their required permissions, a `Message` contains a
98/// single shared flat list of _all_ accounts required by _all_ instructions in
99/// a transaction. When building a `Message`, this flat list is created and
100/// `Instruction`s are converted to [`CompiledInstruction`]s. Those
101/// `CompiledInstruction`s then reference by index the accounts they require in
102/// the single shared account list.
103///
104/// [`Instruction`]: https://docs.rs/solana-instruction/latest/solana_instruction/struct.Instruction.html
105/// [`CompiledInstruction`]: crate::compiled_instruction::CompiledInstruction
106///
107/// The shared account list is ordered by the permissions required of the accounts:
108///
109/// - accounts that are writable and signers
110/// - accounts that are read-only and signers
111/// - accounts that are writable and not signers
112/// - accounts that are read-only and not signers
113///
114/// Given this ordering, the fields of `MessageHeader` describe which accounts
115/// in a transaction require which permissions.
116///
117/// When multiple transactions access the same read-only accounts, the runtime
118/// may process them in parallel, in a single [PoH] entry. Transactions that
119/// access the same read-write accounts are processed sequentially.
120///
121/// [PoH]: https://docs.solanalabs.com/consensus/synchronization
122#[cfg_attr(
123 feature = "frozen-abi",
124 derive(AbiExample, StableAbi, StableAbiSample),
125 frozen_abi(
126 abi_digest = "BoNk47PmBYTf1fzuJoZ8tcFm9EVnFjv8v7bDccreDvgB",
127 abi_serializer = ["bincode", "wincode"],
128 test_roundtrip = "eq_and_wire"
129 )
130)]
131#[cfg_attr(
132 feature = "serde",
133 derive(Deserialize, Serialize),
134 serde(rename_all = "camelCase")
135)]
136#[cfg_attr(feature = "wincode", derive(SchemaWrite, SchemaRead, UninitBuilder))]
137#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
138pub struct MessageHeader {
139 /// The number of signatures required for this message to be considered
140 /// valid. The signers of those signatures must match the first
141 /// `num_required_signatures` of [`Message::account_keys`].
142 // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
143 pub num_required_signatures: u8,
144
145 /// The last `num_readonly_signed_accounts` of the signed keys are read-only
146 /// accounts.
147 pub num_readonly_signed_accounts: u8,
148
149 /// The last `num_readonly_unsigned_accounts` of the unsigned keys are
150 /// read-only accounts.
151 pub num_readonly_unsigned_accounts: u8,
152}
153
154/// The definition of address lookup table accounts.
155///
156/// As used by the `crate::v0` message format.
157#[derive(Debug, PartialEq, Eq, Clone)]
158pub struct AddressLookupTableAccount {
159 pub key: Address,
160 pub addresses: Vec<Address>,
161}
162
163/// Returns true if the account at the specified index was requested to be
164/// writable.
165///
166/// This method should not be used directly. It is used by Legacy and V1
167/// message types.
168#[cfg(feature = "std")]
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/// Returns true if the account at the specified index is in the optional
180/// reserved account keys set.
181#[cfg(feature = "std")]
182#[inline(always)]
183fn is_account_maybe_reserved(
184 i: usize,
185 account_keys: &[Address],
186 reserved_account_keys: Option<&HashSet<Address>>,
187) -> bool {
188 let mut is_maybe_reserved = false;
189 if let Some(reserved_account_keys) = reserved_account_keys {
190 if let Some(key) = account_keys.get(i) {
191 is_maybe_reserved = reserved_account_keys.contains(key);
192 }
193 }
194 is_maybe_reserved
195}
196
197#[inline(always)]
198fn is_program_id_write_demoted(
199 i: usize,
200 account_keys: &[Address],
201 instructions: &[CompiledInstruction],
202) -> bool {
203 is_key_called_as_program(instructions, i) && !is_upgradeable_loader_present(account_keys)
204}
205
206#[inline(always)]
207fn is_key_called_as_program(instructions: &[CompiledInstruction], key_index: usize) -> bool {
208 if let Ok(key_index) = u8::try_from(key_index) {
209 instructions
210 .iter()
211 .any(|ix| ix.program_id_index == key_index)
212 } else {
213 false
214 }
215}
216
217/// Returns `true` if any account is the BPF upgradeable loader.
218#[inline(always)]
219fn is_upgradeable_loader_present(account_keys: &[Address]) -> bool {
220 account_keys
221 .iter()
222 .any(|&key| key == bpf_loader_upgradeable::id())
223}
224
225/// Returns true if the account at the specified index is writable by the
226/// instructions in this message. The `reserved_account_keys` param has been
227/// optional to allow clients to approximate writability without requiring
228/// fetching the latest set of reserved account keys. If this method is
229/// called by the runtime, the latest set of reserved account keys must be
230/// passed.
231#[cfg(feature = "std")]
232#[inline(always)]
233fn is_maybe_writable(
234 i: usize,
235 header: MessageHeader,
236 account_keys: &[Address],
237 instructions: &[CompiledInstruction],
238 reserved_account_keys: Option<&HashSet<Address>>,
239) -> bool {
240 (is_writable_index(i, header, account_keys))
241 && !is_account_maybe_reserved(i, account_keys, reserved_account_keys)
242 && !is_program_id_write_demoted(i, account_keys, instructions)
243}
244
245#[cfg(test)]
246mod tests {
247 use {
248 crate::{is_account_maybe_reserved, Message},
249 alloc::vec,
250 solana_address::Address,
251 std::collections::HashSet,
252 };
253
254 #[test]
255 fn test_is_account_maybe_reserved() {
256 let key0 = Address::new_unique();
257 let key1 = Address::new_unique();
258
259 let message = Message {
260 account_keys: vec![key0, key1],
261 ..Message::default()
262 };
263
264 let reserved_account_keys = HashSet::from([key1]);
265
266 assert!(!is_account_maybe_reserved(
267 0,
268 &message.account_keys,
269 Some(&reserved_account_keys)
270 ));
271 assert!(is_account_maybe_reserved(
272 1,
273 &message.account_keys,
274 Some(&reserved_account_keys)
275 ));
276 assert!(!is_account_maybe_reserved(
277 2,
278 &message.account_keys,
279 Some(&reserved_account_keys)
280 ));
281 assert!(!is_account_maybe_reserved(0, &message.account_keys, None));
282 assert!(!is_account_maybe_reserved(1, &message.account_keys, None));
283 assert!(!is_account_maybe_reserved(2, &message.account_keys, None));
284 }
285}