From 798f3c6c6e155bca8f5cda1cc72d1e46aa86fe62 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 10 May 2026 18:19:26 +0700 Subject: [PATCH 1/4] feat(merk,grovedb): expose aggregate_count proof verification under verify feature The AggregateCountOnRange proof primitive (PR #656) had its modules gated behind feature = "minimal" in both grovedb-merk and grovedb, making the verifier entry points (`verify_aggregate_count_on_range_proof` and `GroveDb::verify_aggregate_count_query`) unreachable from downstream lean-verifier crates that depend on `grovedb`/`grovedb-merk` with `default-features = false, features = ["verify"]`. Widen the module gates to `any(feature = "minimal", feature = "verify")` to match the pattern already used by `query::common`, `query::merge`, `query::query_item`, and `query::verify` in the same file. Refactor aggregate_count.rs to keep prover-only items (`RefWalker`, `Fetch`, `AggregateData`, `emit_count_proof`, etc.) gated `feature = "minimal"` while exposing the verifier (`verify_aggregate_count_on_range_proof`, `verify_count_shape`, `classify_subtree`, `key_strictly_inside`) under the wider gate. Add a `cfg(all(test, feature = "verify"))` test module with hex-fixture round-trip, empty-merk, and byte-mutation no-silent-forgery tests that exercise the verifier without any prover dependency, plus an `#[ignore]`d helper to regenerate the fixture if the proof encoding ever changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- grovedb/src/operations/proof/mod.rs | 2 +- merk/src/proofs/query/aggregate_count.rs | 153 ++++++++++++++++++++++- merk/src/proofs/query/mod.rs | 4 +- 3 files changed, 152 insertions(+), 7 deletions(-) diff --git a/grovedb/src/operations/proof/mod.rs b/grovedb/src/operations/proof/mod.rs index c10681c4b..74784f1c9 100644 --- a/grovedb/src/operations/proof/mod.rs +++ b/grovedb/src/operations/proof/mod.rs @@ -1,6 +1,6 @@ //! Proof operations -#[cfg(feature = "minimal")] +#[cfg(any(feature = "minimal", feature = "verify"))] mod aggregate_count; #[cfg(feature = "minimal")] mod generate; diff --git a/merk/src/proofs/query/aggregate_count.rs b/merk/src/proofs/query/aggregate_count.rs index 8cd493986..f7be9e91c 100644 --- a/merk/src/proofs/query/aggregate_count.rs +++ b/merk/src/proofs/query/aggregate_count.rs @@ -12,19 +12,26 @@ //! mechanics). On any other tree type the entry point returns //! `Error::InvalidProofError`. +#[cfg(feature = "minimal")] use std::collections::LinkedList; use grovedb_costs::{cost_return_on_error, CostResult, CostsExt, OperationCost}; +#[cfg(feature = "minimal")] use grovedb_version::version::GroveVersion; +#[cfg(feature = "minimal")] +use crate::{ + proofs::Op, + tree::{kv::ValueDefinedCostType, AggregateData, Fetch, RefWalker}, + TreeType, +}; use crate::{ proofs::{ query::QueryItem, tree::{execute_with_options, Tree as ProofTree}, - Decoder, Node, Op, + Decoder, Node, }, - tree::{kv::ValueDefinedCostType, AggregateData, Fetch, RefWalker}, - CryptoHash, Error, TreeType, + CryptoHash, Error, }; /// All-zero `CryptoHash`, used in `Node::HashWithCount` for missing children. @@ -120,6 +127,7 @@ fn classify_subtree( /// Returns true if `tree_type` is one of the four tree types that can host an /// `AggregateCountOnRange` proof. Wrapper types are accepted by stripping /// down to the inner tree type via `is_provable_count_bearing`. +#[cfg(feature = "minimal")] fn is_provable_count_bearing(tree_type: TreeType) -> bool { matches!( tree_type, @@ -131,6 +139,7 @@ fn is_provable_count_bearing(tree_type: TreeType) -> bool { /// Returns `Err(InvalidProofError)` for any other variant — the entry point /// has already gated `tree_type`, so reaching the error means the tree's /// in-memory state disagrees with its declared type. +#[cfg(feature = "minimal")] fn provable_count_from_aggregate(data: AggregateData) -> Result { match data { AggregateData::ProvableCount(c) => Ok(c), @@ -142,6 +151,7 @@ fn provable_count_from_aggregate(data: AggregateData) -> Result { } } +#[cfg(feature = "minimal")] impl RefWalker<'_, S> where S: Fetch + Sized + Clone, @@ -189,6 +199,7 @@ where /// At entry, `subtree_lo_excl` / `subtree_hi_excl` are the inherited /// exclusive key bounds for the subtree this walker points at (both `None` /// at the root call). +#[cfg(feature = "minimal")] fn emit_count_proof( walker: &mut RefWalker<'_, S>, range: &QueryItem, @@ -650,10 +661,36 @@ fn key_strictly_inside(key: &[u8], lo: Option<&[u8]>, hi: Option<&[u8]>) -> bool lo_ok && hi_ok } -#[cfg(test)] +#[cfg(all(test, feature = "minimal"))] mod tests { use super::*; + #[test] + #[ignore = "regenerate fixtures on demand: cargo test -p grovedb-merk --features full -- aggregate_count::tests::dump_verify_only_fixtures --ignored --nocapture"] + fn dump_verify_only_fixtures() { + let v = GroveVersion::latest(); + let (merk, root) = make_15_key_provable_count_tree(v); + let inner_range = QueryItem::RangeInclusive(b"c".to_vec()..=b"l".to_vec()); + let (ops, count) = merk + .prove_aggregate_count_on_range(&inner_range, v) + .unwrap() + .expect("prove"); + let bytes = encode_proof(&ops); + println!( + "// 15-key ProvableCountTree, RangeInclusive(b\"c\"..=b\"l\") → count = {}", + count + ); + println!( + "const FIXTURE_15_KEY_C_TO_L_PROOF_HEX: &str = \"{}\";", + hex::encode(&bytes) + ); + println!( + "const FIXTURE_15_KEY_C_TO_L_ROOT_HEX: &str = \"{}\";", + hex::encode(root) + ); + println!("const FIXTURE_15_KEY_C_TO_L_COUNT: u64 = {};", count); + } + fn range_inclusive(lo: &[u8], hi: &[u8]) -> QueryItem { QueryItem::RangeInclusive(lo.to_vec()..=hi.to_vec()) } @@ -1589,3 +1626,111 @@ mod tests { } } } + +/// Verifier-only smoke tests that exercise the leaf-level verifier without +/// pulling in any prover-side machinery (no `TempMerk`, no `RefWalker`). +/// They consume hardcoded fixtures generated by the +/// `dump_verify_only_fixtures` helper above; regenerate with +/// `cargo test -p grovedb-merk --features full -- aggregate_count::tests::dump_verify_only_fixtures --ignored --nocapture` +/// if the proof encoding ever changes. +#[cfg(all(test, feature = "verify"))] +mod verify_only_tests { + use super::*; + + /// Hex-encoded proof bytes for a 15-key `ProvableCountTree` (keys + /// "a"..="o", each with feature `ProvableCountedMerkNode(1)`) queried + /// with `RangeInclusive("c"..="l")`. Captured from + /// `dump_verify_only_fixtures`; regenerate if the proof encoding ever + /// changes. + const FIXTURE_15_KEY_C_TO_L_PROOF_HEX: &str = "1e76f2d62fbefb076d8902b2f25bcf9acbd1e903b740c98ea0d926473922f6bbb50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011a01622022ec9d571ba774cf9e83d0194962f5d1e3aa1a48d486a67e2762a6c79590150000000000000003101a0163b7d770040f780e9deff6bc038abea66e108b88d098d16d24cd7486eb671060b20000000000000001111a0164d2ad1a0bb9fdf4450bd87151c08b9968cd046bda6654aabdba2430b0a981e7900000000000000007101e28b724715b1fab1f72e0be7e944488dcfbeeb875867d27c06ad9bad8c739997207ce95cd4d1789e01c0a079a3f2c18a3888f2d69fa6d0eabf51c2b434f7cb99e212f1a0042798fb890e8203f007f4f58b033a72cb4e070bfddceb27687d641fe0000000000000003111a01683fc14ed7ecde203a90425ee191e9db5966336d737f0398ec93b764517b6df400000000000000000f101e6da2e2f8e4bdead2a8ac51909f0fa0fb88d47d6bc3b84858bb739fb28a36501031b7c191d5ac70764f815bd7a6c7d0e628f48cef5b813933c07d5ce0ac1dbd5a995443ca10193ebf20e64468deaecc061a981a6dbf4f30e7154b5e9ab806866d00000000000000031a016c55c024f95ca4cc338f7cc2e25db37be2a3fa3a40b151017e460bfc0779cf369f0000000000000007101e3673296561a4d6c3e1ec5cd02c5c468acbd3c8ccd4a42906e8ed06d3fb587a0d2b6d9e310b7c94d3f91fcbb3d5f7547b76c6d1ab3ac3d3540752c5f0b46be24a2f66bf541434a53eae46fa4e6092c03511538c0e1a2c5fc0f0deb72de08a71e500000000000000031111"; + const FIXTURE_15_KEY_C_TO_L_ROOT_HEX: &str = + "19ed16776ebe6643b342a238baf7508ddf687fc4bdd53e98f91df8bffb605d96"; + const FIXTURE_15_KEY_C_TO_L_COUNT: u64 = 10; + + /// Empty proof bytes encode "empty merk" — the verifier returns + /// `(NULL_HASH, 0)`. This case has no prover dependency at all and is + /// the most basic compile-time signal that the verifier path is wired + /// up correctly under `--no-default-features --features verify`. + #[test] + fn empty_merk_returns_null_hash_and_zero_count() { + let inner = QueryItem::Range(b"a".to_vec()..b"z".to_vec()); + let (root, count) = verify_aggregate_count_on_range_proof(&[], &inner) + .unwrap() + .expect("verify on empty proof must succeed"); + assert_eq!(root, NULL_HASH); + assert_eq!(count, 0); + } + + /// A real `RangeInclusive("c"..="l")` proof against a 15-key + /// `ProvableCountTree`. The verifier must reconstruct the expected + /// merk root hash and recover count = 10. + #[test] + fn fixture_15_key_range_c_to_l_verifies() { + let proof = hex::decode(FIXTURE_15_KEY_C_TO_L_PROOF_HEX).expect("valid hex"); + let mut expected_root = [0u8; 32]; + expected_root + .copy_from_slice(&hex::decode(FIXTURE_15_KEY_C_TO_L_ROOT_HEX).expect("valid hex")); + + let inner = QueryItem::RangeInclusive(b"c".to_vec()..=b"l".to_vec()); + let (root, count) = verify_aggregate_count_on_range_proof(&proof, &inner) + .unwrap() + .expect("fixture proof must verify"); + assert_eq!( + root, expected_root, + "verifier reconstructed an unexpected root — fixture stale?" + ); + assert_eq!(count, FIXTURE_15_KEY_C_TO_L_COUNT); + } + + /// Mutating any single byte of the fixture proof must not yield a + /// `(honest_root, wrong_count)` outcome — the hash chain binds count via + /// `node_hash_with_count`, so any successful verify with the honest root + /// must reproduce the honest count. Single-fixture analogue of + /// `fuzz_byte_mutation_no_silent_forgery` that runs without the prover. + #[test] + fn fixture_byte_mutation_does_not_silently_forge_count() { + let proof = hex::decode(FIXTURE_15_KEY_C_TO_L_PROOF_HEX).expect("valid hex"); + let mut expected_root = [0u8; 32]; + expected_root + .copy_from_slice(&hex::decode(FIXTURE_15_KEY_C_TO_L_ROOT_HEX).expect("valid hex")); + let inner = QueryItem::RangeInclusive(b"c".to_vec()..=b"l".to_vec()); + + // Sanity check: the honest fixture verifies under the same code path + // the mutation loop will exercise. Without this, an `Err`-on-honest + // fixture would silently make every mutation a vacuous pass. + let (honest_root, honest_count) = verify_aggregate_count_on_range_proof(&proof, &inner) + .unwrap() + .expect("honest fixture must verify (regenerate fixture if this fails)"); + assert_eq!(honest_root, expected_root); + assert_eq!(honest_count, FIXTURE_15_KEY_C_TO_L_COUNT); + + for byte_idx in 0..proof.len() { + for &delta in &[1u8, 0x55, 0xff] { + let mut bytes = proof.clone(); + let original = bytes[byte_idx]; + let mutated = if delta == 0xff { + original ^ 0xff + } else { + original.wrapping_add(delta) + }; + if mutated == original { + continue; + } + bytes[byte_idx] = mutated; + if let Ok((root, count)) = + verify_aggregate_count_on_range_proof(&bytes, &inner).unwrap() + && root == expected_root + { + assert_eq!( + count, FIXTURE_15_KEY_C_TO_L_COUNT, + "SILENT FORGERY at byte {} (delta=0x{:02x}): \ + verifier returned the honest root but a wrong count \ + ({} != {}).", + byte_idx, delta, count, FIXTURE_15_KEY_C_TO_L_COUNT + ); + } + // Err and Ok-with-different-root are both safe outcomes. + } + } + } +} diff --git a/merk/src/proofs/query/mod.rs b/merk/src/proofs/query/mod.rs index 1fd556a2f..fcff0ad2b 100644 --- a/merk/src/proofs/query/mod.rs +++ b/merk/src/proofs/query/mod.rs @@ -5,14 +5,14 @@ pub use grovedb_query::*; #[cfg(test)] mod merk_integration_tests; -#[cfg(feature = "minimal")] +#[cfg(any(feature = "minimal", feature = "verify"))] pub mod aggregate_count; #[cfg(any(feature = "minimal", feature = "verify"))] mod map; #[cfg(any(feature = "minimal", feature = "verify"))] mod verify; -#[cfg(feature = "minimal")] +#[cfg(any(feature = "minimal", feature = "verify"))] pub use aggregate_count::verify_aggregate_count_on_range_proof; #[cfg(feature = "minimal")] From ab88c752ebe41a465f32b4ad98a5bc0b92d98817 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 10 May 2026 18:24:58 +0700 Subject: [PATCH 2/4] test: widen verify_only_tests gate so default CI runs them The verify_only_tests module was gated cfg(all(test, feature = "verify")), which hid it from CI runs that only enable the default `full` feature set (which doesn't include `verify`). Codecov flagged the test bodies as uncovered patch lines as a result. Widen to cfg(all(test, any(feature = "minimal", feature = "verify"))). The verifier function is available under both gates and these tests have no prover dependency, so the `verify`-only gate was buying nothing except missing CI coverage. Co-Authored-By: Claude Opus 4.7 (1M context) --- merk/src/proofs/query/aggregate_count.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/merk/src/proofs/query/aggregate_count.rs b/merk/src/proofs/query/aggregate_count.rs index f7be9e91c..883a4a4eb 100644 --- a/merk/src/proofs/query/aggregate_count.rs +++ b/merk/src/proofs/query/aggregate_count.rs @@ -1633,7 +1633,13 @@ mod tests { /// `dump_verify_only_fixtures` helper above; regenerate with /// `cargo test -p grovedb-merk --features full -- aggregate_count::tests::dump_verify_only_fixtures --ignored --nocapture` /// if the proof encoding ever changes. -#[cfg(all(test, feature = "verify"))] +/// +/// Gated `any(minimal, verify)` (rather than `verify` only) so the +/// default `full` CI test runs still exercise these. The verifier +/// function is available under both gates and these tests have no +/// prover dependency, so a `verify`-only gate would just hide them +/// from CI. +#[cfg(all(test, any(feature = "minimal", feature = "verify")))] mod verify_only_tests { use super::*; From ac30d31be6059125a52ab2ca898d593d83ff46fd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 10 May 2026 18:30:18 +0700 Subject: [PATCH 3/4] test: convert ignored fixture-dump helper into active parity check The #[ignore]d dump_verify_only_fixtures test never ran in CI, so its ~17 lines counted as uncovered patch lines. Replace it with an active verify_only_fixture_matches_fresh_prover_output test that runs every build, asserts the hardcoded fixture in verify_only_tests still matches fresh prover output byte-for-byte, and prints regeneration-ready constants on mismatch. This catches encoding drift automatically and gets the fixture maintenance lines into CI coverage. Promote the FIXTURE_*_HEX/COUNT constants to pub(super) so the parity test can compare against them. Co-Authored-By: Claude Opus 4.7 (1M context) --- merk/src/proofs/query/aggregate_count.rs | 49 ++++++++++++++++-------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/merk/src/proofs/query/aggregate_count.rs b/merk/src/proofs/query/aggregate_count.rs index 883a4a4eb..7568f426e 100644 --- a/merk/src/proofs/query/aggregate_count.rs +++ b/merk/src/proofs/query/aggregate_count.rs @@ -665,9 +665,12 @@ fn key_strictly_inside(key: &[u8], lo: Option<&[u8]>, hi: Option<&[u8]>) -> bool mod tests { use super::*; + /// Asserts the hardcoded fixture in the `verify_only_tests` module + /// still matches the bytes a fresh prove run produces. If the proof + /// encoding ever changes, this test fails and prints the new + /// constants — copy them into `verify_only_tests`. #[test] - #[ignore = "regenerate fixtures on demand: cargo test -p grovedb-merk --features full -- aggregate_count::tests::dump_verify_only_fixtures --ignored --nocapture"] - fn dump_verify_only_fixtures() { + fn verify_only_fixture_matches_fresh_prover_output() { let v = GroveVersion::latest(); let (merk, root) = make_15_key_provable_count_tree(v); let inner_range = QueryItem::RangeInclusive(b"c".to_vec()..=b"l".to_vec()); @@ -675,20 +678,34 @@ mod tests { .prove_aggregate_count_on_range(&inner_range, v) .unwrap() .expect("prove"); - let bytes = encode_proof(&ops); - println!( - "// 15-key ProvableCountTree, RangeInclusive(b\"c\"..=b\"l\") → count = {}", - count + let proof_hex = hex::encode(encode_proof(&ops)); + let root_hex = hex::encode(root); + + let drift_msg = format!( + "aggregate_count proof encoding has drifted — update verify_only_tests:\n\ + const FIXTURE_15_KEY_C_TO_L_PROOF_HEX: &str = \"{}\";\n\ + const FIXTURE_15_KEY_C_TO_L_ROOT_HEX: &str = \"{}\";\n\ + const FIXTURE_15_KEY_C_TO_L_COUNT: u64 = {};", + proof_hex, root_hex, count ); - println!( - "const FIXTURE_15_KEY_C_TO_L_PROOF_HEX: &str = \"{}\";", - hex::encode(&bytes) + assert_eq!( + proof_hex, + super::verify_only_tests::FIXTURE_15_KEY_C_TO_L_PROOF_HEX, + "{}", + drift_msg ); - println!( - "const FIXTURE_15_KEY_C_TO_L_ROOT_HEX: &str = \"{}\";", - hex::encode(root) + assert_eq!( + root_hex, + super::verify_only_tests::FIXTURE_15_KEY_C_TO_L_ROOT_HEX, + "{}", + drift_msg + ); + assert_eq!( + count, + super::verify_only_tests::FIXTURE_15_KEY_C_TO_L_COUNT, + "{}", + drift_msg ); - println!("const FIXTURE_15_KEY_C_TO_L_COUNT: u64 = {};", count); } fn range_inclusive(lo: &[u8], hi: &[u8]) -> QueryItem { @@ -1648,10 +1665,10 @@ mod verify_only_tests { /// with `RangeInclusive("c"..="l")`. Captured from /// `dump_verify_only_fixtures`; regenerate if the proof encoding ever /// changes. - const FIXTURE_15_KEY_C_TO_L_PROOF_HEX: &str = "1e76f2d62fbefb076d8902b2f25bcf9acbd1e903b740c98ea0d926473922f6bbb50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011a01622022ec9d571ba774cf9e83d0194962f5d1e3aa1a48d486a67e2762a6c79590150000000000000003101a0163b7d770040f780e9deff6bc038abea66e108b88d098d16d24cd7486eb671060b20000000000000001111a0164d2ad1a0bb9fdf4450bd87151c08b9968cd046bda6654aabdba2430b0a981e7900000000000000007101e28b724715b1fab1f72e0be7e944488dcfbeeb875867d27c06ad9bad8c739997207ce95cd4d1789e01c0a079a3f2c18a3888f2d69fa6d0eabf51c2b434f7cb99e212f1a0042798fb890e8203f007f4f58b033a72cb4e070bfddceb27687d641fe0000000000000003111a01683fc14ed7ecde203a90425ee191e9db5966336d737f0398ec93b764517b6df400000000000000000f101e6da2e2f8e4bdead2a8ac51909f0fa0fb88d47d6bc3b84858bb739fb28a36501031b7c191d5ac70764f815bd7a6c7d0e628f48cef5b813933c07d5ce0ac1dbd5a995443ca10193ebf20e64468deaecc061a981a6dbf4f30e7154b5e9ab806866d00000000000000031a016c55c024f95ca4cc338f7cc2e25db37be2a3fa3a40b151017e460bfc0779cf369f0000000000000007101e3673296561a4d6c3e1ec5cd02c5c468acbd3c8ccd4a42906e8ed06d3fb587a0d2b6d9e310b7c94d3f91fcbb3d5f7547b76c6d1ab3ac3d3540752c5f0b46be24a2f66bf541434a53eae46fa4e6092c03511538c0e1a2c5fc0f0deb72de08a71e500000000000000031111"; - const FIXTURE_15_KEY_C_TO_L_ROOT_HEX: &str = + pub(super) const FIXTURE_15_KEY_C_TO_L_PROOF_HEX: &str = "1e76f2d62fbefb076d8902b2f25bcf9acbd1e903b740c98ea0d926473922f6bbb50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011a01622022ec9d571ba774cf9e83d0194962f5d1e3aa1a48d486a67e2762a6c79590150000000000000003101a0163b7d770040f780e9deff6bc038abea66e108b88d098d16d24cd7486eb671060b20000000000000001111a0164d2ad1a0bb9fdf4450bd87151c08b9968cd046bda6654aabdba2430b0a981e7900000000000000007101e28b724715b1fab1f72e0be7e944488dcfbeeb875867d27c06ad9bad8c739997207ce95cd4d1789e01c0a079a3f2c18a3888f2d69fa6d0eabf51c2b434f7cb99e212f1a0042798fb890e8203f007f4f58b033a72cb4e070bfddceb27687d641fe0000000000000003111a01683fc14ed7ecde203a90425ee191e9db5966336d737f0398ec93b764517b6df400000000000000000f101e6da2e2f8e4bdead2a8ac51909f0fa0fb88d47d6bc3b84858bb739fb28a36501031b7c191d5ac70764f815bd7a6c7d0e628f48cef5b813933c07d5ce0ac1dbd5a995443ca10193ebf20e64468deaecc061a981a6dbf4f30e7154b5e9ab806866d00000000000000031a016c55c024f95ca4cc338f7cc2e25db37be2a3fa3a40b151017e460bfc0779cf369f0000000000000007101e3673296561a4d6c3e1ec5cd02c5c468acbd3c8ccd4a42906e8ed06d3fb587a0d2b6d9e310b7c94d3f91fcbb3d5f7547b76c6d1ab3ac3d3540752c5f0b46be24a2f66bf541434a53eae46fa4e6092c03511538c0e1a2c5fc0f0deb72de08a71e500000000000000031111"; + pub(super) const FIXTURE_15_KEY_C_TO_L_ROOT_HEX: &str = "19ed16776ebe6643b342a238baf7508ddf687fc4bdd53e98f91df8bffb605d96"; - const FIXTURE_15_KEY_C_TO_L_COUNT: u64 = 10; + pub(super) const FIXTURE_15_KEY_C_TO_L_COUNT: u64 = 10; /// Empty proof bytes encode "empty merk" — the verifier returns /// `(NULL_HASH, 0)`. This case has no prover dependency at all and is From d3b1bb179e321708879b4601ee39d719667cef0b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 10 May 2026 19:04:38 +0700 Subject: [PATCH 4/4] test: drop redundant feature gates on test modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both test modules were gated with feature = "minimal" / any(minimal, verify), but the crate convention (e.g., merk/src/element/tree_type.rs) is plain #[cfg(test)] — tests rely on default = ["full"] (which pulls in minimal) being active at test time. The extra gates were defensive beyond what the surrounding code does. Also tighten the verify_only_tests doc comment now that the drift check runs on every build instead of being a separate ignored helper. Co-Authored-By: Claude Opus 4.7 (1M context) --- merk/src/proofs/query/aggregate_count.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/merk/src/proofs/query/aggregate_count.rs b/merk/src/proofs/query/aggregate_count.rs index 7568f426e..915de7223 100644 --- a/merk/src/proofs/query/aggregate_count.rs +++ b/merk/src/proofs/query/aggregate_count.rs @@ -661,7 +661,7 @@ fn key_strictly_inside(key: &[u8], lo: Option<&[u8]>, hi: Option<&[u8]>) -> bool lo_ok && hi_ok } -#[cfg(all(test, feature = "minimal"))] +#[cfg(test)] mod tests { use super::*; @@ -1646,17 +1646,10 @@ mod tests { /// Verifier-only smoke tests that exercise the leaf-level verifier without /// pulling in any prover-side machinery (no `TempMerk`, no `RefWalker`). -/// They consume hardcoded fixtures generated by the -/// `dump_verify_only_fixtures` helper above; regenerate with -/// `cargo test -p grovedb-merk --features full -- aggregate_count::tests::dump_verify_only_fixtures --ignored --nocapture` -/// if the proof encoding ever changes. -/// -/// Gated `any(minimal, verify)` (rather than `verify` only) so the -/// default `full` CI test runs still exercise these. The verifier -/// function is available under both gates and these tests have no -/// prover dependency, so a `verify`-only gate would just hide them -/// from CI. -#[cfg(all(test, any(feature = "minimal", feature = "verify")))] +/// They consume hardcoded fixtures kept in lockstep with the prover by +/// `tests::verify_only_fixture_matches_fresh_prover_output` above — +/// when that drift check fails it prints fresh constants to paste here. +#[cfg(test)] mod verify_only_tests { use super::*;