From 52c8265305f39779bd1d888c0036de70ab91429a Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Wed, 1 Apr 2026 15:48:22 +0200 Subject: [PATCH 1/4] chore: increment crate versions to v0.15.0 --- CHANGELOG.md | 2 ++ Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 30 +++++++++++++++--------------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffd710df..d4bf3a408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## v0.15.0 (TBD) + ## v0.14.0 (2025-04-01) ### Enhancements diff --git a/Cargo.lock b/Cargo.lock index 007597ce7..3de88021e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3035,7 +3035,7 @@ dependencies = [ [[package]] name = "miden-genesis" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "clap", @@ -3054,7 +3054,7 @@ dependencies = [ [[package]] name = "miden-large-smt-backend-rocksdb" -version = "0.14.0" +version = "0.15.0" dependencies = [ "miden-crypto", "miden-node-rocksdb-cxx-linkage-fix", @@ -3114,7 +3114,7 @@ dependencies = [ [[package]] name = "miden-network-monitor" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "axum", @@ -3142,7 +3142,7 @@ dependencies = [ [[package]] name = "miden-node" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "clap", @@ -3162,7 +3162,7 @@ dependencies = [ [[package]] name = "miden-node-block-producer" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -3197,7 +3197,7 @@ dependencies = [ [[package]] name = "miden-node-db" -version = "0.14.0" +version = "0.15.0" dependencies = [ "deadpool", "deadpool-diesel", @@ -3210,7 +3210,7 @@ dependencies = [ [[package]] name = "miden-node-grpc-error-macro" -version = "0.14.0" +version = "0.15.0" dependencies = [ "quote", "syn 2.0.117", @@ -3218,7 +3218,7 @@ dependencies = [ [[package]] name = "miden-node-ntx-builder" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "build-rs", @@ -3251,7 +3251,7 @@ dependencies = [ [[package]] name = "miden-node-proto" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -3276,7 +3276,7 @@ dependencies = [ [[package]] name = "miden-node-proto-build" -version = "0.14.0" +version = "0.15.0" dependencies = [ "build-rs", "fs-err", @@ -3287,11 +3287,11 @@ dependencies = [ [[package]] name = "miden-node-rocksdb-cxx-linkage-fix" -version = "0.14.0" +version = "0.15.0" [[package]] name = "miden-node-rpc" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "futures", @@ -3323,7 +3323,7 @@ dependencies = [ [[package]] name = "miden-node-store" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -3369,7 +3369,7 @@ dependencies = [ [[package]] name = "miden-node-stress-test" -version = "0.14.0" +version = "0.15.0" dependencies = [ "clap", "fs-err", @@ -3397,7 +3397,7 @@ dependencies = [ [[package]] name = "miden-node-utils" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "bytes", @@ -3430,7 +3430,7 @@ dependencies = [ [[package]] name = "miden-node-validator" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "aws-config", @@ -3535,7 +3535,7 @@ dependencies = [ [[package]] name = "miden-remote-prover" -version = "0.14.0" +version = "0.15.0" dependencies = [ "anyhow", "assert_matches", @@ -3572,7 +3572,7 @@ dependencies = [ [[package]] name = "miden-remote-prover-client" -version = "0.14.0" +version = "0.15.0" dependencies = [ "build-rs", "fs-err", diff --git a/Cargo.toml b/Cargo.toml index 1079a7f9c..7bc7d1ede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ license = "MIT" readme = "README.md" repository = "https://github.com/0xMiden/node" rust-version = "1.93" -version = "0.14.0" +version = "0.15.0" # Optimize the cryptography for faster tests involving account creation. [profile.test.package.miden-crypto] @@ -43,25 +43,25 @@ debug = true [workspace.dependencies] # Workspace crates. -miden-agglayer = { version = "0.14" } -miden-large-smt-backend-rocksdb = { path = "crates/large-smt-backend-rocksdb", version = "0.14" } -miden-node-block-producer = { path = "crates/block-producer", version = "0.14" } -miden-node-db = { path = "crates/db", version = "0.14" } -miden-node-grpc-error-macro = { path = "crates/grpc-error-macro", version = "0.14" } -miden-node-ntx-builder = { path = "crates/ntx-builder", version = "0.14" } -miden-node-proto = { path = "crates/proto", version = "0.14" } -miden-node-proto-build = { path = "proto", version = "0.14" } -miden-node-rpc = { path = "crates/rpc", version = "0.14" } -miden-node-store = { path = "crates/store", version = "0.14" } +miden-large-smt-backend-rocksdb = { path = "crates/large-smt-backend-rocksdb", version = "0.15" } +miden-node-block-producer = { path = "crates/block-producer", version = "0.15" } +miden-node-db = { path = "crates/db", version = "0.15" } +miden-node-grpc-error-macro = { path = "crates/grpc-error-macro", version = "0.15" } +miden-node-ntx-builder = { path = "crates/ntx-builder", version = "0.15" } +miden-node-proto = { path = "crates/proto", version = "0.15" } +miden-node-proto-build = { path = "proto", version = "0.15" } +miden-node-rpc = { path = "crates/rpc", version = "0.15" } +miden-node-store = { path = "crates/store", version = "0.15" } miden-node-test-macro = { path = "crates/test-macro" } -miden-node-utils = { path = "crates/utils", version = "0.14" } -miden-node-validator = { path = "crates/validator", version = "0.14" } -miden-remote-prover-client = { path = "crates/remote-prover-client", version = "0.14" } +miden-node-utils = { path = "crates/utils", version = "0.15" } +miden-node-validator = { path = "crates/validator", version = "0.15" } +miden-remote-prover-client = { path = "crates/remote-prover-client", version = "0.15" } # Temporary workaround until # is part of `rocksdb-rust` release -miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "0.14" } +miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "0.15" } # miden-protocol dependencies. These should be updated in sync. +miden-agglayer = { version = "0.14" } miden-block-prover = { version = "0.14" } miden-protocol = { default-features = false, version = "0.14" } miden-standards = { version = "0.14" } From a893a97c1d96c1ba02df5b02a625d03f47cb9a81 Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:29:47 +1300 Subject: [PATCH 2/4] fix: Update SyncChainMmr endpoint upper bound logic (#1860) --- bin/stress-test/src/store/mod.rs | 4 +-- crates/proto/src/domain/block.rs | 36 +++++++++++++++++++++++++++ crates/rpc/src/tests.rs | 6 +++-- crates/store/src/server/rpc_api.rs | 39 ++++++++++++------------------ proto/proto/rpc.proto | 38 ++++++++++++++--------------- 5 files changed, 76 insertions(+), 47 deletions(-) diff --git a/bin/stress-test/src/store/mod.rs b/bin/stress-test/src/store/mod.rs index 7c68b025e..cedd3e23a 100644 --- a/bin/stress-test/src/store/mod.rs +++ b/bin/stress-test/src/store/mod.rs @@ -469,8 +469,8 @@ async fn sync_chain_mmr( block_to: u32, ) -> SyncChainMmrRun { let sync_request = proto::rpc::SyncChainMmrRequest { - block_range: Some(proto::rpc::BlockRange { block_from, block_to: Some(block_to) }), - finality: proto::rpc::Finality::Committed.into(), + block_from, + upper_bound: Some(proto::rpc::sync_chain_mmr_request::UpperBound::BlockNum(block_to)), }; let start = Instant::now(); diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 7f2646db0..19a4bf8bf 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -332,6 +332,42 @@ 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 for SyncTarget { + type Error = ConversionError; + + fn try_from( + value: proto::rpc::sync_chain_mmr_request::UpperBound, + ) -> Result { + 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 // ================================================================================================ diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index 8ed9524f8..c1c81994c 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -607,8 +607,10 @@ 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_range: Some(proto::rpc::BlockRange { block_from: 0, block_to: None }), - finality: proto::rpc::Finality::Committed.into(), + block_from: 0, + upper_bound: Some(proto::rpc::sync_chain_mmr_request::UpperBound::ChainTip( + proto::rpc::ChainTip::Committed.into(), + )), }; let response = rpc_client.sync_chain_mmr(request).await.expect("sync_chain_mmr should succeed"); let response = response.into_inner(); diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 38dd9f9f4..58d0c55d8 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -1,6 +1,5 @@ use miden_node_proto::convert; -use miden_node_proto::domain::block::InvalidBlockRange; -use miden_node_proto::errors::ConversionError; +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::{ @@ -169,17 +168,20 @@ impl rpc_server::Rpc for StoreApi { let request = request.into_inner(); let chain_tip = self.state.latest_block_num().await; - let block_range = request - .block_range - .ok_or_else(|| { - ConversionError::missing_field::("block_range") - }) - .map_err(SyncChainMmrError::DeserializationFailed)?; + let block_from = BlockNumber::from(request.block_from); + + // 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); - // Determine the effective tip based on the requested finality level. - let effective_tip = match request.finality() { - proto::rpc::Finality::Unspecified | proto::rpc::Finality::Committed => chain_tip, - proto::rpc::Finality::Proven => self + let block_to = match sync_target { + SyncTarget::BlockNumber(block_num) => block_num.min(chain_tip), + SyncTarget::CommittedChainTip => chain_tip, + SyncTarget::ProvenChainTip => self .state .db() .select_latest_proven_in_sequence_block_num() @@ -187,19 +189,8 @@ impl rpc_server::Rpc for StoreApi { .map_err(SyncChainMmrError::DatabaseError)?, }; - let block_from = BlockNumber::from(block_range.block_from); - if block_from > effective_tip { - Err(SyncChainMmrError::FutureBlock { chain_tip: effective_tip, block_from })?; - } - - let block_to = - block_range.block_to.map_or(effective_tip, BlockNumber::from).min(effective_tip); - if block_from > block_to { - Err(SyncChainMmrError::InvalidBlockRange(InvalidBlockRange::StartGreaterThanEnd { - start: block_from, - end: block_to, - }))?; + Err(SyncChainMmrError::FutureBlock { chain_tip: block_to, block_from })?; } let block_range = block_from..=block_to; let mmr_delta = diff --git a/proto/proto/rpc.proto b/proto/proto/rpc.proto index 0f697f433..36d1d134b 100644 --- a/proto/proto/rpc.proto +++ b/proto/proto/rpc.proto @@ -494,30 +494,30 @@ message SyncNotesResponse { // SYNC CHAIN MMR // ================================================================================================ -// The finality level for chain data queries. -enum Finality { - // Return data up to the latest committed block. - FINALITY_UNSPECIFIED = 0; - // Return data up to the latest committed block. - FINALITY_COMMITTED = 1; - // Return data only up to the latest proven block. - FINALITY_PROVEN = 2; +// The chain tip variant to sync up to. +enum ChainTip { + CHAIN_TIP_UNSPECIFIED = 0; + // Sync up to the latest committed block (chain tip). + CHAIN_TIP_COMMITTED = 1; + // Sync up to the latest proven block. + CHAIN_TIP_PROVEN = 2; } // Chain MMR synchronization request. message SyncChainMmrRequest { - // Block range from which to synchronize the chain MMR. - // - // The response will contain MMR delta starting after `block_range.block_from` up to - // `block_range.block_to` or the effective tip (whichever is lower). Set `block_from` to the - // last block already present in the caller's MMR so the delta begins at the next block. - BlockRange block_range = 1; + // 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; - // The finality level to use when clamping the upper bound of the block range. - // - // When set to `FINALITY_UNSPECIFIED` or `FINALITY_COMMITTED`, the upper bound is clamped to the chain tip. - // When set to `FINALITY_PROVEN`, the upper bound is clamped to the latest proven block. - Finality finality = 2; + // 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; + } + + reserved 4; } // Represents the result of syncing chain MMR. From 9ea3a63a95f33b5591959bbc1748ccb6bea13a62 Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:30:13 +1300 Subject: [PATCH 3/4] fix: Genesis(SignedBlock) (#1858) --- bin/node/src/commands/store.rs | 6 +++--- bin/stress-test/src/seeding/mod.rs | 7 +++---- crates/store/src/db/mod.rs | 5 ++--- crates/store/src/genesis/mod.rs | 23 +++++++++-------------- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/bin/node/src/commands/store.rs b/bin/node/src/commands/store.rs index 7fb62278d..d3ea6038f 100644 --- a/bin/node/src/commands/store.rs +++ b/bin/node/src/commands/store.rs @@ -7,7 +7,7 @@ use miden_node_store::{DEFAULT_MAX_CONCURRENT_PROOFS, Store}; use miden_node_utils::clap::{GrpcOptionsInternal, StorageOptions}; use miden_node_utils::fs::ensure_empty_directory; use miden_node_utils::grpc::UrlExt; -use miden_protocol::block::ProvenBlock; +use miden_protocol::block::SignedBlock; use miden_protocol::utils::serde::Deserializable; use url::Url; @@ -175,10 +175,10 @@ impl StoreCommand { pub fn bootstrap_store(data_directory: &Path, genesis_block_path: &Path) -> anyhow::Result<()> { // Read and deserialize the genesis block file. let bytes = fs_err::read(genesis_block_path).context("failed to read genesis block")?; - let proven_block = ProvenBlock::read_from_bytes(&bytes) + let signed_block = SignedBlock::read_from_bytes(&bytes) .context("failed to deserialize genesis block from file")?; let genesis_block = - GenesisBlock::try_from(proven_block).context("genesis block validation failed")?; + GenesisBlock::try_from(signed_block).context("genesis block validation failed")?; Store::bootstrap(genesis_block, data_directory) } diff --git a/bin/stress-test/src/seeding/mod.rs b/bin/stress-test/src/seeding/mod.rs index 0b860838c..79b461848 100644 --- a/bin/stress-test/src/seeding/mod.rs +++ b/bin/stress-test/src/seeding/mod.rs @@ -28,7 +28,6 @@ use miden_protocol::block::{ BlockNumber, FeeParameters, ProposedBlock, - ProvenBlock, SignedBlock, }; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey; @@ -111,12 +110,12 @@ pub async fn seed_store( let accounts_filepath = data_directory.join(ACCOUNTS_FILENAME); let data_directory = miden_node_store::DataDirectory::load(data_directory).expect("data directory should exist"); - let genesis_header = genesis_state.into_block().await.unwrap().into_inner(); + let genesis_block = genesis_state.into_block().await.unwrap().into_inner(); let metrics = generate_blocks( num_accounts, public_accounts_percentage, faucet, - genesis_header, + genesis_block, &store_client, data_directory, accounts_filepath, @@ -137,7 +136,7 @@ async fn generate_blocks( num_accounts: usize, public_accounts_percentage: u8, mut faucet: Account, - genesis_block: ProvenBlock, + genesis_block: SignedBlock, store_client: &StoreClient, data_directory: DataDirectory, accounts_filepath: PathBuf, diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index bda0cb8d6..460d68d5a 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -261,9 +261,8 @@ impl Db { // Run migrations. apply_migrations(&mut conn).context("failed to apply database migrations")?; - // Insert genesis block data. Deconstruct into signed block. - let (header, body, signature, _proof) = genesis.into_inner().into_parts(); - let genesis_block = SignedBlock::new_unchecked(header, body, signature); + // Insert genesis block data. + let genesis_block = genesis.into_inner(); conn.transaction(move |conn| models::queries::apply_block(conn, &genesis_block, &[], None)) .context("failed to insert genesis block")?; Ok(()) diff --git a/crates/store/src/genesis/mod.rs b/crates/store/src/genesis/mod.rs index 6c4624fb0..b875aa5f3 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -9,9 +9,8 @@ use miden_protocol::block::{ BlockHeader, BlockNoteTree, BlockNumber, - BlockProof, FeeParameters, - ProvenBlock, + SignedBlock, }; use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks}; use miden_protocol::crypto::merkle::smt::{LargeSmt, MemoryStorage, Smt}; @@ -35,23 +34,23 @@ pub struct GenesisState { } /// A type-safety wrapper ensuring that genesis block data can only be created from -/// [`GenesisState`] or validated from a [`ProvenBlock`] via [`GenesisBlock::try_from`]. -pub struct GenesisBlock(ProvenBlock); +/// [`GenesisState`] or validated from a [`SignedBlock`] via [`GenesisBlock::try_from`]. +pub struct GenesisBlock(SignedBlock); impl GenesisBlock { - pub fn inner(&self) -> &ProvenBlock { + pub fn inner(&self) -> &SignedBlock { &self.0 } - pub fn into_inner(self) -> ProvenBlock { + pub fn into_inner(self) -> SignedBlock { self.0 } } -impl TryFrom for GenesisBlock { +impl TryFrom for GenesisBlock { type Error = anyhow::Error; - fn try_from(block: ProvenBlock) -> anyhow::Result { + fn try_from(block: SignedBlock) -> anyhow::Result { anyhow::ensure!( block.header().block_num() == BlockNumber::GENESIS, "expected genesis block number (0), got {}", @@ -152,15 +151,11 @@ impl GenesisState { empty_transactions, ); - let block_proof = BlockProof::new_dummy(); - // Sign and assert verification for sanity (no mismatch between frontend and backend signing // impls). let signature = self.block_signer.sign(&header).await?; assert!(signature.verify(header.commitment(), &self.block_signer.public_key())); - // SAFETY: Header and accounts should be valid by construction. - // No notes or nullifiers are created at genesis, which is consistent with the above empty - // block note tree root and empty nullifier tree root. - Ok(GenesisBlock(ProvenBlock::new_unchecked(header, body, signature, block_proof))) + let signed_block = SignedBlock::new(header, body, signature)?; + Ok(GenesisBlock(signed_block)) } } From 62e76b831bbee68c1039818f41f0431cf1c3607b Mon Sep 17 00:00:00 2001 From: Serge Radinovich <47865535+sergerad@users.noreply.github.com> Date: Fri, 3 Apr 2026 08:30:41 +1300 Subject: [PATCH 4/4] feat: Add proof to GetBlockByNumber (#1864) --- CHANGELOG.md | 2 ++ crates/rpc/src/server/api.rs | 2 +- crates/store/src/blocks.rs | 17 +++++++++++++++++ crates/store/src/server/rpc_api.rs | 19 ++++++++++++------- crates/store/src/state/mod.rs | 11 +++++++++++ proto/proto/internal/store.proto | 4 ++-- proto/proto/rpc.proto | 4 ++-- proto/proto/types/blockchain.proto | 18 ++++++++++++++++-- 8 files changed, 63 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66405a853..70e380906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## v0.15.0 (TBD) +- [BREAKING] Changed `GetBlockByNumber` to accept a `BlockRequest` (with optional `include_proof` flag) and returns a response containing the block and an optional block proof ([#1864](https://github.com/0xMiden/node/pull/1864)). + ## v0.14.1 (2025-04-02) - Fixed batch building issue with unauthenticated notes consumed in the same batch as they were created ([#1875](https://github.com/0xMiden/node/issues/1875)). diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index be5af9c18..80298af9e 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -234,7 +234,7 @@ impl api_server::Api for RpcService { async fn get_block_by_number( &self, - request: Request, + request: Request, ) -> Result, Status> { let request = request.into_inner(); diff --git a/crates/store/src/blocks.rs b/crates/store/src/blocks.rs index ad34ff0dd..5ea612718 100644 --- a/crates/store/src/blocks.rs +++ b/crates/store/src/blocks.rs @@ -1,3 +1,12 @@ +//! File-based storage for raw block data and block proofs. +//! +//! Block data is stored under `{store_dir}/{epoch:04x}/block_{block_num:08x}.dat`, and proof data +//! for proven blocks is stored under `{store_dir}/{epoch:04x}/proof_{block_num:08x}.dat`. +//! +//! The epoch is derived from the 16 most significant bits of the block number (i.e., +//! `block_num >> 16`), and both the epoch and block number are formatted as zero-padded +//! hexadecimal strings. + use std::io::ErrorKind; use std::ops::Not; use std::path::PathBuf; @@ -115,6 +124,14 @@ impl BlockStore { tokio::fs::write(proof_path, data).await } + pub async fn load_proof(&self, block_num: BlockNumber) -> std::io::Result>> { + match tokio::fs::read(self.proof_path(block_num)).await { + Ok(data) => Ok(Some(data)), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err), + } + } + // HELPER FUNCTIONS // -------------------------------------------------------------------------------------------- diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 58d0c55d8..18aba9949 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -238,19 +238,24 @@ impl rpc_server::Rpc for StoreApi { async fn get_block_by_number( &self, - request: Request, + request: Request, ) -> Result, Status> { let request = request.into_inner(); debug!(target: COMPONENT, ?request); - let block = self - .state - .load_block(request.block_num.into()) - .await - .map_err(GetBlockByNumberError::from)?; + // Load block from state. + let block_num = BlockNumber::from(request.block_num); + let block = self.state.load_block(block_num).await.map_err(GetBlockByNumberError::from)?; + + // Load proof from state. + let proof = if request.include_proof.unwrap_or_default() { + self.state.load_proof(block_num).await.map_err(GetBlockByNumberError::from)? + } else { + None + }; - Ok(Response::new(proto::blockchain::MaybeBlock { block })) + Ok(Response::new(proto::blockchain::MaybeBlock { block, proof })) } async fn get_account( diff --git a/crates/store/src/state/mod.rs b/crates/store/src/state/mod.rs index 418c890de..91b1706d0 100644 --- a/crates/store/src/state/mod.rs +++ b/crates/store/src/state/mod.rs @@ -842,6 +842,17 @@ impl State { self.block_store.load_block(block_num).await.map_err(Into::into) } + /// Loads a block proof from the block store. Returns `Ok(None)` if the proof is not found. + pub async fn load_proof( + &self, + block_num: BlockNumber, + ) -> Result>, DatabaseError> { + if block_num > self.latest_block_num().await { + return Ok(None); + } + self.block_store.load_proof(block_num).await.map_err(Into::into) + } + /// Returns the latest block number. pub async fn latest_block_num(&self) -> BlockNumber { self.inner.read().await.latest_block_num() diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 7de72ef0d..d510d4666 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -34,8 +34,8 @@ service Rpc { // Returns the latest details the specified account. rpc GetAccount(rpc.AccountRequest) returns (rpc.AccountResponse) {} - // Returns raw block data for the specified block number. - rpc GetBlockByNumber(blockchain.BlockNumber) returns (blockchain.MaybeBlock) {} + // Returns raw block data for the specified block number, optionally including the block proof. + rpc GetBlockByNumber(blockchain.BlockRequest) returns (blockchain.MaybeBlock) {} // Retrieves block header by given block number. Optionally, it also returns the MMR path // and current chain length to authenticate the block's inclusion. diff --git a/proto/proto/rpc.proto b/proto/proto/rpc.proto index 36d1d134b..8cc14a64b 100644 --- a/proto/proto/rpc.proto +++ b/proto/proto/rpc.proto @@ -40,8 +40,8 @@ service Api { // Returns the latest details of the specified account. rpc GetAccount(AccountRequest) returns (AccountResponse) {} - // Returns raw block data for the specified block number. - rpc GetBlockByNumber(blockchain.BlockNumber) returns (blockchain.MaybeBlock) {} + // Returns raw block data for the specified block number, optionally including the block proof. + rpc GetBlockByNumber(blockchain.BlockRequest) returns (blockchain.MaybeBlock) {} // Retrieves block header by given block number. Optionally, it also returns the MMR path // and current chain length to authenticate the block's inclusion. diff --git a/proto/proto/types/blockchain.proto b/proto/proto/types/blockchain.proto index e87a3648d..e86525876 100644 --- a/proto/proto/types/blockchain.proto +++ b/proto/proto/types/blockchain.proto @@ -21,11 +21,25 @@ message ProposedBlock { bytes proposed_block = 1; } -// Represents a block or nothing. +// Request for retrieving a block by its number, optionally including the block proof. +message BlockRequest { + // The block number of the target block. + fixed32 block_num = 1; + // Whether to include the block proof in the response. + optional bool include_proof = 2; +} + +// Response containing the block data and optionally its proof. +// +// Contains empty values for both blocks and proofs that are not found. Some blocks may not yet be +// proven so it is possible to retrieve a block without a proof even if the proof has been requested. message MaybeBlock { // The requested block data encoded using [miden_serde_utils::Serializable] implementation for - // [miden_protocol::block::Block]. + // [miden_protocol::block::SignedBlock]. optional bytes block = 1; + // The block proof encoded using [miden_serde_utils::Serializable] implementation for + // [miden_protocol::block::BlockProof], if requested and available. + optional bytes proof = 2; } // Represents a block number.