From bce00daf9c19fb34a1b55a7574964d5cf71111dd Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 23 Mar 2026 16:40:35 +0100 Subject: [PATCH 1/3] fix(store): handle AllEntries pagination for genesis block storage maps (#1816) Co-authored-by: Claude (Opus) Co-authored-by: Mirko <48352201+Mirko-von-Leipzig@users.noreply.github.com> --- CHANGELOG.md | 1 + .../store/src/db/models/queries/accounts.rs | 12 +- crates/store/src/db/tests.rs | 149 ++++++++++++++++++ 3 files changed, 155 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 904db239b..0aea5e51b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - Fixed `bundled bootstrap` requiring `--validator.key.hex` or `--validator.key.kms-id` despite a default key being configured ([#1732](https://github.com/0xMiden/node/pull/1732)). - Fixed incorrectly classifying private notes with the network attachment as network notes ([#1378](https://github.com/0xMiden/node/pull/1738)). - Fixed accept header version negotiation rejecting all pre-release versions; pre-release label matching is now lenient, accepting any numeric suffix within the same label (e.g. `alpha.3` accepts `alpha.1`) ([#1755](https://github.com/0xMiden/node/pull/1755)). +- Fixed `GetAccount` returning an internal error for `AllEntries` requests on storage maps where all entries are in a single block (e.g. genesis accounts) ([#1816](https://github.com/0xMiden/node/pull/1816)). ## v0.13.8 (2026-03-12) diff --git a/crates/store/src/db/models/queries/accounts.rs b/crates/store/src/db/models/queries/accounts.rs index 0e8ae47b6..018ab7389 100644 --- a/crates/store/src/db/models/queries/accounts.rs +++ b/crates/store/src/db/models/queries/accounts.rs @@ -716,22 +716,20 @@ pub(crate) fn select_account_storage_map_values_paged( .limit(i64::try_from(limit + 1).expect("limit fits within i64")) .load(conn)?; - // Discard the last block in the response (assumes more than one block may be present) - + // If we got more rows than the limit, the last block may be incomplete so we + // drop it entirely and derive last_block_included from the remaining rows. let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last() && raw.len() > limit { - // NOTE: If the query contains at least one more row than the amount of storage map updates - // allowed in a single block for an account, then the response is guaranteed to have at - // least two blocks - let values = raw .into_iter() .take_while(|(bn, ..)| *bn != last_block_num) .map(StorageMapValue::from_raw_row) .collect::, DatabaseError>>()?; - (BlockNumber::from_raw_sql(last_block_num.saturating_sub(1))?, values) + let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num); + + (last_block_included, values) } else { ( *block_range.end(), diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index a9f72b99a..0d885123b 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -1219,6 +1219,155 @@ fn select_storage_map_sync_values_paginates_until_last_block() { assert_eq!(page.values.len(), 1, "should include block 1 only"); } +/// Tests that `select_account_storage_map_values_paged` does not panic when all entries +/// exceed the limit and are in genesis block (block 0). Previously, this caused +/// `last_block_num.saturating_sub(1) = -1` which failed `BlockNumber::from_raw_sql`. +#[test] +fn select_storage_map_sync_values_all_entries_in_genesis_block() { + let mut conn = create_db(); + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + let slot_name = StorageSlotName::mock(8); + + let genesis = BlockNumber::GENESIS; + create_block(&mut conn, genesis); + + queries::upsert_accounts(&mut conn, &[mock_block_account_update(account_id, 0)], genesis) + .unwrap(); + + // Insert 3 entries, all in genesis block + for i in 0..3 { + queries::insert_account_storage_map_value( + &mut conn, + account_id, + genesis, + slot_name.clone(), + StorageMapKey::from_index(i), + num_to_word(u64::from(i) + 100), + ) + .unwrap(); + } + + // Query with limit=1 so that raw.len() (3) > limit (1), triggering the + // pagination branch. All entries are in block 0, so take_while produces + // nothing and last_block_num.saturating_sub(1) = -1. + let result = queries::select_account_storage_map_values_paged( + &mut conn, + account_id, + genesis..=genesis, + 1, + ); + + // Should not error - should return a valid page (possibly with empty values + // indicating no progress, which the caller interprets as limit_exceeded) + let page = result.expect("should not return an internal error for genesis block entries"); + // The page should indicate no progress was made (stuck at genesis) + assert!( + page.values.is_empty() || page.last_block_included == genesis, + "should indicate pagination did not make progress" + ); +} + +/// Tests that single-block overflow works for non-genesis blocks too. +/// All entries are in block 5 and exceed the limit. The function should +/// signal no progress rather than returning incorrect data. +#[test] +fn select_storage_map_sync_values_all_entries_in_single_non_genesis_block() { + let mut conn = create_db(); + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + let slot_name = StorageSlotName::mock(10); + + let block5 = BlockNumber::from(5); + create_block(&mut conn, block5); + + queries::upsert_accounts(&mut conn, &[mock_block_account_update(account_id, 0)], block5) + .unwrap(); + + for i in 0..3 { + queries::insert_account_storage_map_value( + &mut conn, + account_id, + block5, + slot_name.clone(), + StorageMapKey::from_index(i), + num_to_word(u64::from(i) + 200), + ) + .unwrap(); + } + + // limit=1, so 3 rows > 1 triggers pagination. All in block 5. + let page = + queries::select_account_storage_map_values_paged(&mut conn, account_id, block5..=block5, 1) + .unwrap(); + + assert!(page.values.is_empty(), "should have no values when single block exceeds limit"); + assert_eq!(page.last_block_included, block5, "should signal no progress at block 5"); +} + +/// Tests that normal multi-block pagination still works correctly: +/// entries in blocks 1, 2, 3 with limit causing block 3 to be dropped. +#[test] +fn select_storage_map_sync_values_multi_block_pagination() { + let mut conn = create_db(); + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + let slot_name = StorageSlotName::mock(11); + + let block1 = BlockNumber::from(1); + let block2 = BlockNumber::from(2); + let block3 = BlockNumber::from(3); + + create_block(&mut conn, block1); + create_block(&mut conn, block2); + create_block(&mut conn, block3); + + queries::upsert_accounts(&mut conn, &[mock_block_account_update(account_id, 0)], block1) + .unwrap(); + queries::upsert_accounts(&mut conn, &[mock_block_account_update(account_id, 1)], block2) + .unwrap(); + queries::upsert_accounts(&mut conn, &[mock_block_account_update(account_id, 2)], block3) + .unwrap(); + + // 1 entry in block 1, 1 in block 2, 1 in block 3 + queries::insert_account_storage_map_value( + &mut conn, + account_id, + block1, + slot_name.clone(), + StorageMapKey::from_index(1), + num_to_word(11), + ) + .unwrap(); + queries::insert_account_storage_map_value( + &mut conn, + account_id, + block2, + slot_name.clone(), + StorageMapKey::from_index(2), + num_to_word(22), + ) + .unwrap(); + queries::insert_account_storage_map_value( + &mut conn, + account_id, + block3, + slot_name.clone(), + StorageMapKey::from_index(3), + num_to_word(33), + ) + .unwrap(); + + // limit=2: query fetches 3 rows (limit+1), drops block 3, keeps blocks 1-2 + let page = queries::select_account_storage_map_values_paged( + &mut conn, + account_id, + BlockNumber::GENESIS..=block3, + 2, + ) + .unwrap(); + + assert_eq!(page.values.len(), 2, "should include entries from blocks 1 and 2"); + assert_eq!(page.last_block_included, block2, "last included block should be 2"); +} + #[tokio::test] #[miden_node_test_macro::enable_logging] async fn reconstruct_storage_map_from_db_pages_until_latest() { From e68176e1039dfd8179925530870af4b7ec2e221a Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 23 Mar 2026 17:07:16 +0100 Subject: [PATCH 2/3] chore: bump version to `0.14.0-alpha.9` (#1821) --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 28 ++++++++++++++-------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d2a98a359..5ee3e504e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2873,7 +2873,7 @@ dependencies = [ [[package]] name = "miden-genesis" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "clap", @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "miden-large-smt-backend-rocksdb" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "miden-crypto", "miden-node-rocksdb-cxx-linkage-fix", @@ -2958,7 +2958,7 @@ dependencies = [ [[package]] name = "miden-network-monitor" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "axum", @@ -2986,7 +2986,7 @@ dependencies = [ [[package]] name = "miden-node" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "clap", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "miden-node-block-producer" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "assert_matches", @@ -3042,7 +3042,7 @@ dependencies = [ [[package]] name = "miden-node-db" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "deadpool", "deadpool-diesel", @@ -3055,7 +3055,7 @@ dependencies = [ [[package]] name = "miden-node-grpc-error-macro" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "quote", "syn 2.0.114", @@ -3063,7 +3063,7 @@ dependencies = [ [[package]] name = "miden-node-ntx-builder" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "build-rs", @@ -3096,7 +3096,7 @@ dependencies = [ [[package]] name = "miden-node-proto" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "assert_matches", @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "miden-node-proto-build" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "build-rs", "fs-err", @@ -3132,11 +3132,11 @@ dependencies = [ [[package]] name = "miden-node-rocksdb-cxx-linkage-fix" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" [[package]] name = "miden-node-rpc" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "futures", @@ -3168,7 +3168,7 @@ dependencies = [ [[package]] name = "miden-node-store" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "assert_matches", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "miden-node-stress-test" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "clap", "fs-err", @@ -3244,7 +3244,7 @@ dependencies = [ [[package]] name = "miden-node-utils" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "bytes", @@ -3277,7 +3277,7 @@ dependencies = [ [[package]] name = "miden-node-validator" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "aws-config", @@ -3378,7 +3378,7 @@ dependencies = [ [[package]] name = "miden-remote-prover" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "anyhow", "assert_matches", @@ -3415,7 +3415,7 @@ dependencies = [ [[package]] name = "miden-remote-prover-client" -version = "0.14.0-alpha.8" +version = "0.14.0-alpha.9" dependencies = [ "build-rs", "fs-err", diff --git a/Cargo.toml b/Cargo.toml index 93c0e9105..7646d0bb5 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-alpha.8" +version = "0.14.0-alpha.9" # Optimize the cryptography for faster tests involving account creation. [profile.test.package.miden-crypto] @@ -47,22 +47,22 @@ debug = true [workspace.dependencies] # Workspace crates. -miden-large-smt-backend-rocksdb = { path = "crates/large-smt-backend-rocksdb", version = "=0.14.0-alpha.8" } -miden-node-block-producer = { path = "crates/block-producer", version = "=0.14.0-alpha.8" } -miden-node-db = { path = "crates/db", version = "=0.14.0-alpha.8" } -miden-node-grpc-error-macro = { path = "crates/grpc-error-macro", version = "=0.14.0-alpha.8" } -miden-node-ntx-builder = { path = "crates/ntx-builder", version = "=0.14.0-alpha.8" } -miden-node-proto = { path = "crates/proto", version = "=0.14.0-alpha.8" } -miden-node-proto-build = { path = "proto", version = "=0.14.0-alpha.8" } -miden-node-rpc = { path = "crates/rpc", version = "=0.14.0-alpha.8" } -miden-node-store = { path = "crates/store", version = "=0.14.0-alpha.8" } +miden-large-smt-backend-rocksdb = { path = "crates/large-smt-backend-rocksdb", version = "=0.14.0-alpha.9" } +miden-node-block-producer = { path = "crates/block-producer", version = "=0.14.0-alpha.9" } +miden-node-db = { path = "crates/db", version = "=0.14.0-alpha.9" } +miden-node-grpc-error-macro = { path = "crates/grpc-error-macro", version = "=0.14.0-alpha.9" } +miden-node-ntx-builder = { path = "crates/ntx-builder", version = "=0.14.0-alpha.9" } +miden-node-proto = { path = "crates/proto", version = "=0.14.0-alpha.9" } +miden-node-proto-build = { path = "proto", version = "=0.14.0-alpha.9" } +miden-node-rpc = { path = "crates/rpc", version = "=0.14.0-alpha.9" } +miden-node-store = { path = "crates/store", version = "=0.14.0-alpha.9" } miden-node-test-macro = { path = "crates/test-macro" } -miden-node-utils = { path = "crates/utils", version = "=0.14.0-alpha.8" } -miden-node-validator = { path = "crates/validator", version = "=0.14.0-alpha.8" } -miden-remote-prover-client = { path = "crates/remote-prover-client", version = "=0.14.0-alpha.8" } +miden-node-utils = { path = "crates/utils", version = "=0.14.0-alpha.9" } +miden-node-validator = { path = "crates/validator", version = "=0.14.0-alpha.9" } +miden-remote-prover-client = { path = "crates/remote-prover-client", version = "=0.14.0-alpha.9" } # Temporary workaround until # is part of `rocksdb-rust` release -miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "=0.14.0-alpha.8" } +miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "=0.14.0-alpha.9" } # miden-base aka protocol dependencies. These should be updated in sync. miden-block-prover = { version = "=0.14.0-alpha.2" } From 24c475c05eee0c56617fa5edbca529328eaa82bd Mon Sep 17 00:00:00 2001 From: Marti Date: Mon, 23 Mar 2026 23:48:36 +0100 Subject: [PATCH 3/3] fix: correctly return `LimitExceeded` from `reconstruct_storage_map_from_db` when start block has too many entries (#1825) Co-authored-by: Claude (Opus) --- CHANGELOG.md | 1 + Cargo.lock | 36 ++++++------- Cargo.toml | 28 +++++----- crates/store/src/db/mod.rs | 6 +++ .../store/src/db/models/queries/accounts.rs | 11 ++-- .../src/db/models/queries/transactions.rs | 2 + crates/store/src/db/tests.rs | 51 +++++++++++++++++++ 7 files changed, 97 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aea5e51b..a9d864ad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Fixed incorrectly classifying private notes with the network attachment as network notes ([#1378](https://github.com/0xMiden/node/pull/1738)). - Fixed accept header version negotiation rejecting all pre-release versions; pre-release label matching is now lenient, accepting any numeric suffix within the same label (e.g. `alpha.3` accepts `alpha.1`) ([#1755](https://github.com/0xMiden/node/pull/1755)). - Fixed `GetAccount` returning an internal error for `AllEntries` requests on storage maps where all entries are in a single block (e.g. genesis accounts) ([#1816](https://github.com/0xMiden/node/pull/1816)). +- Fixed `GetAccount` returning empty storage map entries instead of `too_many_entries` when a genesis account's map exceeds the pagination limit ([#1816](https://github.com/0xMiden/node/pull/1816)). ## v0.13.8 (2026-03-12) diff --git a/Cargo.lock b/Cargo.lock index 5ee3e504e..cbc459d01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2873,7 +2873,7 @@ dependencies = [ [[package]] name = "miden-genesis" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "clap", @@ -2892,7 +2892,7 @@ dependencies = [ [[package]] name = "miden-large-smt-backend-rocksdb" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "miden-crypto", "miden-node-rocksdb-cxx-linkage-fix", @@ -2958,7 +2958,7 @@ dependencies = [ [[package]] name = "miden-network-monitor" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "axum", @@ -2986,7 +2986,7 @@ dependencies = [ [[package]] name = "miden-node" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "clap", @@ -3006,7 +3006,7 @@ dependencies = [ [[package]] name = "miden-node-block-producer" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "assert_matches", @@ -3042,7 +3042,7 @@ dependencies = [ [[package]] name = "miden-node-db" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "deadpool", "deadpool-diesel", @@ -3055,7 +3055,7 @@ dependencies = [ [[package]] name = "miden-node-grpc-error-macro" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "quote", "syn 2.0.114", @@ -3063,7 +3063,7 @@ dependencies = [ [[package]] name = "miden-node-ntx-builder" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "build-rs", @@ -3096,7 +3096,7 @@ dependencies = [ [[package]] name = "miden-node-proto" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "assert_matches", @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "miden-node-proto-build" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "build-rs", "fs-err", @@ -3132,11 +3132,11 @@ dependencies = [ [[package]] name = "miden-node-rocksdb-cxx-linkage-fix" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" [[package]] name = "miden-node-rpc" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "futures", @@ -3168,7 +3168,7 @@ dependencies = [ [[package]] name = "miden-node-store" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "assert_matches", @@ -3215,7 +3215,7 @@ dependencies = [ [[package]] name = "miden-node-stress-test" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "clap", "fs-err", @@ -3244,7 +3244,7 @@ dependencies = [ [[package]] name = "miden-node-utils" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "bytes", @@ -3277,7 +3277,7 @@ dependencies = [ [[package]] name = "miden-node-validator" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "aws-config", @@ -3378,7 +3378,7 @@ dependencies = [ [[package]] name = "miden-remote-prover" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "anyhow", "assert_matches", @@ -3415,7 +3415,7 @@ dependencies = [ [[package]] name = "miden-remote-prover-client" -version = "0.14.0-alpha.9" +version = "0.14.0-alpha.10" dependencies = [ "build-rs", "fs-err", diff --git a/Cargo.toml b/Cargo.toml index 7646d0bb5..0f9154d6b 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-alpha.9" +version = "0.14.0-alpha.10" # Optimize the cryptography for faster tests involving account creation. [profile.test.package.miden-crypto] @@ -47,22 +47,22 @@ debug = true [workspace.dependencies] # Workspace crates. -miden-large-smt-backend-rocksdb = { path = "crates/large-smt-backend-rocksdb", version = "=0.14.0-alpha.9" } -miden-node-block-producer = { path = "crates/block-producer", version = "=0.14.0-alpha.9" } -miden-node-db = { path = "crates/db", version = "=0.14.0-alpha.9" } -miden-node-grpc-error-macro = { path = "crates/grpc-error-macro", version = "=0.14.0-alpha.9" } -miden-node-ntx-builder = { path = "crates/ntx-builder", version = "=0.14.0-alpha.9" } -miden-node-proto = { path = "crates/proto", version = "=0.14.0-alpha.9" } -miden-node-proto-build = { path = "proto", version = "=0.14.0-alpha.9" } -miden-node-rpc = { path = "crates/rpc", version = "=0.14.0-alpha.9" } -miden-node-store = { path = "crates/store", version = "=0.14.0-alpha.9" } +miden-large-smt-backend-rocksdb = { path = "crates/large-smt-backend-rocksdb", version = "=0.14.0-alpha.10" } +miden-node-block-producer = { path = "crates/block-producer", version = "=0.14.0-alpha.10" } +miden-node-db = { path = "crates/db", version = "=0.14.0-alpha.10" } +miden-node-grpc-error-macro = { path = "crates/grpc-error-macro", version = "=0.14.0-alpha.10" } +miden-node-ntx-builder = { path = "crates/ntx-builder", version = "=0.14.0-alpha.10" } +miden-node-proto = { path = "crates/proto", version = "=0.14.0-alpha.10" } +miden-node-proto-build = { path = "proto", version = "=0.14.0-alpha.10" } +miden-node-rpc = { path = "crates/rpc", version = "=0.14.0-alpha.10" } +miden-node-store = { path = "crates/store", version = "=0.14.0-alpha.10" } miden-node-test-macro = { path = "crates/test-macro" } -miden-node-utils = { path = "crates/utils", version = "=0.14.0-alpha.9" } -miden-node-validator = { path = "crates/validator", version = "=0.14.0-alpha.9" } -miden-remote-prover-client = { path = "crates/remote-prover-client", version = "=0.14.0-alpha.9" } +miden-node-utils = { path = "crates/utils", version = "=0.14.0-alpha.10" } +miden-node-validator = { path = "crates/validator", version = "=0.14.0-alpha.10" } +miden-remote-prover-client = { path = "crates/remote-prover-client", version = "=0.14.0-alpha.10" } # Temporary workaround until # is part of `rocksdb-rust` release -miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "=0.14.0-alpha.9" } +miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "=0.14.0-alpha.10" } # miden-base aka protocol dependencies. These should be updated in sync. miden-block-prover = { version = "=0.14.0-alpha.2" } diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 43df59d5e..1dcd8af4a 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -649,6 +649,12 @@ impl Db { values.extend(page.values); let mut last_block_included = page.last_block_included; + // If the first page returned no values, the block at block_range_start has more + // entries than the limit allows (e.g. genesis accounts with large storage maps). + if values.is_empty() && last_block_included == block_range_start { + return Ok(AccountStorageMapDetails::limit_exceeded(slot_name)); + } + loop { if page.last_block_included == block_num || page.last_block_included < block_range_start { diff --git a/crates/store/src/db/models/queries/accounts.rs b/crates/store/src/db/models/queries/accounts.rs index 018ab7389..f4260c12f 100644 --- a/crates/store/src/db/models/queries/accounts.rs +++ b/crates/store/src/db/models/queries/accounts.rs @@ -468,21 +468,20 @@ pub(crate) fn select_account_vault_assets( .limit(i64::try_from(MAX_ROWS + 1).expect("should fit within i64")) .load::<(i64, Vec, Option>)>(conn)?; - // Discard the last block in the response (assumes more than one block may be present) + // If we got more rows than the limit, the last block may be incomplete so we + // drop it entirely and derive last_block_included from the remaining rows. let (last_block_included, values) = if let Some(&(last_block_num, ..)) = raw.last() && raw.len() > MAX_ROWS { - // NOTE: If the query contains at least one more row than the amount of storage map updates - // allowed in a single block for an account, then the response is guaranteed to have at - // least two blocks - let values = raw .into_iter() .take_while(|(bn, ..)| *bn != last_block_num) .map(AccountVaultValue::from_raw_row) .collect::, DatabaseError>>()?; - (BlockNumber::from_raw_sql(last_block_num.saturating_sub(1))?, values) + let last_block_included = values.last().map_or(*block_range.start(), |v| v.block_num); + + (last_block_included, values) } else { ( *block_range.end(), diff --git a/crates/store/src/db/models/queries/transactions.rs b/crates/store/src/db/models/queries/transactions.rs index 69b3986f1..33301b453 100644 --- a/crates/store/src/db/models/queries/transactions.rs +++ b/crates/store/src/db/models/queries/transactions.rs @@ -301,6 +301,8 @@ pub fn select_transactions_records( )?; // SAFETY: block_num came from the database and was previously validated + // Subtraction is safe under the assumption that genesis block (where it could fail) does + // not have any transactions. let last_included_block = BlockNumber::from_raw_sql(last_block_num.saturating_sub(1))?; Ok((last_included_block, filtered_transactions)) } else { diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 0d885123b..4ca7cb379 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -1434,6 +1434,57 @@ async fn reconstruct_storage_map_from_db_pages_until_latest() { }); } +/// Tests that `reconstruct_storage_map_from_db` returns `LimitExceeded` when the first +/// block in the range has more entries than the limit allows. Previously this returned +/// `AllEntries([])` because the pagination loop exited immediately (`last_block_included` == +/// `block_num`) without checking that no values were actually returned. +#[tokio::test] +#[miden_node_test_macro::enable_logging] +async fn reconstruct_storage_map_from_db_returns_limit_exceeded_for_single_block_overflow() { + let temp_dir = tempdir().unwrap(); + let db_path = temp_dir.path().join("store.sqlite"); + + let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + let slot_name = StorageSlotName::mock(12); + + let block5 = BlockNumber::from(5); + + let db = crate::db::Db::load(db_path).await.unwrap(); + let slot_name_for_db = slot_name.clone(); + db.query("insert entries in single block", move |db_conn| { + db_conn.transaction(|db_conn| { + apply_migrations(db_conn)?; + create_block(db_conn, block5); + + queries::upsert_accounts(db_conn, &[mock_block_account_update(account_id, 0)], block5)?; + + // Insert 3 entries, all in the same block + for i in 1..=3 { + queries::insert_account_storage_map_value( + db_conn, + account_id, + block5, + slot_name_for_db.clone(), + num_to_storage_map_key(i), + num_to_word(i * 10), + )?; + } + Ok::<_, DatabaseError>(()) + }) + }) + .await + .unwrap(); + + // Use limit=1 so that 3 entries in a single block exceed the limit. + // block_range_start is block5 (the first block with data), and the target is also block5. + let details = db + .reconstruct_storage_map_from_db(account_id, slot_name.clone(), block5, Some(1)) + .await + .unwrap(); + + assert_matches!(details.entries, StorageMapEntries::LimitExceeded); +} + // UTILITIES // ------------------------------------------------------------------------------------------- fn num_to_word(n: u64) -> Word {