Skip to content
Closed
Show file tree
Hide file tree
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
6 changes: 4 additions & 2 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use miden_node_utils::limiter::{
QueryParamNoteIdLimit,
QueryParamNoteTagLimit,
QueryParamNullifierLimit,
QueryParamNullifierPrefixLimit,
QueryParamStorageMapKeyTotalLimit,
};
use miden_protocol::batch::{ProposedBatch, ProvenBatch};
Expand Down Expand Up @@ -216,7 +217,7 @@ impl api_server::Api for RpcService {
) -> Result<Response<proto::rpc::SyncNullifiersResponse>, Status> {
debug!(target: COMPONENT, request = ?request.get_ref());

check::<QueryParamNullifierLimit>(request.get_ref().nullifiers.len())?;
check::<QueryParamNullifierPrefixLimit>(request.get_ref().nullifiers.len())?;

self.store.clone().sync_nullifiers(request).await
}
Expand Down Expand Up @@ -652,6 +653,7 @@ static RPC_LIMITS: LazyLock<proto::rpc::RpcLimits> = LazyLock::new(|| {
use QueryParamNoteIdLimit as NoteId;
use QueryParamNoteTagLimit as NoteTag;
use QueryParamNullifierLimit as Nullifier;
use QueryParamNullifierPrefixLimit as NullifierPrefix;
use QueryParamStorageMapKeyTotalLimit as StorageMapKeyTotal;

proto::rpc::RpcLimits {
Expand All @@ -662,7 +664,7 @@ static RPC_LIMITS: LazyLock<proto::rpc::RpcLimits> = LazyLock::new(|| {
),
(
"SyncNullifiers".into(),
endpoint_limits(&[(Nullifier::PARAM_NAME, Nullifier::LIMIT)]),
endpoint_limits(&[(NullifierPrefix::PARAM_NAME, NullifierPrefix::LIMIT)]),
),
(
"SyncTransactions".into(),
Expand Down
23 changes: 23 additions & 0 deletions crates/rpc/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use miden_node_utils::limiter::{
QueryParamAccountIdLimit,
QueryParamLimiter,
QueryParamNoteIdLimit,
QueryParamNoteTagLimit,
QueryParamNullifierLimit,
QueryParamNullifierPrefixLimit,
};
use miden_protocol::Word;
use miden_protocol::account::delta::AccountUpdateDetails;
Expand Down Expand Up @@ -576,6 +578,27 @@ async fn get_limits_endpoint() {
QueryParamAccountIdLimit::LIMIT
);

// Verify SyncNullifiers endpoint
let sync_nullifiers =
limits.endpoints.get("SyncNullifiers").expect("SyncNullifiers should exist");
assert_eq!(
sync_nullifiers.parameters.get(QueryParamNullifierPrefixLimit::PARAM_NAME),
Some(&(QueryParamNullifierPrefixLimit::LIMIT as u32)),
"SyncNullifiers {} limit should be {}",
QueryParamNullifierPrefixLimit::PARAM_NAME,
QueryParamNullifierPrefixLimit::LIMIT
);

// Verify SyncNotes endpoint
let sync_notes = limits.endpoints.get("SyncNotes").expect("SyncNotes should exist");
assert_eq!(
sync_notes.parameters.get(QueryParamNoteTagLimit::PARAM_NAME),
Some(&(QueryParamNoteTagLimit::LIMIT as u32)),
"SyncNotes {} limit should be {}",
QueryParamNoteTagLimit::PARAM_NAME,
QueryParamNoteTagLimit::LIMIT
);

// SyncAccountVault and SyncAccountStorageMaps accept a singular account_id,
// not a repeated list, so they do not have list parameter limits.
assert!(
Expand Down
4 changes: 4 additions & 0 deletions crates/store/src/server/rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use miden_node_utils::limiter::{
QueryParamNoteIdLimit,
QueryParamNoteTagLimit,
QueryParamNullifierLimit,
QueryParamNullifierPrefixLimit,
};
use miden_protocol::Word;
use miden_protocol::account::AccountId;
Expand Down Expand Up @@ -94,6 +95,9 @@ impl rpc_server::Rpc for StoreApi {
return Err(SyncNullifiersError::InvalidPrefixLength(request.prefix_len).into());
}

// Validate nullifier prefix list size before querying state.
check::<QueryParamNullifierPrefixLimit>(request.nullifiers.len())?;

let chain_tip = self.state.chain_tip(Finality::Committed).await;
let block_range =
read_block_range::<SyncNullifiersError>(request.block_range, "SyncNullifiersRequest")?
Expand Down
19 changes: 18 additions & 1 deletion docs/internal/src/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ multi-value parameters (e.g. number of nullifiers, note tags, note IDs, account
These limits are defined centrally in `miden_node_utils::limiter` and are enforced at the RPC boundary (and also inside
the store) to keep database queries bounded and to keep response payloads within the ~4 MB budget.

`GENERAL_REQUEST_LIMIT` is currently `1000`, and endpoint-specific limits are:

| Endpoint | Parameter | Limit | Rationale |
|---|---:|---:|---|
| `CheckNullifiers` | `nullifier` | `1000` | Bounds `IN`-style lookups and keeps responses under payload budget |
| `SyncNullifiers` | `nullifier_prefix` | `1000` | Bounds prefix-based nullifier scans |
| `SyncNotes` | `note_tag` | `1000` | Keeps note sync responses within payload budget |
| `GetNotesById` | `note_id` | `100` | Notes can be large (~32 KiB), so this is intentionally tighter |
| `SyncTransactions` | `account_id` | `1000` | Bounds account filter fan-out and response size |
| `GetAccount` | `storage_map_key` | `64` | SMT proof generation for storage map keys is comparatively expensive |

Additional internal-only limits in `miden_node_utils::limiter` (not surfaced by `GetLimits`) include:

| Parameter | Limit | Used by |
|---:|---:|---|
| `note_commitment` | `1000` | Internal note proof lookups |
| `block_header` | `1000` | Internal batch/block header operations |

## Error Handling

The RPC component uses domain-specific error enums for structured error reporting instead of proto-generated error types. This provides better control over error codes and makes error handling more maintainable.
Expand Down Expand Up @@ -57,4 +75,3 @@ enum SubmitProvenTransactionGrpcError {
```

Error codes are embedded as single bytes in `Status.details`