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