Skip to main content

agave_scheduler_bindings/
lib.rs

1#![cfg_attr(
2    not(feature = "agave-unstable-api"),
3    deprecated(
4        since = "3.1.0",
5        note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6                v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7                acknowledge use of an interface that may break without warning."
8    )
9)]
10#![no_std]
11
12//! Messages passed between agave and an external pack process.
13//! Messages are passed via `shaq::Consumer/Producer`.
14//!
15//! Memory freeing is responsibility of the external pack process,
16//! and is done via `rts-alloc` crate. It is also possible the external
17//! pack process allocates memory to pass to agave, BUT it will still be
18//! the responsibility of the external pack process to free that memory.
19//!
20//! Setting up the shared memory allocator and queues is done outside of
21//! agave - it can be done by the external pack process or another
22//! process. agave will just `join` shared memory regions, but not
23//! create them.
24//! Similarly, agave will not delete files used for shared memory regions.
25//! See `shaq` and `rts-alloc` crates for details.
26//!
27//! The basic architecture is as follows:
28//!        ┌───────────────┐       ┌─────────────────┐
29//!        │  tpu_to_pack  │       │ progress_tracker│
30//!        └───────┬───────┘       └───────┬─────────┘
31//!                │                       │
32//!                │                       │
33//!                │                       │
34//!             ┌──▼───────────────────────▼───┐
35//!             │  external scheduler          │
36//!             └─▲─────── ▲────────────────▲──┘
37//!               │        │                │
38//!               │        │                │
39//!           ┌───▼───┐ ┌──▼─────┐ ...  ┌───▼───┐
40//!           │worker1│ │worker2 │      │workerN│
41//!           └───────┘ └────────┘      └───────┘
42//!
43//! - [`TpuToPackMessage`] are sent from `tpu_to_pack` queue to the
44//!   external scheduler process. This passes in tpu transactions to be scheduled,
45//!   and optionally vote transactions.
46//! - [`ProgressMessage`] are sent from `progress_tracker` queue to the
47//!   external scheduler process. This passes information about leader status
48//!   and slot progress to the external scheduler process.
49//! - [`PackToWorkerMessage`] are sent from the external scheduler process
50//!   to worker threads within agave. This passes a batch of transactions
51//!   to be processed by the worker threads. This processing can also involve
52//!   resolving the transactions' addresses, or similar operations beyond
53//!   execution.
54//! - [`WorkerToPackMessage`] are sent from worker threads within agave
55//!   back to the external scheduler process. This passes back the results
56//!   of processing the transactions.
57//!
58
59/// Reference to a transaction that can shared safely across processes.
60#[cfg_attr(
61    feature = "dev-context-only-utils",
62    derive(Debug, Clone, Copy, PartialEq, Eq)
63)]
64#[repr(C)]
65pub struct SharableTransactionRegion {
66    /// Offset within the shared memory allocator.
67    pub offset: usize,
68    /// Length of the transaction in bytes.
69    pub length: u32,
70}
71
72/// Reference to an array of Pubkeys that can be shared safely across processes.
73#[cfg_attr(
74    feature = "dev-context-only-utils",
75    derive(Debug, Clone, Copy, PartialEq, Eq)
76)]
77#[repr(C)]
78pub struct SharablePubkeys {
79    /// Offset within the shared memory allocator.
80    pub offset: usize,
81    /// Number of pubkeys in the array.
82    /// IF 0, indicates no pubkeys and no allocation needing to be freed.
83    pub num_pubkeys: u32,
84}
85
86/// Reference to an array of [`SharableTransactionRegion`] that can be shared safely
87/// across processes.
88/// General flow:
89/// 1. External pack process allocates memory for
90///    `num_transactions` [`SharableTransactionRegion`].
91/// 2. External pack sends a [`PackToWorkerMessage`] with `batch`.
92/// 3. agave processes the transactions and sends back a [`WorkerToPackMessage`]
93///    with the same `batch`.
94/// 4. External pack process frees all transaction memory pointed to by the
95///    [`SharableTransactionRegion`] in the batch, then frees the memory for
96///    the array of [`SharableTransactionRegion`].
97#[cfg_attr(feature = "dev-context-only-utils", derive(Debug, PartialEq, Eq))]
98#[derive(Clone, Copy)]
99#[repr(C)]
100pub struct SharableTransactionBatchRegion {
101    /// Number of transactions in the batch.
102    pub num_transactions: u8,
103    /// Offset within the shared memory allocator for the batch of transactions.
104    /// The transactions are laid out back-to-back in memory as a
105    /// [`SharableTransactionRegion`] with size `num_transactions`.
106    pub transactions_offset: usize,
107}
108/// Reference to an array of response messages.
109/// General flow:
110/// 1. agave allocates memory for `num_transaction_responses` inner messages.
111/// 2. agave sends a [`WorkerToPackMessage`] with `responses`.
112/// 3. External pack process processes the inner messages. Potentially freeing
113///    any memory within each inner message (see [`worker_message_types`] for details).
114#[cfg_attr(
115    feature = "dev-context-only-utils",
116    derive(Debug, Clone, Copy, PartialEq, Eq)
117)]
118#[repr(C)]
119pub struct TransactionResponseRegion {
120    /// Tag indicating the type of message.
121    /// See [`worker_message_types`] for details.
122    /// All inner messages/responses per trasaction will be of the same type.
123    pub tag: u8,
124    /// The number of transactions in the original message.
125    /// This corresponds to the number of inner response
126    /// messages that will be pointed to by `response_offset`.
127    /// This MUST be the same as `batch.num_transactions`.
128    pub num_transaction_responses: u8,
129    /// Offset within the shared memory allocator for the array of
130    /// inner messages.
131    /// The inner messages are laid out back-to-back in memory starting at
132    /// this offset. The type of each inner message is indicated by `tag`.
133    /// There are `num_transaction_responses` inner messages.
134    /// See [`worker_message_types`] for details on the inner message types.
135    pub transaction_responses_offset: usize,
136}
137
138/// Message: [TPU -> Pack]
139/// TPU passes transactions to the external pack process.
140/// This is also a transfer of ownership of the transaction:
141///   the external pack process is responsible for freeing the memory.
142#[cfg_attr(
143    feature = "dev-context-only-utils",
144    derive(Debug, Clone, Copy, PartialEq, Eq)
145)]
146#[repr(C)]
147pub struct TpuToPackMessage {
148    pub transaction: SharableTransactionRegion,
149    /// See [`tpu_message_flags`] for details.
150    pub flags: u8,
151    /// The source address of the transaction.
152    /// IPv6-mapped IPv4 addresses: `::ffff:a.b.c.d`
153    /// where a.b.c.d is the IPv4 address.
154    /// See <https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2>.
155    pub src_addr: [u8; 16],
156}
157
158pub mod tpu_message_flags {
159    /// No special flags.
160    pub const NONE: u8 = 0;
161
162    /// The transaction is a simple vote transaction.
163    pub const IS_SIMPLE_VOTE: u8 = 1 << 0;
164    /// The transaction was forwarded by a validator node.
165    pub const FORWARDED: u8 = 1 << 1;
166    /// The transaction was sent from a staked node.
167    pub const FROM_STAKED_NODE: u8 = 1 << 2;
168}
169
170/// Indicates the node is not leader.
171pub const IS_NOT_LEADER: u8 = 0;
172/// Indicates the node is leader.
173pub const IS_LEADER: u8 = 1;
174
175/// Message: [Agave -> Pack]
176/// Agave passes leader status to the external pack process.
177#[cfg_attr(
178    feature = "dev-context-only-utils",
179    derive(Debug, Clone, Copy, PartialEq, Eq)
180)]
181#[repr(C)]
182pub struct ProgressMessage {
183    /// Indicates if node is currently leader or not.
184    /// [`IS_LEADER`] if the node is leader.
185    /// [`IS_NOT_LEADER`] if the node is not leader.
186    /// Other values should be considered invalid.
187    pub leader_state: u8,
188    /// The current slot. This along with a leader schedule is not sufficient
189    /// for determining if the node is currently leader. There is a slight
190    /// delay between when a node is supposed to begin its' leader slot, and
191    /// when a bank is ready for processing transactions as leader.
192    /// Using [`Self::leader_state`] for determining if the node is leader
193    /// and has a bank available.
194    pub current_slot: u64,
195    /// Next known leader slot or u64::MAX if unknown.
196    /// This will **not** include the current slot if leader.
197    /// Node is leader for contiguous slots in the inclusive range
198    /// [[`Self::next_leader_slot`], [`Self::leader_range_end`]].
199    pub next_leader_slot: u64,
200    /// Next known leader slot range end (inclusive) or u64::MAX if unknown.
201    /// Node is leader for contiguous slots in the inclusive range
202    /// [[`Self::next_leader_slot`], [`Self::leader_range_end`]].
203    pub leader_range_end: u64,
204    /// The remaining cost units allowed to be packed in the block.
205    /// i.e. block_limit - current_cost_units_used.
206    /// Only valid if currently leader, otherwise the value is undefined.
207    pub remaining_cost_units: u64,
208    /// Progress through the current slot in percentage.
209    pub current_slot_progress: u8,
210}
211
212/// Maximum number of transactions allowed in a [`PackToWorkerMessage`].
213/// If the number of transactions exceeds this value, agave will
214/// not process the message.
215//
216// The reason for this constraint is because rts-alloc currently only
217// supports up to 4096 byte allocations. We must ensure that the
218// `TransactionResponseRegion` is able to contain responses for all
219// transactions sent. This is a conservative bound.
220pub const MAX_TRANSACTIONS_PER_MESSAGE: usize = 64;
221
222/// Message: [Pack -> Worker]
223/// External pack processe passes transactions to worker threads within agave.
224///
225/// These messages do not transfer ownership of the transactions.
226/// The external pack process is still responsible for freeing the memory.
227#[cfg_attr(
228    feature = "dev-context-only-utils",
229    derive(Debug, Clone, Copy, PartialEq, Eq)
230)]
231#[repr(C)]
232pub struct PackToWorkerMessage {
233    /// Flags on how to handle this message.
234    /// See [`pack_message_flags`] for details.
235    pub flags: u16,
236    /// If [`pack_message_flags::RESOLVE`] flag is not set, this is the
237    /// maximum slot the transactions can be processed in. If the working
238    /// bank's slot in the worker thread is greater than this slot,
239    /// the transaction will not be processed.
240    pub max_execution_slot: u64,
241    /// Offset and number of transactions in the batch.
242    /// See [`SharableTransactionBatchRegion`] for details.
243    /// Agave will return this batch in the response message, it is
244    /// the responsibility of the external pack process to free the memory
245    /// ONLY after receiving the response message.
246    pub batch: SharableTransactionBatchRegion,
247}
248
249pub mod pack_message_flags {
250    //! Flags for [`crate::PackToWorkerMessage::flags`].
251    //! These flags can be ORed together so must be unique bits, with
252    //! the exception of [`NONE`].
253    //! The *default* behavior, [`NONE`], is to attempt execution and
254    //! inclusion in the specified `max_execution_slot`.
255
256    /// No special handling - execute the transactions normally.
257    pub const NONE: u16 = 0;
258
259    /// Transactions on the [`super::PackToWorkerMessage`] should have their
260    /// addresses resolved.
261    ///
262    /// If this flag, the transaction will attempt to be executed and included
263    /// in the current block.
264    pub const RESOLVE: u16 = 1 << 1;
265}
266
267/// The message was not processed.
268pub const NOT_PROCESSED: u8 = 0;
269/// The message was processed.
270pub const PROCESSED: u8 = 1;
271
272/// Message: [Worker -> Pack]
273/// Message from worker threads in response to a [`PackToWorkerMessage`].
274#[cfg_attr(
275    feature = "dev-context-only-utils",
276    derive(Debug, Clone, Copy, PartialEq, Eq)
277)]
278#[repr(C)]
279pub struct WorkerToPackMessage {
280    /// Offset and number of transactions in the batch.
281    /// See [`SharableTransactionBatchRegion`] for details.
282    /// Once the external pack process receives this message,
283    /// it is responsible for freeing the memory for this batch,
284    /// and is safe to do so - agave will hold no references to this memory
285    /// after sending this message.
286    pub batch: SharableTransactionBatchRegion,
287    /// [`PROCESSED`] if the message was processed.
288    /// [`NOT_PROCESSED`] if the message could not be processed. This will occur
289    /// if the passed message was invalid, and could indicate an issue
290    /// with the external pack process.
291    /// If  [`NOT_PROCESSED`], the value of [`Self::responses`] is undefined.
292    /// Other values should be considered invalid.
293    pub processed: u8,
294    /// Response per transaction in the batch.
295    /// If [`Self::processed`] is false, this field is undefined.
296    /// See [`TransactionResponseRegion`] for details.
297    pub responses: TransactionResponseRegion,
298}
299
300pub mod worker_message_types {
301    use crate::SharablePubkeys;
302
303    /// Tag indicating [`ExecutionResponse`] inner message.
304    pub const EXECUTION_RESPONSE: u8 = 0;
305
306    /// Response to pack for a transaction that attempted execution.
307    /// This response will only be sent if the original message flags
308    /// requested execution i.e. not [`super::pack_message_flags::RESOLVE`].
309    #[cfg_attr(
310        feature = "dev-context-only-utils",
311        derive(Debug, Clone, Copy, PartialEq, Eq)
312    )]
313    #[repr(C)]
314    pub struct ExecutionResponse {
315        /// Indicates if the transaction was included in the block or not.
316        /// If [`not_included_reasons::NONE`], the transaction was included.
317        pub not_included_reason: u8,
318        /// If included, cost units used by the transaction.
319        pub cost_units: u64,
320        /// If included, the fee-payer balance after execution.
321        pub fee_payer_balance: u64,
322    }
323
324    pub mod not_included_reasons {
325        /// The transaction was included in the block.
326        pub const NONE: u8 = 0;
327        /// The transaction could not attempt processing because the
328        /// working bank was unavailable.
329        pub const BANK_NOT_AVAILABLE: u8 = 1;
330        /// The transaction could not be processed because the `slot`
331        /// in the passed message did not match the working bank's slot.
332        pub const SLOT_MISMATCH: u8 = 2;
333
334        /// Transaction dropped because the batch was marked as
335        /// all_or_nothing and a different transacation failed.
336        pub const ALL_OR_NOTHING_BATCH_FAILURE: u8 = 3;
337
338        // Remaining errors are translations from SDK.
339        // Moved up to 64 so we have room to add custom reasons in the future.
340        // Also allows for easy distinguishing between custom scheduling errors
341        // and sdk errors.
342
343        /// An account is already being processed in another transaction in a way
344        /// that does not support parallelism
345        pub const ACCOUNT_IN_USE: u8 = 64;
346        /// A `Pubkey` appears twice in the transaction's `account_keys`.  Instructions can reference
347        /// `Pubkey`s more than once but the message must contain a list with no duplicate keys
348        pub const ACCOUNT_LOADED_TWICE: u8 = 65;
349        /// Attempt to debit an account but found no record of a prior credit.
350        pub const ACCOUNT_NOT_FOUND: u8 = 66;
351        /// Attempt to load a program that does not exist
352        pub const PROGRAM_ACCOUNT_NOT_FOUND: u8 = 67;
353        /// The from `Pubkey` does not have sufficient balance to pay the fee to schedule the transaction
354        pub const INSUFFICIENT_FUNDS_FOR_FEE: u8 = 68;
355        /// This account may not be used to pay transaction fees
356        pub const INVALID_ACCOUNT_FOR_FEE: u8 = 69;
357        /// The bank has seen this transaction before. This can occur under normal operation
358        pub const ALREADY_PROCESSED: u8 = 70;
359        /// The bank has not seen the given `recent_blockhash`
360        pub const BLOCKHASH_NOT_FOUND: u8 = 71;
361        /// An error occurred while processing an instruction.
362        pub const INSTRUCTION_ERROR: u8 = 72;
363        /// Loader call chain is too deep
364        pub const CALL_CHAIN_TOO_DEEP: u8 = 73;
365        /// Transaction requires a fee but has no signature present
366        pub const MISSING_SIGNATURE_FOR_FEE: u8 = 74;
367        /// Transaction contains an invalid account reference
368        pub const INVALID_ACCOUNT_INDEX: u8 = 75;
369        /// Transaction did not pass signature verification
370        pub const SIGNATURE_FAILURE: u8 = 76;
371        /// This program may not be used for executing instructions
372        pub const INVALID_PROGRAM_FOR_EXECUTION: u8 = 77;
373        /// Transaction failed to sanitize accounts offsets correctly
374        pub const SANITIZE_FAILURE: u8 = 78;
375        pub const CLUSTER_MAINTENANCE: u8 = 79;
376        /// Transaction processing left an account with an outstanding borrowed reference
377        pub const ACCOUNT_BORROW_OUTSTANDING: u8 = 80;
378        /// Transaction would exceed max Block Cost Limit
379        pub const WOULD_EXCEED_MAX_BLOCK_COST_LIMIT: u8 = 81;
380        /// Transaction version is unsupported
381        pub const UNSUPPORTED_VERSION: u8 = 82;
382        /// Transaction loads a writable account that cannot be written
383        pub const INVALID_WRITABLE_ACCOUNT: u8 = 83;
384        /// Transaction would exceed max account limit within the block
385        pub const WOULD_EXCEED_MAX_ACCOUNT_COST_LIMIT: u8 = 84;
386        /// Transaction would exceed account data limit within the block
387        pub const WOULD_EXCEED_ACCOUNT_DATA_BLOCK_LIMIT: u8 = 85;
388        /// Transaction locked too many accounts
389        pub const TOO_MANY_ACCOUNT_LOCKS: u8 = 86;
390        /// Address lookup table not found
391        pub const ADDRESS_LOOKUP_TABLE_NOT_FOUND: u8 = 87;
392        /// Attempted to lookup addresses from an account owned by the wrong program
393        pub const INVALID_ADDRESS_LOOKUP_TABLE_OWNER: u8 = 88;
394        /// Attempted to lookup addresses from an invalid account
395        pub const INVALID_ADDRESS_LOOKUP_TABLE_DATA: u8 = 89;
396        /// Address table lookup uses an invalid index
397        pub const INVALID_ADDRESS_LOOKUP_TABLE_INDEX: u8 = 90;
398        /// Transaction leaves an account with a lower balance than rent-exempt minimum
399        pub const INVALID_RENT_PAYING_ACCOUNT: u8 = 91;
400        /// Transaction would exceed max Vote Cost Limit
401        pub const WOULD_EXCEED_MAX_VOTE_COST_LIMIT: u8 = 92;
402        /// Transaction would exceed total account data limit
403        pub const WOULD_EXCEED_ACCOUNT_DATA_TOTAL_LIMIT: u8 = 93;
404        /// Transaction contains a duplicate instruction that is not allowed
405        pub const DUPLICATE_INSTRUCTION: u8 = 94;
406        /// Transaction results in an account with insufficient funds for rent
407        pub const INSUFFICIENT_FUNDS_FOR_RENT: u8 = 95;
408        /// Transaction exceeded max loaded accounts data size cap
409        pub const MAX_LOADED_ACCOUNTS_DATA_SIZE_EXCEEDED: u8 = 96;
410        /// LoadedAccountsDataSizeLimit set for transaction must be greater than 0.
411        pub const INVALID_LOADED_ACCOUNTS_DATA_SIZE_LIMIT: u8 = 97;
412        /// Sanitized transaction differed before/after feature activation. Needs to be resanitized.
413        pub const RESANITIZATION_NEEDED: u8 = 98;
414        /// Program execution is temporarily restricted on an account.
415        pub const PROGRAM_EXECUTION_TEMPORARILY_RESTRICTED: u8 = 99;
416        /// The total balance before the transaction does not equal the total balance after the transaction
417        pub const UNBALANCED_TRANSACTION: u8 = 100;
418        /// Program cache hit max limit.
419        pub const PROGRAM_CACHE_HIT_MAX_LIMIT: u8 = 101;
420
421        // This error in agave is only internal, and to avoid updating the sdk
422        // it is re-used for mapping into `ALL_OR_NOTHING_BATCH_FAILURE`.
423        // /// Commit cancelled internally.
424        // pub const COMMIT_CANCELLED: u8 = 102;
425    }
426
427    /// Tag indicating [`Resolved`] inner message.
428    pub const RESOLVED: u8 = 1;
429
430    /// Resolving was unsuccessful.
431    pub const RESOLVE_FAILURE: u8 = 0;
432    /// Resolving was successful.
433    pub const RESOLVE_SUCCESS: u8 = 1;
434
435    #[cfg_attr(
436        feature = "dev-context-only-utils",
437        derive(Debug, Clone, Copy, PartialEq, Eq)
438    )]
439    #[repr(C)]
440    pub struct Resolved {
441        /// Indicates if resolution was successful.
442        /// [`RESOLVE_SUCCESS`] if resolving succeeded.
443        /// [`RESOLVE_FAILURE`] if resolved failed.
444        /// Other values should be considered invalid.
445        pub success: u8,
446        /// Slot of the bank used for resolution.
447        pub slot: u64,
448        /// Minimum deactivation slot of any ALT if any.
449        /// u64::MAX if no ALTs or deactivation.
450        pub min_alt_deactivation_slot: u64,
451        /// Resolved pubkeys - writable then readonly.
452        /// Freeing this memory is the responsiblity of the external
453        /// pack process.
454        pub resolved_pubkeys: SharablePubkeys,
455    }
456}