Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/stress-test-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ jobs:
run: miden-node-stress-test benchmark-store --data-directory stress-test-store --iterations 10 --concurrency 1 sync-state
- name: Run sync notes benchmark
run: miden-node-stress-test benchmark-store --data-directory stress-test-store --iterations 10 --concurrency 1 sync-notes
- name: Run check nullifiers by prefix benchmark
run: miden-node-stress-test benchmark-store --data-directory stress-test-store --iterations 10 --concurrency 1 check-nullifiers-by-prefix --prefixes 10
- name: Run sync nullifiers benchmark
run: miden-node-stress-test benchmark-store --data-directory stress-test-store --iterations 10 --concurrency 1 sync-nullifiers --prefixes 10
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

- [BREAKING] Updated MSRV to 1.89.
- Added `GetNoteScriptByRoot` gRPC endpoint for retrieving a note script by its root ([#1196](https://github.com/0xMiden/miden-node/pull/1196)).
- [BREAKING] Refactored `CheckNullifiersByPrefix` endpoint adding pagination ([#1191](https://github.com/0xMiden/miden-node/pull/1191)).
- [BREAKING] Renamed `CheckNullifiersByPrefix` endpoint to `SyncNullifiers` ([#1191](https://github.com/0xMiden/miden-node/pull/1191)).

## v0.11.0 (2025-08-28)

Expand Down
8 changes: 4 additions & 4 deletions bin/stress-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ The endpoints that you can test are:
- `load_state`
- `sync_state`
- `sync_notes`
- `check_nullifiers_by_prefix`
- `sync_nullifiers`

Most benchmarks accept options to control the number of iterations and concurrency level. The `load_state` endpoint is different - it simply measures the one-time startup cost of loading the state from disk.

**Note on Concurrency**: For the endpoints that support it (`sync_state`, `sync_notes`, `check_nullifiers_by_prefix`), the concurrency parameter controls how many requests are sent in parallel to the store. Since these benchmarks run against a local store (no network overhead), higher concurrency values can help identify bottlenecks in the store's internal processing. The latency measurements exclude network time and represent pure store processing time.
**Note on Concurrency**: For the endpoints that support it (`sync_state`, `sync_notes`, `sync_nullifiers`), the concurrency parameter controls how many requests are sent in parallel to the store. Since these benchmarks run against a local store (no network overhead), higher concurrency values can help identify bottlenecks in the store's internal processing. The latency measurements exclude network time and represent pure store processing time.

Example usage:

Expand Down Expand Up @@ -141,9 +141,9 @@ P99 request latency: 1.528667ms
P99.9 request latency: 5.247875ms
```

- check-nullifiers-by-prefix
- sync-nullifiers
``` bash
$ miden-node-stress-test benchmark-store --data-directory ./data --iterations 10000 --concurrency 16 check-nullifiers-by-prefix --prefixes 10
$ miden-node-stress-test benchmark-store --data-directory ./data --iterations 10000 --concurrency 16 sync-nullifiers --prefixes 10

Average request latency: 519.239µs
P50 request latency: 503.708µs
Expand Down
9 changes: 4 additions & 5 deletions bin/stress-test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;
use clap::{Parser, Subcommand};
use miden_node_utils::logging::OpenTelemetry;
use seeding::seed_store;
use store::{bench_check_nullifiers_by_prefix, bench_sync_notes, bench_sync_state, load_state};
use store::{bench_sync_notes, bench_sync_nullifiers, bench_sync_state, load_state};

mod seeding;
mod store;
Expand Down Expand Up @@ -58,7 +58,7 @@ pub enum Command {

#[derive(Subcommand, Clone, Copy)]
pub enum Endpoint {
CheckNullifiersByPrefix {
SyncNullifiers {
/// Number of prefixes to send in each request.
#[arg(short, long, value_name = "PREFIXES", default_value = "10")]
prefixes: usize,
Expand Down Expand Up @@ -89,9 +89,8 @@ async fn main() {
iterations,
concurrency,
} => match endpoint {
Endpoint::CheckNullifiersByPrefix { prefixes } => {
bench_check_nullifiers_by_prefix(data_directory, iterations, concurrency, prefixes)
.await;
Endpoint::SyncNullifiers { prefixes } => {
bench_sync_nullifiers(data_directory, iterations, concurrency, prefixes).await;
},
Endpoint::SyncState => {
bench_sync_state(data_directory, iterations, concurrency).await;
Expand Down
23 changes: 12 additions & 11 deletions bin/stress-test/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const ACCOUNTS_PER_SYNC_STATE: usize = 5;
/// Number of accounts used in each `sync_notes` call.
const ACCOUNTS_PER_SYNC_NOTES: usize = 15;

/// Number of note IDs used in each `check_nullifiers_by_prefix` call.
/// Number of note IDs used in each `sync_nullifiers` call.
const NOTE_IDS_PER_NULLIFIERS_CHECK: usize = 20;

/// Number of attempts the benchmark will make to reach the store before proceeding.
Expand Down Expand Up @@ -165,18 +165,18 @@ pub async fn sync_notes(
start.elapsed()
}

// CHECK NULLIFIERS BY PREFIX
// SYNC NULLIFIERS
// ================================================================================================

/// Sends multiple `check_nullifiers_by_prefix` requests to the store and prints the performance.
/// Sends multiple `sync_nullifiers` requests to the store and prints the performance.
///
/// Arguments:
/// - `data_directory`: directory that contains the database dump file and the accounts ids dump
/// file.
/// - `iterations`: number of requests to send.
/// - `concurrency`: number of requests to send in parallel.
/// - `prefixes_per_request`: number of prefixes to send in each request.
pub async fn bench_check_nullifiers_by_prefix(
pub async fn bench_sync_nullifiers(
data_directory: PathBuf,
iterations: usize,
concurrency: usize,
Expand Down Expand Up @@ -252,7 +252,7 @@ pub async fn bench_check_nullifiers_by_prefix(

let nullifiers_batch: Vec<u32> = nullifiers.by_ref().take(prefixes_per_request).collect();

tokio::spawn(async move { check_nullifiers_by_prefix(&mut client, nullifiers_batch).await })
tokio::spawn(async move { sync_nullifiers(&mut client, nullifiers_batch).await })
};

// create a stream of tasks to send the requests
Expand All @@ -271,21 +271,22 @@ pub async fn bench_check_nullifiers_by_prefix(
println!("Average nullifiers per response: {average_nullifiers_per_response}");
}

/// Sends a single `check_nullifiers_by_prefix` request to the store and returns:
/// Sends a single `sync_nullifiers` request to the store and returns:
/// - the elapsed time.
/// - the response.
async fn check_nullifiers_by_prefix(
async fn sync_nullifiers(
api_client: &mut RpcClient<InterceptedService<Channel, OtelInterceptor>>,
nullifiers_prefixes: Vec<u32>,
) -> (Duration, proto::rpc_store::CheckNullifiersByPrefixResponse) {
let sync_request = proto::rpc_store::CheckNullifiersByPrefixRequest {
) -> (Duration, proto::rpc_store::SyncNullifiersResponse) {
let sync_request = proto::rpc_store::SyncNullifiersRequest {
nullifiers: nullifiers_prefixes,
prefix_len: 16,
block_num: 0,
block_from: 0,
block_to: None,
};

let start = Instant::now();
let response = api_client.check_nullifiers_by_prefix(sync_request).await.unwrap();
let response = api_client.sync_nullifiers(sync_request).await.unwrap();
(start.elapsed(), response.into_inner())
}

Expand Down
174 changes: 84 additions & 90 deletions crates/proto/src/generated/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,35 +150,6 @@ pub mod api_client {
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "CheckNullifiers"));
self.inner.unary(req, path, codec).await
}
/// Returns a list of nullifiers that match the specified prefixes and are recorded in the node.
///
/// Note that only 16-bit prefixes are supported at this time.
pub async fn check_nullifiers_by_prefix(
&mut self,
request: impl tonic::IntoRequest<
super::super::rpc_store::CheckNullifiersByPrefixRequest,
>,
) -> std::result::Result<
tonic::Response<super::super::rpc_store::CheckNullifiersByPrefixResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static(
"/rpc.Api/CheckNullifiersByPrefix",
);
let mut req = request.into_request();
req.extensions_mut()
.insert(GrpcMethod::new("rpc.Api", "CheckNullifiersByPrefix"));
self.inner.unary(req, path, codec).await
}
/// Returns the latest state of an account with the specified ID.
pub async fn get_account_details(
&mut self,
Expand Down Expand Up @@ -388,6 +359,32 @@ pub mod api_client {
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "SubmitProvenBatch"));
self.inner.unary(req, path, codec).await
}
/// Returns a list of nullifiers that match the specified prefixes and are recorded in the node.
///
/// Note that only 16-bit prefixes are supported at this time.
pub async fn sync_nullifiers(
&mut self,
request: impl tonic::IntoRequest<
super::super::rpc_store::SyncNullifiersRequest,
>,
) -> std::result::Result<
tonic::Response<super::super::rpc_store::SyncNullifiersResponse>,
tonic::Status,
> {
self.inner
.ready()
.await
.map_err(|e| {
tonic::Status::unknown(
format!("Service was not ready: {}", e.into()),
)
})?;
let codec = tonic::codec::ProstCodec::default();
let path = http::uri::PathAndQuery::from_static("/rpc.Api/SyncNullifiers");
let mut req = request.into_request();
req.extensions_mut().insert(GrpcMethod::new("rpc.Api", "SyncNullifiers"));
self.inner.unary(req, path, codec).await
}
/// Returns account vault updates for specified account within a block range.
pub async fn sync_account_vault(
&mut self,
Expand Down Expand Up @@ -530,18 +527,6 @@ pub mod api_server {
tonic::Response<super::super::rpc_store::CheckNullifiersResponse>,
tonic::Status,
>;
/// Returns a list of nullifiers that match the specified prefixes and are recorded in the node.
///
/// Note that only 16-bit prefixes are supported at this time.
async fn check_nullifiers_by_prefix(
&self,
request: tonic::Request<
super::super::rpc_store::CheckNullifiersByPrefixRequest,
>,
) -> std::result::Result<
tonic::Response<super::super::rpc_store::CheckNullifiersByPrefixResponse>,
tonic::Status,
>;
/// Returns the latest state of an account with the specified ID.
async fn get_account_details(
&self,
Expand Down Expand Up @@ -618,6 +603,16 @@ pub mod api_server {
tonic::Response<super::super::block_producer::SubmitProvenBatchResponse>,
tonic::Status,
>;
/// Returns a list of nullifiers that match the specified prefixes and are recorded in the node.
///
/// Note that only 16-bit prefixes are supported at this time.
async fn sync_nullifiers(
&self,
request: tonic::Request<super::super::rpc_store::SyncNullifiersRequest>,
) -> std::result::Result<
tonic::Response<super::super::rpc_store::SyncNullifiersResponse>,
tonic::Status,
>;
/// Returns account vault updates for specified account within a block range.
async fn sync_account_vault(
&self,
Expand Down Expand Up @@ -836,55 +831,6 @@ pub mod api_server {
};
Box::pin(fut)
}
"/rpc.Api/CheckNullifiersByPrefix" => {
#[allow(non_camel_case_types)]
struct CheckNullifiersByPrefixSvc<T: Api>(pub Arc<T>);
impl<
T: Api,
> tonic::server::UnaryService<
super::super::rpc_store::CheckNullifiersByPrefixRequest,
> for CheckNullifiersByPrefixSvc<T> {
type Response = super::super::rpc_store::CheckNullifiersByPrefixResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<
super::super::rpc_store::CheckNullifiersByPrefixRequest,
>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Api>::check_nullifiers_by_prefix(&inner, request)
.await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = CheckNullifiersByPrefixSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/rpc.Api/GetAccountDetails" => {
#[allow(non_camel_case_types)]
struct GetAccountDetailsSvc<T: Api>(pub Arc<T>);
Expand Down Expand Up @@ -1260,6 +1206,54 @@ pub mod api_server {
};
Box::pin(fut)
}
"/rpc.Api/SyncNullifiers" => {
#[allow(non_camel_case_types)]
struct SyncNullifiersSvc<T: Api>(pub Arc<T>);
impl<
T: Api,
> tonic::server::UnaryService<
super::super::rpc_store::SyncNullifiersRequest,
> for SyncNullifiersSvc<T> {
type Response = super::super::rpc_store::SyncNullifiersResponse;
type Future = BoxFuture<
tonic::Response<Self::Response>,
tonic::Status,
>;
fn call(
&mut self,
request: tonic::Request<
super::super::rpc_store::SyncNullifiersRequest,
>,
) -> Self::Future {
let inner = Arc::clone(&self.0);
let fut = async move {
<T as Api>::sync_nullifiers(&inner, request).await
};
Box::pin(fut)
}
}
let accept_compression_encodings = self.accept_compression_encodings;
let send_compression_encodings = self.send_compression_encodings;
let max_decoding_message_size = self.max_decoding_message_size;
let max_encoding_message_size = self.max_encoding_message_size;
let inner = self.inner.clone();
let fut = async move {
let method = SyncNullifiersSvc(inner);
let codec = tonic::codec::ProstCodec::default();
let mut grpc = tonic::server::Grpc::new(codec)
.apply_compression_config(
accept_compression_encodings,
send_compression_encodings,
)
.apply_max_message_size_config(
max_decoding_message_size,
max_encoding_message_size,
);
let res = grpc.unary(method, req).await;
Ok(res)
};
Box::pin(fut)
}
"/rpc.Api/SyncAccountVault" => {
#[allow(non_camel_case_types)]
struct SyncAccountVaultSvc<T: Api>(pub Arc<T>);
Expand Down
Loading