From 89f1772adace4b1fb46d0cf3a9cc78f224da0e69 Mon Sep 17 00:00:00 2001 From: Mr-kay-cloud2 Date: Sun, 24 May 2026 14:36:43 +0100 Subject: [PATCH] fix(store): reject future sync block ranges --- CHANGELOG.md | 1 + crates/store/src/errors.rs | 20 +++++++++++++++++++ crates/store/src/server/rpc_api.rs | 31 ++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ab38e17..d953f1b93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - [BREAKING] Renamed `SubmitProvenBatch` RPC endpoint to `SubmitProvenTxBatch` ([#2094](https://github.com/0xMiden/node/pull/2094)). - Fixed block producer mempool panic when selecting transactions that depend on notes created by pruned committed transactions ([#2097](https://github.com/0xMiden/node/pull/2097)). - Implemented filtering based on the network account note script root allowlist in ntx-builder ([#2042](https://github.com/0xMiden/node/issues/2042)). +- Validated sync endpoint block ranges against the committed chain tip before querying state ([#1866](https://github.com/0xMiden/node/issues/1866)). - [BREAKING] Renamed `ExplorerStatusDetails` fields in the network monitor's `/status` payload from `number_of_*` to `total_*` (`total_transactions`, `total_nullifiers`, `total_notes`, `total_account_updates`). The values now represent network-wide cumulative totals from the explorer's `overviewStats` query instead of last-block counts. - [BREAKING] Removed `--wallet-filepath` / `--counter-filepath` flags and the `MIDEN_MONITOR_WALLET_FILEPATH` / `MIDEN_MONITOR_COUNTER_FILEPATH` env vars from the network monitor. The monitor now keeps wallet and counter accounts fully in memory and regenerates them on every startup; the dashboard's counter value resets to zero on restart. diff --git a/crates/store/src/errors.rs b/crates/store/src/errors.rs index 05b277e77..17842e1c5 100644 --- a/crates/store/src/errors.rs +++ b/crates/store/src/errors.rs @@ -359,6 +359,11 @@ pub enum SyncNullifiersError { DatabaseError(#[from] DatabaseError), #[error("invalid block range")] InvalidBlockRange(#[from] InvalidBlockRange), + #[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")] + FutureBlock { + chain_tip: BlockNumber, + block_to: BlockNumber, + }, #[error("unsupported prefix length: {0} (only 16-bit prefixes are supported)")] InvalidPrefixLength(u32), #[error("malformed nullifier prefix")] @@ -375,6 +380,11 @@ pub enum SyncAccountVaultError { DatabaseError(#[from] DatabaseError), #[error("invalid block range")] InvalidBlockRange(#[from] InvalidBlockRange), + #[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")] + FutureBlock { + chain_tip: BlockNumber, + block_to: BlockNumber, + }, #[error("malformed account ID")] DeserializationFailed(#[from] ConversionError), #[error("account {0} is not public")] @@ -391,6 +401,11 @@ pub enum SyncAccountStorageMapsError { DatabaseError(#[from] DatabaseError), #[error("invalid block range")] InvalidBlockRange(#[from] InvalidBlockRange), + #[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")] + FutureBlock { + chain_tip: BlockNumber, + block_to: BlockNumber, + }, #[error("malformed account ID")] DeserializationFailed(#[from] ConversionError), #[error("account {0} not found")] @@ -485,6 +500,11 @@ pub enum SyncTransactionsError { DatabaseError(#[from] DatabaseError), #[error("invalid block range")] InvalidBlockRange(#[from] InvalidBlockRange), + #[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")] + FutureBlock { + chain_tip: BlockNumber, + block_to: BlockNumber, + }, #[error("malformed account ID")] DeserializationFailed(#[from] ConversionError), #[error("account {0} not found")] diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index bab6c0521..5d313d5b8 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -124,6 +124,11 @@ impl rpc_server::Rpc for StoreApi { read_block_range::(request.block_range, "SyncNullifiersRequest")? .into_inclusive_range::()?; + let chain_tip = self.state.chain_tip(Finality::Committed).await; + if *block_range.end() > chain_tip { + Err(SyncNullifiersError::FutureBlock { chain_tip, block_to: *block_range.end() })?; + } + let (nullifiers, block_num) = self .state .sync_nullifiers(request.prefix_len, request.nullifiers, block_range) @@ -138,8 +143,6 @@ impl rpc_server::Rpc for StoreApi { }) .collect(); - let chain_tip = self.state.chain_tip(Finality::Committed).await; - Ok(Response::new(proto::rpc::SyncNullifiersResponse { pagination_info: Some(proto::rpc::PaginationInfo { chain_tip: chain_tip.as_u32(), @@ -314,6 +317,11 @@ impl rpc_server::Rpc for StoreApi { )? .into_inclusive_range::()?; + let chain_tip = self.state.chain_tip(Finality::Committed).await; + if *block_range.end() > chain_tip { + Err(SyncAccountVaultError::FutureBlock { chain_tip, block_to: *block_range.end() })?; + } + let (last_included_block, updates) = self .state .sync_account_vault(account_id, block_range) @@ -332,8 +340,6 @@ impl rpc_server::Rpc for StoreApi { }) .collect(); - let chain_tip = self.state.chain_tip(Finality::Committed).await; - Ok(Response::new(proto::rpc::SyncAccountVaultResponse { pagination_info: Some(proto::rpc::PaginationInfo { chain_tip: chain_tip.as_u32(), @@ -367,6 +373,14 @@ impl rpc_server::Rpc for StoreApi { )? .into_inclusive_range::()?; + let chain_tip = self.state.chain_tip(Finality::Committed).await; + if *block_range.end() > chain_tip { + Err(SyncAccountStorageMapsError::FutureBlock { + chain_tip, + block_to: *block_range.end(), + })?; + } + let storage_maps_page = self .state .sync_account_storage_maps(account_id, block_range) @@ -384,8 +398,6 @@ impl rpc_server::Rpc for StoreApi { }) .collect(); - let chain_tip = self.state.chain_tip(Finality::Committed).await; - Ok(Response::new(proto::rpc::SyncAccountStorageMapsResponse { pagination_info: Some(proto::rpc::PaginationInfo { chain_tip: chain_tip.as_u32(), @@ -440,6 +452,11 @@ impl rpc_server::Rpc for StoreApi { )? .into_inclusive_range::()?; + let chain_tip = self.state.chain_tip(Finality::Committed).await; + if *block_range.end() > chain_tip { + Err(SyncTransactionsError::FutureBlock { chain_tip, block_to: *block_range.end() })?; + } + let account_ids: Vec = read_account_ids::(request.account_ids)?; @@ -459,8 +476,6 @@ impl rpc_server::Rpc for StoreApi { .map(crate::db::TransactionRecord::into_proto) .collect(); - let chain_tip = self.state.chain_tip(Finality::Committed).await; - Ok(Response::new(proto::rpc::SyncTransactionsResponse { pagination_info: Some(proto::rpc::PaginationInfo { chain_tip: chain_tip.as_u32(),