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}