Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- [BREAKING] Renamed `--url` CLI flags and `*_URL` env vars to `--listen` / `*_LISTEN` across all components.
- [BREAKING] Removed `miden-node validator` subcommand and created a separate `miden-validator` binary ([#2053](https://github.com/0xMiden/node/pull/2053)).
- [BREAKING] Removed `miden-node ntx-builder` subcommand and created a separate `miden-ntx-builder` binary ([#2067](https://github.com/0xMiden/node/pull/2067)).
- [BREAKING] Changed `SyncChainMmr` endpoint: the upper end of the block range we're syncing is now the chain tip with the requested finality level. Validator signature is also returned ([#2075](https://github.com/0xMiden/node/pull/2075)).

## v0.14.10 (2026-05-29)

Expand Down
10 changes: 3 additions & 7 deletions bin/stress-test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,7 @@ pub enum Endpoint {
block_range: u32,
},
#[command(name = "sync-chain-mmr")]
SyncChainMmr {
/// Block range size for each request (number of blocks to query).
#[arg(short, long, value_name = "BLOCK_RANGE", default_value = "1000")]
block_range: u32,
},
SyncChainMmr,
#[command(name = "load-state")]
LoadState,
#[command(name = "get-account")]
Expand Down Expand Up @@ -158,8 +154,8 @@ async fn main() {
)
.await;
},
Endpoint::SyncChainMmr { block_range } => {
bench_sync_chain_mmr(data_directory, iterations, concurrency, block_range).await;
Endpoint::SyncChainMmr => {
bench_sync_chain_mmr(data_directory, iterations, concurrency).await;
},
Endpoint::LoadState => {
load_state(&data_directory).await;
Expand Down
17 changes: 5 additions & 12 deletions bin/stress-test/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,22 +612,16 @@ async fn sync_transactions_paginated(
/// - `iterations`: number of requests to send.
/// - `concurrency`: number of requests to send in parallel.
/// - `block_range_size`: number of blocks to include per request.
pub async fn bench_sync_chain_mmr(
data_directory: PathBuf,
iterations: usize,
concurrency: usize,
block_range_size: u32,
) {
pub async fn bench_sync_chain_mmr(data_directory: PathBuf, iterations: usize, concurrency: usize) {
let (store_client, _) = start_store(data_directory).await;

wait_for_store(&store_client).await.unwrap();

let chain_tip = store_client.clone().status(()).await.unwrap().into_inner().chain_tip;
let block_range_size = block_range_size.max(1);

let request = |_| {
let mut client = store_client.clone();
tokio::spawn(async move { sync_chain_mmr(&mut client, chain_tip, block_range_size).await })
tokio::spawn(async move { sync_chain_mmr(&mut client, chain_tip).await })
};

let results = stream::iter(0..iterations)
Expand All @@ -652,12 +646,11 @@ pub async fn bench_sync_chain_mmr(
/// - the response.
async fn sync_chain_mmr(
api_client: &mut RpcClient<InterceptedService<Channel, OtelInterceptor>>,
block_from: u32,
block_to: u32,
current_block_height: u32,
) -> SyncChainMmrRun {
let sync_request = proto::rpc::SyncChainMmrRequest {
block_from,
upper_bound: Some(proto::rpc::sync_chain_mmr_request::UpperBound::BlockNum(block_to)),
current_block_height,
finality_level: proto::rpc::FinalityLevel::Committed.into(),
};

let start = Instant::now();
Expand Down
36 changes: 0 additions & 36 deletions crates/proto/src/domain/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,42 +332,6 @@ impl From<&FeeParameters> for proto::blockchain::FeeParameters {
}
}

// SYNC TARGET
// ================================================================================================

/// The target block to sync up to in a chain MMR sync request.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncTarget {
/// Sync up to a specific block number (inclusive).
BlockNumber(BlockNumber),
/// Sync up to the latest committed block (chain tip).
CommittedChainTip,
/// Sync up to the latest proven block.
ProvenChainTip,
}

impl TryFrom<proto::rpc::sync_chain_mmr_request::UpperBound> for SyncTarget {
type Error = ConversionError;

fn try_from(
value: proto::rpc::sync_chain_mmr_request::UpperBound,
) -> Result<Self, Self::Error> {
use proto::rpc::sync_chain_mmr_request::UpperBound;

match value {
UpperBound::BlockNum(block_num) => Ok(Self::BlockNumber(block_num.into())),
UpperBound::ChainTip(tip) => match proto::rpc::ChainTip::try_from(tip) {
Ok(proto::rpc::ChainTip::Committed) => Ok(Self::CommittedChainTip),
Ok(proto::rpc::ChainTip::Proven) => Ok(Self::ProvenChainTip),
// These variants should never be encountered.
Ok(proto::rpc::ChainTip::Unspecified) | Err(_) => {
Err(ConversionError::message("unexpected chain tip"))
},
},
}
}
}

// BLOCK RANGE
// ================================================================================================

Expand Down
16 changes: 2 additions & 14 deletions crates/rpc/src/server/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,20 +297,8 @@ impl api_server::Api for RpcService {
let request_ref = request.get_ref();

let span = Span::current();
span.set_attribute("block_range.from", request_ref.block_from);
match request_ref.upper_bound {
Some(proto::rpc::sync_chain_mmr_request::UpperBound::BlockNum(block_num)) => {
span.set_attribute("block_range.to", block_num);
},
Some(proto::rpc::sync_chain_mmr_request::UpperBound::ChainTip(chain_tip)) => {
let chain_tip = proto::rpc::ChainTip::try_from(chain_tip)
.unwrap_or(proto::rpc::ChainTip::Unspecified);
span.set_attribute("sync.target", chain_tip.as_str_name());
},
None => {
span.set_attribute("sync.target", "CHAIN_TIP_COMMITTED");
},
}
span.set_attribute("current_block_height", request_ref.current_block_height);
span.set_attribute("finality_level", request_ref.finality_level().as_str_name());

debug!(target: COMPONENT, request = ?request_ref);

Expand Down
6 changes: 2 additions & 4 deletions crates/rpc/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,8 @@ async fn sync_chain_mmr_returns_delta() {
let (store_runtime, _data_directory, _genesis, _store_addr) = start_store(store_listener).await;

let request = proto::rpc::SyncChainMmrRequest {
block_from: 0,
upper_bound: Some(proto::rpc::sync_chain_mmr_request::UpperBound::ChainTip(
proto::rpc::ChainTip::Committed.into(),
)),
current_block_height: 0,
finality_level: proto::rpc::FinalityLevel::Committed.into(),
};
let response = rpc_client.sync_chain_mmr(request).await.expect("sync_chain_mmr should succeed");
let response = response.into_inner();
Expand Down
14 changes: 14 additions & 0 deletions crates/store/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::sync::Arc;

use anyhow::Context;
use diesel::{Connection, SqliteConnection};
use miden_crypto::dsa::ecdsa_k256_keccak::Signature;
use miden_node_proto::domain::account::AccountInfo;
use miden_node_proto::{BlockProofRequest, generated as proto};
use miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES;
Expand Down Expand Up @@ -350,6 +351,19 @@ impl Db {
.await
}

/// Search for a [`BlockHeader`] and its [`Signature`] from the database by its `block_num`.
#[instrument(level = "debug", target = COMPONENT, skip_all, ret(level = "debug"), err)]
pub async fn select_block_header_and_signature_by_block_num(
&self,
block_number: BlockNumber,
) -> Result<Option<(BlockHeader, Signature)>> {
self.transact("block headers and signature by block number", move |conn| {
let val = queries::select_block_header_and_signature_by_block_num(conn, block_number)?;
Ok(val)
})
.await
}

/// Loads multiple block headers from the DB.
#[instrument(level = "debug", target = COMPONENT, skip_all, ret(level = "debug"), err)]
pub async fn select_block_headers(
Expand Down
33 changes: 33 additions & 0 deletions crates/store/src/db/models/queries/block_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,38 @@ pub(crate) fn select_block_header_by_block_num(
row.map(std::convert::TryInto::try_into).transpose()
}

/// Select a [`BlockHeader`] and its [`Signature`] from the DB by its `block_num` using the given
/// [`SqliteConnection`].
///
/// # Returns
///
/// When `block_num` is [None], the latest block header and its signature is returned. Otherwise,
/// the block with the given block height is returned.
///
/// ```sql
/// -- with argument
/// SELECT block_num, block_header, signature
/// FROM block_headers
/// WHERE block_num = ?1
///
/// -- without argument
/// SELECT block_num, block_header, signature
/// FROM block_headers
/// ORDER BY block_num DESC
/// LIMIT 1
/// ```
pub(crate) fn select_block_header_and_signature_by_block_num(
conn: &mut SqliteConnection,
block_number: BlockNumber,
) -> Result<Option<(BlockHeader, Signature)>, DatabaseError> {
Comment on lines +74 to +89
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.

Are the two options still relevant? Seems like we always provide block_number now.

let sel = SelectDsl::select(schema::block_headers::table, BlockHeaderRawRow::as_select());
let row = sel
.filter(schema::block_headers::block_num.eq(block_number.to_raw_sql()))
.get_result::<BlockHeaderRawRow>(conn)
.optional()?;
row.map(std::convert::TryInto::try_into).transpose()
}

/// Select block headers for the given block numbers.
///
/// # Parameters
Expand Down Expand Up @@ -175,6 +207,7 @@ pub struct BlockHeaderRawRow {
pub signature: Vec<u8>,
pub commitment: Vec<u8>,
}

impl TryInto<BlockHeader> for BlockHeaderRawRow {
type Error = DatabaseError;
fn try_into(self) -> Result<BlockHeader, Self::Error> {
Expand Down
2 changes: 1 addition & 1 deletion crates/store/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ pub enum SyncChainMmrError {
#[error("start block is not known")]
FutureBlock {
chain_tip: BlockNumber,
block_from: BlockNumber,
current_block_height: BlockNumber,
},
#[error("malformed block number")]
DeserializationFailed(#[source] ConversionError),
Expand Down
33 changes: 14 additions & 19 deletions crates/store/src/server/rpc_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use miden_node_proto::decode::{
read_block_range,
read_root,
};
use miden_node_proto::domain::block::SyncTarget;
use miden_node_proto::generated::store::rpc_server;
use miden_node_proto::generated::{self as proto};
use miden_node_utils::limiter::{
Expand Down Expand Up @@ -145,29 +144,24 @@ impl rpc_server::Rpc for StoreApi {
) -> Result<Response<proto::rpc::SyncChainMmrResponse>, Status> {
let request = request.into_inner();

let block_from = BlockNumber::from(request.block_from);
let current_block_height = BlockNumber::from(request.current_block_height);

// Determine upper bound to sync to or default to last committed block.
let sync_target = request
.upper_bound
.map(SyncTarget::try_from)
.transpose()
.map_err(SyncChainMmrError::DeserializationFailed)?
.unwrap_or(SyncTarget::CommittedChainTip);

let block_to = match sync_target {
SyncTarget::BlockNumber(block_num) => {
block_num.min(self.state.chain_tip(Finality::Committed).await)
// Determine finality level of the tip to sync to or default to the committed tip.
let sync_target = match request.finality_level() {
proto::rpc::FinalityLevel::Committed | proto::rpc::FinalityLevel::Unspecified => {
self.state.chain_tip(Finality::Committed).await
},
SyncTarget::CommittedChainTip => self.state.chain_tip(Finality::Committed).await,
SyncTarget::ProvenChainTip => self.state.chain_tip(Finality::Proven).await,
proto::rpc::FinalityLevel::Proven => self.state.chain_tip(Finality::Proven).await,
};

if block_from > block_to {
Err(SyncChainMmrError::FutureBlock { chain_tip: block_to, block_from })?;
if current_block_height > sync_target {
Comment thread
Mirko-von-Leipzig marked this conversation as resolved.
Err(SyncChainMmrError::FutureBlock {
chain_tip: sync_target,
current_block_height,
})?;
}
let block_range = block_from..=block_to;
let (mmr_delta, block_header) =
let block_range = current_block_height..=sync_target;
let (mmr_delta, block_header, block_signature) =
self.state.sync_chain_mmr(block_range.clone()).await.map_err(internal_error)?;

Ok(Response::new(proto::rpc::SyncChainMmrResponse {
Expand All @@ -177,6 +171,7 @@ impl rpc_server::Rpc for StoreApi {
}),
mmr_delta: Some(mmr_delta.into()),
block_header: Some(block_header.into()),
block_signature: Some(block_signature.into()),
}))
}

Expand Down
10 changes: 6 additions & 4 deletions crates/store/src/state/sync_state.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::ops::RangeInclusive;

use miden_crypto::dsa::ecdsa_k256_keccak::Signature;
use miden_protocol::account::AccountId;
use miden_protocol::block::{BlockHeader, BlockNumber};
use miden_protocol::crypto::merkle::mmr::{Forest, MmrDelta, MmrProof};
Expand Down Expand Up @@ -30,15 +31,15 @@ impl State {
pub async fn sync_chain_mmr(
&self,
block_range: RangeInclusive<BlockNumber>,
) -> Result<(MmrDelta, BlockHeader), StateSyncError> {
) -> Result<(MmrDelta, BlockHeader, Signature), StateSyncError> {
let block_from = *block_range.start();
let block_to = *block_range.end();

// SAFETY: block_to has been validated to be <= the effective tip (chain tip or latest
// proven block) by the caller, so it must exist in the database.
let block_header = self
let (block_header, signature) = self
.db
.select_block_header_by_block_num(Some(block_to))
.select_block_header_and_signature_by_block_num(block_to)
.await?
.expect("block_to should exist in the database");

Expand All @@ -49,6 +50,7 @@ impl State {
data: vec![],
},
block_header,
signature,
));
}

Expand All @@ -73,7 +75,7 @@ impl State {
.get_delta(Forest::new(from_forest), Forest::new(to_forest))
.map_err(StateSyncError::FailedToBuildMmrDelta)?;

Ok((mmr_delta, block_header))
Ok((mmr_delta, block_header, signature))
}

/// Loads data to synchronize a client's notes.
Expand Down
34 changes: 16 additions & 18 deletions proto/proto/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -469,41 +469,39 @@ message SyncNotesResponse {
// SYNC CHAIN MMR
// ================================================================================================

// The chain tip variant to sync up to.
enum ChainTip {
CHAIN_TIP_UNSPECIFIED = 0;
// Finality level we'd like to sync up to.
enum FinalityLevel {
FINALITY_LEVEL_UNSPECIFIED = 0;
// Sync up to the latest committed block (chain tip).
CHAIN_TIP_COMMITTED = 1;
FINALITY_LEVEL_COMMITTED = 1;
// Sync up to the latest proven block.
CHAIN_TIP_PROVEN = 2;
FINALITY_LEVEL_PROVEN = 2;
}

// Chain MMR synchronization request.
message SyncChainMmrRequest {
// Block number from which to synchronize (inclusive). Set this to the last block
// already present in the caller's MMR so the delta begins at the next block.
fixed32 block_from = 1;

// Upper bound for the block range. Determines how far ahead to sync.
oneof upper_bound {
// Sync up to this specific block number (inclusive), clamped to the committed chain tip.
fixed32 block_num = 2;
// Sync up to a chain tip variant (committed or proven).
ChainTip chain_tip = 3;
}
fixed32 current_block_height = 1;

reserved 4;
// Sync target: either the committed or the proven tip.
FinalityLevel finality_level = 2;
}

// Represents the result of syncing chain MMR.
message SyncChainMmrResponse {
// For which block range the MMR delta is returned.
BlockRange block_range = 1;
// Data needed to update the partial MMR from `request.block_range.block_from + 1` to
// `response.block_range.block_to` or the chain tip.

// Data needed to update the partial MMR from `request.block_range.current_block_height + 1` to
// the sync target.
primitives.MmrDelta mmr_delta = 2;
// Block header for `block_range.block_to`.

// Block header for the sync target.
blockchain.BlockHeader block_header = 3;

// Validator signature for the sync target.
blockchain.BlockSignature block_signature = 4;
}

// SYNC ACCOUNT STORAGE MAP
Expand Down
Loading