Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions crates/utils/src/limiter.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//! Limits for RPC and store parameters and payload sizes.
//!
//! # Rationale
//! - Parameter limits are kept at [`GENERAL_REQUEST_LIMIT`] items across all multi-value RPC
//! parameters. This caps worst-case SQL `IN` clauses and keeps responses comfortably under the 4
//! MiB payload budget enforced in the store.
//! - Parameter limits are kept across all multi-value RPC parameters. This caps worst-case SQL `IN`
//! clauses and keeps responses comfortably under the 4 MiB payload budget enforced in the store.
//! - Limits are enforced both at the RPC boundary and inside the store to prevent bypasses and to
//! avoid expensive queries even if validation is skipped earlier in the stack.
//! - `MAX_PAGINATED_PAYLOAD_BYTES` is set to 4 MiB (e.g. 1000 nullifier rows at ~36 B each, 1000
Expand Down Expand Up @@ -48,6 +47,9 @@ pub const MAX_RESPONSE_PAYLOAD_BYTES: usize = 4 * 1024 * 1024;

/// Used for the following RPC endpoints
/// * `state_sync`
///
/// Capped at 1000 account IDs to keep SQL `IN` clauses bounded and response payloads under the
/// 4 MB budget.
pub struct QueryParamAccountIdLimit;
impl QueryParamLimiter for QueryParamAccountIdLimit {
const PARAM_NAME: &str = "account_id";
Expand All @@ -56,6 +58,9 @@ impl QueryParamLimiter for QueryParamAccountIdLimit {

/// Used for the following RPC endpoints
/// * `select_nullifiers_by_prefix`
///
/// Capped at 1000 prefixes to keep queries and responses comfortably within the 4 MB payload
/// budget and to avoid unbounded prefix scans.
pub struct QueryParamNullifierPrefixLimit;
Comment on lines +62 to 64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to add the underlying assumptions to the comments explaining why 1000 is the right number. For example, 1000 prefixes could return a large number of nullifiers - but I think there is some pagination enforced where whatever is returned would still fit into 4 MB, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true for some of those endpoints, but does not apply to all of them since some are not paginated. I can add the approximation that was made to determine the limits

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For not-paginated ones we should also try to ensure that the response size does not exceed 4 MB. I think it would good to have documentation for every endpoint explaining how this limit is satisfied. This may highlight that we are not satisfying this limit somewhere.

The only endpoints that don't need that are the ones that are used for testing only (though, not sure if we have such user-facing endpoints).

impl QueryParamLimiter for QueryParamNullifierPrefixLimit {
const PARAM_NAME: &str = "nullifier_prefix";
Expand All @@ -66,6 +71,8 @@ impl QueryParamLimiter for QueryParamNullifierPrefixLimit {
/// * `select_nullifiers_by_prefix`
/// * `sync_nullifiers`
/// * `sync_state`
///
/// Capped at 1000 nullifiers to bound `IN` clauses and keep response sizes under the 4 MB budget.
pub struct QueryParamNullifierLimit;
impl QueryParamLimiter for QueryParamNullifierLimit {
const PARAM_NAME: &str = "nullifier";
Expand All @@ -74,6 +81,8 @@ impl QueryParamLimiter for QueryParamNullifierLimit {

/// Used for the following RPC endpoints
/// * `get_note_sync`
///
/// Capped at 1000 tags so note sync responses remain within the 4 MB payload budget.
pub struct QueryParamNoteTagLimit;
impl QueryParamLimiter for QueryParamNoteTagLimit {
const PARAM_NAME: &str = "note_tag";
Expand All @@ -82,20 +91,29 @@ impl QueryParamLimiter for QueryParamNoteTagLimit {

/// Used for the following RPC endpoints
/// `select_notes_by_id`
///
/// The limit is set to 100 notes to keep responses within the 4 MiB payload cap because individual
/// notes are bounded to roughly 32 KiB.
pub struct QueryParamNoteIdLimit;
impl QueryParamLimiter for QueryParamNoteIdLimit {
const PARAM_NAME: &str = "note_id";
const LIMIT: usize = GENERAL_REQUEST_LIMIT;
const LIMIT: usize = 100;
}

/// Used for internal queries retrieving note inclusion proofs by commitment.
///
/// Capped at 1000 commitments to keep internal proof lookups bounded and responses under the 4 MB
/// payload cap.
pub struct QueryParamNoteCommitmentLimit;
impl QueryParamLimiter for QueryParamNoteCommitmentLimit {
const PARAM_NAME: &str = "note_commitment";
const LIMIT: usize = GENERAL_REQUEST_LIMIT;
}

/// Only used internally, not exposed via public RPC.
///
/// Capped at 1000 block headers to bound internal batch operations and keep payloads below the
/// 4 MB limit.
pub struct QueryParamBlockLimit;
impl QueryParamLimiter for QueryParamBlockLimit {
const PARAM_NAME: &str = "block_header";
Expand Down