miden_node_utils/limiter.rs
1//! Limits for RPC and store parameters and payload sizes.
2//!
3//! # Rationale
4//! - Parameter limits are kept across all multi-value RPC parameters. This caps worst-case SQL `IN`
5//! clauses and keeps responses comfortably under the 4 MiB payload budget enforced in the store.
6//! - Limits are enforced both at the RPC boundary and inside the store to prevent bypasses and to
7//! avoid expensive queries even if validation is skipped earlier in the stack.
8//! - `MAX_PAGINATED_PAYLOAD_BYTES` is set to 4 MiB (e.g. 1000 nullifier rows at ~36 B each, 1000
9//! transactions summaries streamed in chunks).
10//!
11//! Add new limits here so callers share the same values and rationale.
12
13/// Basic request limit.
14pub const GENERAL_REQUEST_LIMIT: usize = 1000;
15
16#[allow(missing_docs)]
17#[derive(Debug, thiserror::Error)]
18#[error("parameter {which} exceeded limit {limit}: {size}")]
19pub struct QueryLimitError {
20 which: &'static str,
21 size: usize,
22 limit: usize,
23}
24
25/// Checks limits against the desired query parameters, per query parameter and
26/// bails if they exceed a defined value.
27pub trait QueryParamLimiter {
28 /// Name of the parameter to mention in the error.
29 const PARAM_NAME: &'static str;
30 /// Limit that causes a bail if exceeded.
31 const LIMIT: usize;
32 /// Do the actual check.
33 fn check(size: usize) -> Result<(), QueryLimitError> {
34 if size > Self::LIMIT {
35 Err(QueryLimitError {
36 which: Self::PARAM_NAME,
37 size,
38 limit: Self::LIMIT,
39 })?;
40 }
41 Ok(())
42 }
43}
44
45/// Maximum payload size (in bytes) for paginated responses returned by the
46/// store.
47pub const MAX_RESPONSE_PAYLOAD_BYTES: usize = 4 * 1024 * 1024;
48
49/// Used for the following RPC endpoints
50/// * `state_sync`
51///
52/// Capped at 1000 account IDs to keep SQL `IN` clauses bounded and response payloads under the
53/// 4 MB budget.
54pub struct QueryParamAccountIdLimit;
55impl QueryParamLimiter for QueryParamAccountIdLimit {
56 const PARAM_NAME: &str = "account_id";
57 const LIMIT: usize = GENERAL_REQUEST_LIMIT;
58}
59
60/// Used for the following RPC endpoints
61/// * `select_nullifiers_by_prefix`
62///
63/// Capped at 1000 prefixes to keep queries and responses comfortably within the 4 MB payload
64/// budget and to avoid unbounded prefix scans.
65pub struct QueryParamNullifierPrefixLimit;
66impl QueryParamLimiter for QueryParamNullifierPrefixLimit {
67 const PARAM_NAME: &str = "nullifier_prefix";
68 const LIMIT: usize = GENERAL_REQUEST_LIMIT;
69}
70
71/// Used for the following RPC endpoints
72/// * `select_nullifiers_by_prefix`
73/// * `sync_nullifiers`
74/// * `sync_state`
75///
76/// Capped at 1000 nullifiers to bound `IN` clauses and keep response sizes under the 4 MB budget.
77pub struct QueryParamNullifierLimit;
78impl QueryParamLimiter for QueryParamNullifierLimit {
79 const PARAM_NAME: &str = "nullifier";
80 const LIMIT: usize = GENERAL_REQUEST_LIMIT;
81}
82
83/// Used for the following RPC endpoints
84/// * `get_note_sync`
85///
86/// Capped at 1000 tags so note sync responses remain within the 4 MB payload budget.
87pub struct QueryParamNoteTagLimit;
88impl QueryParamLimiter for QueryParamNoteTagLimit {
89 const PARAM_NAME: &str = "note_tag";
90 const LIMIT: usize = GENERAL_REQUEST_LIMIT;
91}
92
93/// Used for the following RPC endpoints
94/// `select_notes_by_id`
95///
96/// The limit is set to 100 notes to keep responses within the 4 MiB payload cap because individual
97/// notes are bounded to roughly 32 KiB.
98pub struct QueryParamNoteIdLimit;
99impl QueryParamLimiter for QueryParamNoteIdLimit {
100 const PARAM_NAME: &str = "note_id";
101 const LIMIT: usize = 100;
102}
103
104/// Used for internal queries retrieving note inclusion proofs by commitment.
105///
106/// Capped at 1000 commitments to keep internal proof lookups bounded and responses under the 4 MB
107/// payload cap.
108pub struct QueryParamNoteCommitmentLimit;
109impl QueryParamLimiter for QueryParamNoteCommitmentLimit {
110 const PARAM_NAME: &str = "note_commitment";
111 const LIMIT: usize = GENERAL_REQUEST_LIMIT;
112}
113
114/// Only used internally, not exposed via public RPC.
115///
116/// Capped at 1000 block headers to bound internal batch operations and keep payloads below the
117/// 4 MB limit.
118pub struct QueryParamBlockLimit;
119impl QueryParamLimiter for QueryParamBlockLimit {
120 const PARAM_NAME: &str = "block_header";
121 const LIMIT: usize = GENERAL_REQUEST_LIMIT;
122}
123
124/// Used for the following RPC endpoints
125/// * `get_account`
126///
127/// Capped at 64 total storage map keys across all slots to limit the number of SMT proofs
128/// returned.
129pub struct QueryParamStorageMapKeyTotalLimit;
130impl QueryParamLimiter for QueryParamStorageMapKeyTotalLimit {
131 const PARAM_NAME: &str = "storage_map_key";
132 const LIMIT: usize = 64;
133}