Skip to content

Commit 50486d5

Browse files
authored
Merge branch 'next' into santiagopittella-update-base-vm
2 parents e1c1195 + 5d6f3b5 commit 50486d5

8 files changed

Lines changed: 297 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Improved tracing in `miden-network-monitor` binary ([#1366](https://github.com/0xMiden/miden-node/pull/1366)).
1616
- Integrated RPC stack with Validator component for transaction validation ([#1457](https://github.com/0xMiden/miden-node/pull/1457)).
1717
- Added explorer status to the `miden-network-monitor` binary ([#1450](https://github.com/0xMiden/miden-node/pull/1450)).
18+
- Added `GetLimits` endpoint to the RPC server ([#1410](https://github.com/0xMiden/miden-node/pull/1410)).
1819

1920
### Changes
2021

crates/proto/src/generated/rpc.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,27 @@ pub struct TransactionRecord {
543543
#[prost(message, optional, tag = "2")]
544544
pub header: ::core::option::Option<super::transaction::TransactionHeader>,
545545
}
546+
/// Represents the query parameter limits for RPC endpoints.
547+
#[derive(Clone, PartialEq, ::prost::Message)]
548+
pub struct RpcLimits {
549+
/// Maps RPC endpoint names to their parameter limits.
550+
/// Key: endpoint name (e.g., "CheckNullifiers", "SyncState")
551+
/// Value: map of parameter names to their limit values
552+
#[prost(map = "string, message", tag = "1")]
553+
pub endpoints: ::std::collections::HashMap<
554+
::prost::alloc::string::String,
555+
EndpointLimits,
556+
>,
557+
}
558+
/// Represents the parameter limits for a single endpoint.
559+
#[derive(Clone, PartialEq, ::prost::Message)]
560+
pub struct EndpointLimits {
561+
/// Maps parameter names to their limit values.
562+
/// Key: parameter name (e.g., "nullifier", "account_id")
563+
/// Value: limit value
564+
#[prost(map = "string, uint32", tag = "1")]
565+
pub parameters: ::std::collections::HashMap<::prost::alloc::string::String, u32>,
566+
}
546567
/// Generated client implementations.
547568
pub mod api_client {
548569
#![allow(
@@ -1049,6 +1070,29 @@ pub mod api_client {
10491070
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "SyncTransactions"));
10501071
self.inner.unary(req, path, codec).await
10511072
}
1073+
/// Returns the query parameter limits configured for RPC methods.
1074+
///
1075+
/// These define the maximum number of each parameter a method will accept.
1076+
/// Exceeding the limit will result in the request being rejected and you should instead send
1077+
/// multiple smaller requests.
1078+
pub async fn get_limits(
1079+
&mut self,
1080+
request: impl tonic::IntoRequest<()>,
1081+
) -> std::result::Result<tonic::Response<super::RpcLimits>, tonic::Status> {
1082+
self.inner
1083+
.ready()
1084+
.await
1085+
.map_err(|e| {
1086+
tonic::Status::unknown(
1087+
format!("Service was not ready: {}", e.into()),
1088+
)
1089+
})?;
1090+
let codec = tonic_prost::ProstCodec::default();
1091+
let path = http::uri::PathAndQuery::from_static("/rpc.Api/GetLimits");
1092+
let mut req = request.into_request();
1093+
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "GetLimits"));
1094+
self.inner.unary(req, path, codec).await
1095+
}
10521096
}
10531097
}
10541098
/// Generated server implementations.
@@ -1234,6 +1278,15 @@ pub mod api_server {
12341278
tonic::Response<super::SyncTransactionsResponse>,
12351279
tonic::Status,
12361280
>;
1281+
/// Returns the query parameter limits configured for RPC methods.
1282+
///
1283+
/// These define the maximum number of each parameter a method will accept.
1284+
/// Exceeding the limit will result in the request being rejected and you should instead send
1285+
/// multiple smaller requests.
1286+
async fn get_limits(
1287+
&self,
1288+
request: tonic::Request<()>,
1289+
) -> std::result::Result<tonic::Response<super::RpcLimits>, tonic::Status>;
12371290
}
12381291
/// RPC API for the RPC component
12391292
#[derive(Debug)]
@@ -2027,6 +2080,45 @@ pub mod api_server {
20272080
};
20282081
Box::pin(fut)
20292082
}
2083+
"/rpc.Api/GetLimits" => {
2084+
#[allow(non_camel_case_types)]
2085+
struct GetLimitsSvc<T: Api>(pub Arc<T>);
2086+
impl<T: Api> tonic::server::UnaryService<()> for GetLimitsSvc<T> {
2087+
type Response = super::RpcLimits;
2088+
type Future = BoxFuture<
2089+
tonic::Response<Self::Response>,
2090+
tonic::Status,
2091+
>;
2092+
fn call(&mut self, request: tonic::Request<()>) -> Self::Future {
2093+
let inner = Arc::clone(&self.0);
2094+
let fut = async move {
2095+
<T as Api>::get_limits(&inner, request).await
2096+
};
2097+
Box::pin(fut)
2098+
}
2099+
}
2100+
let accept_compression_encodings = self.accept_compression_encodings;
2101+
let send_compression_encodings = self.send_compression_encodings;
2102+
let max_decoding_message_size = self.max_decoding_message_size;
2103+
let max_encoding_message_size = self.max_encoding_message_size;
2104+
let inner = self.inner.clone();
2105+
let fut = async move {
2106+
let method = GetLimitsSvc(inner);
2107+
let codec = tonic_prost::ProstCodec::default();
2108+
let mut grpc = tonic::server::Grpc::new(codec)
2109+
.apply_compression_config(
2110+
accept_compression_encodings,
2111+
send_compression_encodings,
2112+
)
2113+
.apply_max_message_size_config(
2114+
max_decoding_message_size,
2115+
max_encoding_message_size,
2116+
);
2117+
let res = grpc.unary(method, req).await;
2118+
Ok(res)
2119+
};
2120+
Box::pin(fut)
2121+
}
20302122
_ => {
20312123
Box::pin(async move {
20322124
let mut response = http::Response::new(

crates/rpc/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ The full gRPC method definitions can be found in the [proto](../proto/README.md)
1919
- [GetAccountProofs](#getaccountproofs)
2020
- [GetBlockByNumber](#getblockbynumber)
2121
- [GetBlockHeaderByNumber](#getblockheaderbynumber)
22+
- [GetLimits](#getlimits)
2223
- [GetNotesById](#getnotesbyid)
2324
- [GetNoteScriptByRoot](#getnotescriptbyroot)
2425
- [SubmitProvenTransaction](#submitproventransaction)
@@ -36,6 +37,8 @@ The full gRPC method definitions can be found in the [proto](../proto/README.md)
3637

3738
Returns a nullifier proof for each of the requested nullifiers.
3839

40+
**Limits:** `nullifier` (1000)
41+
3942
#### Error Handling
4043

4144
When nullifier checking fails, detailed error information is provided through gRPC status details. The following error codes may be returned:
@@ -73,10 +76,21 @@ authenticate the block's inclusion.
7376

7477
---
7578

79+
### GetLimits
80+
81+
Returns the query parameter limits configured for RPC endpoints.
82+
83+
This endpoint allows clients to discover the maximum number of items that can be requested in a single call for
84+
various endpoints. The response contains a map of endpoint names to their parameter limits.
85+
86+
---
87+
7688
### GetNotesById
7789

7890
Returns a list of notes matching the provided note IDs.
7991

92+
**Limits:** `note_id` (100)
93+
8094
#### Error Handling
8195

8296
When note retrieval fails, detailed error information is provided through gRPC status details. The following error codes may be returned:
@@ -137,6 +151,8 @@ Clients should inspect both the gRPC status code and the detailed error code in
137151
Returns nullifier synchronization data for a set of prefixes within a given block range. This method allows
138152
clients to efficiently track nullifier creation by retrieving only the nullifiers produced between two blocks.
139153

154+
**Limits:** `nullifier` (1000)
155+
140156
Caller specifies the `prefix_len` (currently only 16), the list of prefix values (`nullifiers`), and the block
141157
range (`from_start_block`, optional `to_end_block`). The response includes all matching nullifiers created within that
142158
range, the last block included in the response (`block_num`), and the current chain tip (`chain_tip`).
@@ -181,6 +197,8 @@ When account vault synchronization fails, detailed error information is provided
181197

182198
Returns info which can be used by the client to sync up to the tip of chain for the notes they are interested in.
183199

200+
**Limits:** `note_tag` (1000)
201+
184202
Client specifies the `note_tags` they are interested in, and the block range from which to search for matching notes. The request will then return the next block containing any note matching the provided tags within the specified range.
185203

186204
The response includes each note's metadata and inclusion proof.
@@ -205,6 +223,8 @@ When note synchronization fails, detailed error information is provided through
205223
Returns info which can be used by the client to sync up to the latest state of the chain for the objects (accounts and
206224
notes) the client is interested in.
207225

226+
**Limits:** `account_id` (1000), `note_tag` (1000)
227+
208228
This request returns the next block containing requested data. It also returns `chain_tip` which is the latest block
209229
number in the chain. Client is expected to repeat these requests in a loop until
210230
`response.block_header.block_num == response.chain_tip`, at which point the client is fully synchronized with the chain.

crates/rpc/src/server/api.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::Arc;
1+
use std::sync::{Arc, LazyLock};
22
use std::time::Duration;
33

44
use anyhow::Context;
@@ -612,6 +612,23 @@ impl api_server::Api for RpcService {
612612

613613
self.store.clone().sync_transactions(request).await
614614
}
615+
616+
#[instrument(
617+
parent = None,
618+
target = COMPONENT,
619+
name = "rpc.server.get_limits",
620+
skip_all,
621+
ret(level = "debug"),
622+
err
623+
)]
624+
async fn get_limits(
625+
&self,
626+
request: Request<()>,
627+
) -> Result<Response<proto::rpc::RpcLimits>, Status> {
628+
debug!(target: COMPONENT, request = ?request);
629+
630+
Ok(Response::new(RPC_LIMITS.clone()))
631+
}
615632
}
616633

617634
// LIMIT HELPERS
@@ -627,3 +644,42 @@ fn out_of_range_error<E: core::fmt::Display>(err: E) -> Status {
627644
fn check<Q: QueryParamLimiter>(n: usize) -> Result<(), Status> {
628645
<Q as QueryParamLimiter>::check(n).map_err(out_of_range_error)
629646
}
647+
648+
/// Helper to build an [`EndpointLimits`](proto::rpc::EndpointLimits) from (name, limit) pairs.
649+
fn endpoint_limits(params: &[(&str, usize)]) -> proto::rpc::EndpointLimits {
650+
proto::rpc::EndpointLimits {
651+
parameters: params.iter().map(|(k, v)| ((*k).to_string(), *v as u32)).collect(),
652+
}
653+
}
654+
655+
/// Cached RPC query parameter limits.
656+
static RPC_LIMITS: LazyLock<proto::rpc::RpcLimits> = LazyLock::new(|| {
657+
use {
658+
QueryParamAccountIdLimit as AccountId,
659+
QueryParamNoteIdLimit as NoteId,
660+
QueryParamNoteTagLimit as NoteTag,
661+
QueryParamNullifierLimit as Nullifier,
662+
};
663+
664+
proto::rpc::RpcLimits {
665+
endpoints: std::collections::HashMap::from([
666+
(
667+
"CheckNullifiers".into(),
668+
endpoint_limits(&[(Nullifier::PARAM_NAME, Nullifier::LIMIT)]),
669+
),
670+
(
671+
"SyncNullifiers".into(),
672+
endpoint_limits(&[(Nullifier::PARAM_NAME, Nullifier::LIMIT)]),
673+
),
674+
(
675+
"SyncState".into(),
676+
endpoint_limits(&[
677+
(AccountId::PARAM_NAME, AccountId::LIMIT),
678+
(NoteTag::PARAM_NAME, NoteTag::LIMIT),
679+
]),
680+
),
681+
("SyncNotes".into(), endpoint_limits(&[(NoteTag::PARAM_NAME, NoteTag::LIMIT)])),
682+
("GetNotesById".into(), endpoint_limits(&[(NoteId::PARAM_NAME, NoteId::LIMIT)])),
683+
]),
684+
}
685+
});

crates/rpc/src/tests.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ use miden_node_proto::generated::{self as proto};
99
use miden_node_store::Store;
1010
use miden_node_store::genesis::config::GenesisConfig;
1111
use miden_node_utils::fee::test_fee;
12-
use miden_protocol::Word;
12+
use miden_node_utils::limiter::{
13+
QueryParamAccountIdLimit,
14+
QueryParamLimiter,
15+
QueryParamNoteIdLimit,
16+
QueryParamNoteTagLimit,
17+
QueryParamNullifierLimit,
18+
};
1319
use miden_protocol::account::delta::AccountUpdateDetails;
1420
use miden_protocol::account::{
1521
AccountBuilder,
@@ -461,3 +467,58 @@ async fn start_store(store_addr: SocketAddr) -> (Runtime, TempDir, Word) {
461467
genesis_state.into_block().unwrap().inner().header().commitment(),
462468
)
463469
}
470+
471+
#[tokio::test]
472+
async fn get_limits_endpoint() {
473+
// Start the RPC and store
474+
let (mut rpc_client, _rpc_addr, store_addr) = start_rpc().await;
475+
let (store_runtime, _data_directory, _genesis) = start_store(store_addr).await;
476+
477+
// Call the get_limits endpoint
478+
let response = rpc_client.get_limits(()).await.expect("get_limits should succeed");
479+
let limits = response.into_inner();
480+
481+
// Verify the response contains expected endpoints and limits
482+
assert!(!limits.endpoints.is_empty(), "endpoints should not be empty");
483+
484+
// Verify CheckNullifiers endpoint
485+
let check_nullifiers =
486+
limits.endpoints.get("CheckNullifiers").expect("CheckNullifiers should exist");
487+
488+
assert_eq!(
489+
check_nullifiers.parameters.get("nullifier"),
490+
Some(&(QueryParamNullifierLimit::LIMIT as u32)),
491+
"CheckNullifiers nullifier limit should be {}",
492+
QueryParamNullifierLimit::LIMIT
493+
);
494+
495+
// Verify SyncState endpoint has multiple parameters
496+
let sync_state = limits.endpoints.get("SyncState").expect("SyncState should exist");
497+
assert_eq!(
498+
sync_state.parameters.get(QueryParamAccountIdLimit::PARAM_NAME),
499+
Some(&(QueryParamAccountIdLimit::LIMIT as u32)),
500+
"SyncState {} limit should be {}",
501+
QueryParamAccountIdLimit::PARAM_NAME,
502+
QueryParamAccountIdLimit::LIMIT
503+
);
504+
assert_eq!(
505+
sync_state.parameters.get(QueryParamNoteTagLimit::PARAM_NAME),
506+
Some(&(QueryParamNoteTagLimit::LIMIT as u32)),
507+
"SyncState {} limit should be {}",
508+
QueryParamNoteTagLimit::PARAM_NAME,
509+
QueryParamNoteTagLimit::LIMIT
510+
);
511+
512+
// Verify GetNotesById endpoint
513+
let get_notes_by_id = limits.endpoints.get("GetNotesById").expect("GetNotesById should exist");
514+
assert_eq!(
515+
get_notes_by_id.parameters.get(QueryParamNoteIdLimit::PARAM_NAME),
516+
Some(&(QueryParamNoteIdLimit::LIMIT as u32)),
517+
"GetNotesById {} limit should be {}",
518+
QueryParamNoteIdLimit::PARAM_NAME,
519+
QueryParamNoteIdLimit::LIMIT
520+
);
521+
522+
// Shutdown to avoid runtime drop error.
523+
store_runtime.shutdown_background();
524+
}

0 commit comments

Comments
 (0)