RPC Component
This is by far the simplest component. Essentially this is a thin gRPC server which proxies all requests to the store and block-producer components.
Its main function is to pre-validate all requests before sending them on. This means malformed or non-sensical requests get rejected before reaching the store and block-producer, reducing their load. Notably this also includes verifying the proofs of submitting transactions. This allows the block-producer to skip proof verification (it trusts the RPC component), reducing the load in this critical component.
RPC Versioning and the HTTP ACCEPT header
The RPC component allows clients to negotiate their desired Miden RPC version using the well-known HTTP ACCEPT header, using the following format:
application/vnd.miden; version=<version-req>; genesis=<genesis-commitment>
The version lets the client specify their supported version and the server will attempt to comply if it can. At this early stage, only client versions which are semver compatible with the
server version are likely to be accepted i.e. the server in all likely only supports a single version.
The genesis property is intended to let the client confirm they are on the correct network, by specifying the network's genesis commitment. This guards against operating on the wrong network,
as well as against network resets.
Query limits (GetLimits)
The RPC service exposes a GetLimits endpoint which returns the query parameter limits enforced by the server for
multi-value parameters (e.g. number of nullifiers, note tags, note IDs, account IDs).
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 |
|---|---|---|---|
GetAccount | storage_map_key | 64 | SMT proof generation for storage map keys is comparatively expensive |
GetNotesById | note_id | 100 | Notes can be large (~32 KiB), so this is intentionally tighter |
SyncNotes | note_tag | 1000 | Keeps note sync responses within payload budget |
SyncNullifiers | nullifier_prefix | 1000 | Bounds prefix-based nullifier scans |
SyncTransactions | account_id | 1000 | Bounds account filter fan-out and response size |
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.
Error Architecture
Error handling follows this pattern:
- Domain Errors: Business logic errors are defined in domain-specific enums
- gRPC Conversion: Domain errors are converted to gRPC
Statusobjects with structured details - Error Details: Specific error codes are embedded in
Status.detailsas single bytes
SubmitProvenTx Errors
Transaction submission errors are:
#![allow(unused)] fn main() { enum SubmitProvenTxGrpcError { Internal = 0, DeserializationFailed = 1, InvalidTransactionProof = 2, IncorrectAccountInitialCommitment = 3, InputNotesAlreadyConsumed = 4, UnauthenticatedNotesNotFound = 5, OutputNotesAlreadyExist = 6, TransactionExpired = 7, } }
Error codes are embedded as single bytes in Status.details