diff --git a/grovedb-version/src/lib.rs b/grovedb-version/src/lib.rs index ebe1897e4..348150053 100644 --- a/grovedb-version/src/lib.rs +++ b/grovedb-version/src/lib.rs @@ -36,6 +36,25 @@ macro_rules! check_grovedb_v0 { }}; } +#[macro_export] +macro_rules! check_grovedb_v0_or_v1_with_cost { + ($method:expr, $version:expr) => {{ + const EXPECTED_VERSION_V0: u16 = 0; + const EXPECTED_VERSION_V1: u16 = 1; + if $version != EXPECTED_VERSION_V0 && $version != EXPECTED_VERSION_V1 { + return grovedb_costs::CostsExt::wrap_with_cost( + Err($crate::error::GroveVersionError::UnknownVersionMismatch { + method: $method.to_string(), + known_versions: vec![EXPECTED_VERSION_V0, EXPECTED_VERSION_V1], + received: $version, + } + .into()), + Default::default(), + ); + } + }}; +} + #[macro_export] macro_rules! check_grovedb_v0_or_v1 { ($method:expr, $version:expr) => {{ diff --git a/grovedb-version/src/tests.rs b/grovedb-version/src/tests.rs index bc12bb641..bb042e5b1 100644 --- a/grovedb-version/src/tests.rs +++ b/grovedb-version/src/tests.rs @@ -3,6 +3,7 @@ use crate::version::grovedb_versions::*; use crate::version::merk_versions::*; use crate::version::v1::GROVE_V1; use crate::version::v2::GROVE_V2; +use crate::version::v3::GROVE_V3; use crate::version::{GroveVersion, GROVE_VERSIONS}; use crate::{TryFromVersioned, TryIntoVersioned}; @@ -63,14 +64,14 @@ fn grove_version_first_returns_v1() { } #[test] -fn grove_version_latest_returns_v2() { +fn grove_version_latest_returns_v3() { let latest = GroveVersion::latest(); - assert_eq!(latest.protocol_version, GROVE_V2.protocol_version); + assert_eq!(latest.protocol_version, GROVE_V3.protocol_version); } #[test] fn grove_versions_count() { - assert_eq!(GROVE_VERSIONS.len(), 2); + assert_eq!(GROVE_VERSIONS.len(), 3); } #[test] diff --git a/grovedb-version/src/version/grovedb_versions.rs b/grovedb-version/src/version/grovedb_versions.rs index 71fe2474b..6f082b377 100644 --- a/grovedb-version/src/version/grovedb_versions.rs +++ b/grovedb-version/src/version/grovedb_versions.rs @@ -91,6 +91,11 @@ pub struct GroveDBOperationsGetVersions { pub struct GroveDBOperationsProofVersions { pub prove_query: FeatureVersion, pub prove_query_many: FeatureVersion, + pub prove_query_non_serialized: FeatureVersion, + pub prove_trunk_chunk: FeatureVersion, + pub prove_trunk_chunk_non_serialized: FeatureVersion, + pub prove_branch_chunk: FeatureVersion, + pub prove_branch_chunk_non_serialized: FeatureVersion, pub verify_query_with_options: FeatureVersion, pub verify_query_raw: FeatureVersion, pub verify_layer_proof: FeatureVersion, diff --git a/grovedb-version/src/version/mod.rs b/grovedb-version/src/version/mod.rs index d795176c1..6f1b145dc 100644 --- a/grovedb-version/src/version/mod.rs +++ b/grovedb-version/src/version/mod.rs @@ -2,9 +2,11 @@ pub mod grovedb_versions; pub mod merk_versions; pub mod v1; pub mod v2; +pub mod v3; pub use versioned_feature_core::*; +use crate::version::v3::GROVE_V3; use crate::version::{ grovedb_versions::GroveDBVersions, merk_versions::MerkVersions, v1::GROVE_V1, v2::GROVE_V2, }; @@ -30,4 +32,4 @@ impl GroveVersion { } } -pub const GROVE_VERSIONS: &[GroveVersion] = &[GROVE_V1, GROVE_V2]; +pub const GROVE_VERSIONS: &[GroveVersion] = &[GROVE_V1, GROVE_V2, GROVE_V3]; diff --git a/grovedb-version/src/version/v1.rs b/grovedb-version/src/version/v1.rs index fb580aa06..90ebfc5fd 100644 --- a/grovedb-version/src/version/v1.rs +++ b/grovedb-version/src/version/v1.rs @@ -143,6 +143,11 @@ pub const GROVE_V1: GroveVersion = GroveVersion { proof: GroveDBOperationsProofVersions { prove_query: 0, prove_query_many: 0, + prove_query_non_serialized: 0, + prove_trunk_chunk: 0, + prove_trunk_chunk_non_serialized: 0, + prove_branch_chunk: 0, + prove_branch_chunk_non_serialized: 0, verify_query_with_options: 0, verify_query_raw: 0, verify_layer_proof: 0, diff --git a/grovedb-version/src/version/v2.rs b/grovedb-version/src/version/v2.rs index d8b45290b..c42c21ab1 100644 --- a/grovedb-version/src/version/v2.rs +++ b/grovedb-version/src/version/v2.rs @@ -143,6 +143,11 @@ pub const GROVE_V2: GroveVersion = GroveVersion { proof: GroveDBOperationsProofVersions { prove_query: 0, prove_query_many: 0, + prove_query_non_serialized: 0, + prove_trunk_chunk: 0, + prove_trunk_chunk_non_serialized: 0, + prove_branch_chunk: 0, + prove_branch_chunk_non_serialized: 0, verify_query_with_options: 0, verify_query_raw: 0, verify_layer_proof: 0, diff --git a/grovedb-version/src/version/v3.rs b/grovedb-version/src/version/v3.rs new file mode 100644 index 000000000..d0c0d7777 --- /dev/null +++ b/grovedb-version/src/version/v3.rs @@ -0,0 +1,214 @@ +use crate::version::grovedb_versions::GroveDBAggregateSumPathQueryMethodVersions; +use crate::version::{ + grovedb_versions::{ + GroveDBApplyBatchVersions, GroveDBElementMethodVersions, + GroveDBOperationsAverageCaseVersions, GroveDBOperationsDeleteUpTreeVersions, + GroveDBOperationsDeleteVersions, GroveDBOperationsGetVersions, + GroveDBOperationsInsertVersions, GroveDBOperationsProofVersions, + GroveDBOperationsQueryVersions, GroveDBOperationsVersions, + GroveDBOperationsWorstCaseVersions, GroveDBPathQueryMethodVersions, GroveDBQueryLimits, + GroveDBReplicationVersions, GroveDBVersions, + }, + merk_versions::{MerkAverageCaseCostsVersions, MerkVersions}, + GroveVersion, +}; + +pub const GROVE_V3: GroveVersion = GroveVersion { + protocol_version: 2, + grovedb_versions: GroveDBVersions { + apply_batch: GroveDBApplyBatchVersions { + apply_batch_structure: 0, + apply_body: 0, + continue_partial_apply_body: 0, + apply_operations_without_batching: 0, + apply_batch: 0, + apply_partial_batch: 0, + open_batch_transactional_merk_at_path: 0, + open_batch_merk_at_path: 0, + apply_batch_with_element_flags_update: 0, + apply_partial_batch_with_element_flags_update: 0, + estimated_case_operations_for_batch: 0, + }, + element: GroveDBElementMethodVersions { + delete: 0, + delete_with_sectioned_removal_bytes: 0, + delete_into_batch_operations: 0, + element_at_key_already_exists: 0, + get: 0, + get_optional: 0, + get_from_storage: 0, + get_optional_from_storage: 1, + get_with_absolute_refs: 0, + get_value_hash: 0, + get_specialized_cost: 0, + value_defined_cost: 0, + value_defined_cost_for_serialized_value: 0, + specialized_costs_for_key_value: 0, + required_item_space: 0, + insert: 0, + insert_into_batch_operations: 0, + insert_if_not_exists: 0, + insert_if_not_exists_into_batch_operations: 0, + insert_if_changed_value: 0, + insert_if_changed_value_into_batch_operations: 0, + insert_reference: 0, + insert_reference_into_batch_operations: 0, + insert_subtree: 0, + insert_subtree_into_batch_operations: 0, + get_query: 0, + get_aggregate_sum_query: 0, + get_query_values: 0, + get_query_apply_function: 0, + get_path_query: 0, + get_sized_query: 0, + get_aggregate_sum_query_apply_function: 0, + path_query_push: 0, + aggregate_sum_path_query_push: 0, + query_item: 0, + basic_push: 0, + basic_aggregate_sum_push: 0, + serialize: 0, + serialized_size: 0, + deserialize: 0, + get_with_value_hash: 0, + insert_reference_if_changed_value: 0, + aggregate_sum_query_item: 0, + }, + operations: GroveDBOperationsVersions { + get: GroveDBOperationsGetVersions { + get: 0, + get_caching_optional: 0, + follow_reference: 0, + get_raw: 0, + get_raw_caching_optional: 0, + get_raw_optional: 0, + get_raw_optional_caching_optional: 0, + has_raw: 0, + check_subtree_exists_invalid_path: 0, + average_case_for_has_raw: 0, + average_case_for_has_raw_tree: 0, + average_case_for_get_raw: 0, + average_case_for_get: 0, + average_case_for_get_tree: 0, + worst_case_for_has_raw: 0, + worst_case_for_get_raw: 0, + worst_case_for_get: 0, + is_empty_tree: 0, + follow_reference_once: 0, + }, + insert: GroveDBOperationsInsertVersions { + insert: 0, + insert_on_transaction: 0, + insert_without_transaction: 0, + add_element_on_transaction: 0, + add_element_without_transaction: 0, + insert_if_not_exists: 0, + insert_if_not_exists_return_existing_element: 0, + insert_if_changed_value: 0, + }, + delete: GroveDBOperationsDeleteVersions { + delete: 0, + clear_subtree: 0, + delete_with_sectional_storage_function: 0, + delete_if_empty_tree: 0, + delete_if_empty_tree_with_sectional_storage_function: 0, + delete_operation_for_delete_internal: 0, + delete_internal_on_transaction: 0, + delete_internal_without_transaction: 0, + average_case_delete_operation_for_delete: 0, + worst_case_delete_operation_for_delete: 0, + }, + delete_up_tree: GroveDBOperationsDeleteUpTreeVersions { + delete_up_tree_while_empty: 0, + delete_up_tree_while_empty_with_sectional_storage: 0, + delete_operations_for_delete_up_tree_while_empty: 0, + add_delete_operations_for_delete_up_tree_while_empty: 0, + average_case_delete_operations_for_delete_up_tree_while_empty: 0, + worst_case_delete_operations_for_delete_up_tree_while_empty: 0, + }, + query: GroveDBOperationsQueryVersions { + query_encoded_many: 0, + query_many_raw: 0, + get_proved_path_query: 0, + query: 0, + query_item_value: 0, + query_item_value_or_sum: 0, + query_aggregate_sums: 0, + query_sums: 0, + query_raw: 0, + query_keys_optional: 0, + query_raw_keys_optional: 0, + follow_element: 0, + }, + proof: GroveDBOperationsProofVersions { + prove_query: 0, + prove_query_many: 0, + prove_query_non_serialized: 1, // v1 supports MmrTree/BulkAppendTree proof generation + prove_trunk_chunk: 0, + prove_trunk_chunk_non_serialized: 0, + prove_branch_chunk: 0, + prove_branch_chunk_non_serialized: 0, + verify_query_with_options: 0, + verify_query_raw: 0, + verify_layer_proof: 0, + verify_query: 0, + verify_subset_query: 0, + verify_query_with_absence_proof: 0, + verify_subset_query_with_absence_proof: 0, + verify_query_with_chained_path_queries: 0, + verify_query_get_parent_tree_info_with_options: 0, + }, + average_case: GroveDBOperationsAverageCaseVersions { + add_average_case_get_merk_at_path: 0, + average_case_merk_replace_tree: 1, + average_case_merk_insert_tree: 0, + average_case_merk_delete_tree: 0, + average_case_merk_insert_element: 0, + average_case_merk_replace_element: 0, + average_case_merk_patch_element: 0, + average_case_merk_delete_element: 0, + add_average_case_has_raw_cost: 0, + add_average_case_has_raw_tree_cost: 0, + add_average_case_get_raw_cost: 0, + add_average_case_get_raw_tree_cost: 0, + add_average_case_get_cost: 0, + }, + worst_case: GroveDBOperationsWorstCaseVersions { + add_worst_case_get_merk_at_path: 0, + worst_case_merk_replace_tree: 0, + worst_case_merk_insert_tree: 0, + worst_case_merk_delete_tree: 0, + worst_case_merk_insert_element: 0, + worst_case_merk_replace_element: 0, + worst_case_merk_patch_element: 0, + worst_case_merk_delete_element: 0, + add_worst_case_has_raw_cost: 0, + add_worst_case_get_raw_tree_cost: 0, + add_worst_case_get_raw_cost: 0, + add_worst_case_get_cost: 0, + }, + }, + aggregate_sum_path_query_methods: GroveDBAggregateSumPathQueryMethodVersions { merge: 0 }, + path_query_methods: GroveDBPathQueryMethodVersions { + terminal_keys: 0, + merge: 0, + query_items_at_path: 0, + should_add_parent_tree_at_path: 0, + }, + replication: GroveDBReplicationVersions { + get_subtrees_metadata: 0, + fetch_chunk: 0, + start_snapshot_syncing: 0, + apply_chunk: 0, + }, + query_limits: GroveDBQueryLimits { + max_aggregate_sum_query_elements_scanned: 1024, + }, + }, + merk_versions: MerkVersions { + average_case_costs: MerkAverageCaseCostsVersions { + add_average_case_merk_propagate: 1, + sum_tree_estimated_size: 1, + }, + }, +}; diff --git a/grovedb/src/operations/proof/generate.rs b/grovedb/src/operations/proof/generate.rs index 7c0b5c649..abfcbc5db 100644 --- a/grovedb/src/operations/proof/generate.rs +++ b/grovedb/src/operations/proof/generate.rs @@ -62,10 +62,12 @@ impl GroveDb { } } - /// Generate a minimalistic proof for a given path query - /// doesn't allow for subset verification + /// Generate a minimalistic proof for a given path query. + /// Doesn't allow for subset verification. /// Proofs generated with this can only be verified by the path query used /// to generate them. + /// + /// Version dispatch happens in `prove_query_non_serialized`. pub fn prove_query( &self, path_query: &PathQuery, @@ -96,32 +98,36 @@ impl GroveDb { Ok(encoded_proof).wrap_with_cost(cost) } - /// Generates a V1 proof (supports MmrTree/BulkAppendTree subqueries) and - /// returns serialized bytes. - pub fn prove_query_v1( + /// Generates a proof and does not serialize the result. + /// + /// Dispatches to v0 or v1 based on the version. + pub fn prove_query_non_serialized( &self, path_query: &PathQuery, prove_options: Option, grove_version: &GroveVersion, - ) -> CostResult, Error> { - let mut cost = OperationCost::default(); - let proof = cost_return_on_error!( - &mut cost, - self.prove_query_v1_non_serialized(path_query, prove_options, grove_version) - ); - let config = bincode::config::standard() - .with_big_endian() - .with_no_limit(); - let encoded_proof = cost_return_on_error_no_add!( - cost, - bincode::encode_to_vec(proof, config) - .map_err(|e| Error::CorruptedData(format!("unable to encode V1 proof {}", e))) - ); - Ok(encoded_proof).wrap_with_cost(cost) + ) -> CostResult { + match grove_version + .grovedb_versions + .operations + .proof + .prove_query_non_serialized + { + 0 => self.prove_query_non_serialized_v0(path_query, prove_options, grove_version), + 1 => self.prove_query_non_serialized_v1(path_query, prove_options, grove_version), + version => Err(Error::VersionError( + grovedb_version::error::GroveVersionError::UnknownVersionMismatch { + method: "prove_query_non_serialized".to_string(), + known_versions: vec![0, 1], + received: version, + }, + )) + .wrap_with_cost(OperationCost::default()), + } } - /// Generates a proof and does not serialize the result - pub fn prove_query_non_serialized( + /// V0: Generates a Merk-only proof without serialization. + pub(crate) fn prove_query_non_serialized_v0( &self, path_query: &PathQuery, prove_options: Option, @@ -585,6 +591,14 @@ impl GroveDb { query: &PathTrunkChunkQuery, grove_version: &GroveVersion, ) -> CostResult, Error> { + check_grovedb_v0_with_cost!( + "prove_trunk_chunk", + grove_version + .grovedb_versions + .operations + .proof + .prove_trunk_chunk + ); let mut cost = OperationCost::default(); let proof = cost_return_on_error!( @@ -614,6 +628,14 @@ impl GroveDb { query: &PathTrunkChunkQuery, grove_version: &GroveVersion, ) -> CostResult { + check_grovedb_v0_with_cost!( + "prove_trunk_chunk_non_serialized", + grove_version + .grovedb_versions + .operations + .proof + .prove_trunk_chunk_non_serialized + ); let mut cost = OperationCost::default(); let tx = self.start_transaction(); @@ -706,6 +728,14 @@ impl GroveDb { query: &crate::query::PathBranchChunkQuery, grove_version: &GroveVersion, ) -> CostResult, Error> { + check_grovedb_v0_with_cost!( + "prove_branch_chunk", + grove_version + .grovedb_versions + .operations + .proof + .prove_branch_chunk + ); let mut cost = OperationCost::default(); let branch_result = cost_return_on_error!( @@ -730,6 +760,14 @@ impl GroveDb { query: &crate::query::PathBranchChunkQuery, grove_version: &GroveVersion, ) -> CostResult { + check_grovedb_v0_with_cost!( + "prove_branch_chunk_non_serialized", + grove_version + .grovedb_versions + .operations + .proof + .prove_branch_chunk_non_serialized + ); let mut cost = OperationCost::default(); let tx = self.start_transaction(); @@ -760,9 +798,8 @@ impl GroveDb { // ── V1 Proof Generation (MmrTree / BulkAppendTree support) ────────── - /// Generate a V1 proof for a query that may touch MmrTree or - /// BulkAppendTree elements. - pub fn prove_query_v1_non_serialized( + /// V1: Generates a proof that supports MmrTree / BulkAppendTree elements. + pub(crate) fn prove_query_non_serialized_v1( &self, path_query: &PathQuery, prove_options: Option, diff --git a/grovedb/src/tests/commitment_tree_tests.rs b/grovedb/src/tests/commitment_tree_tests.rs index 5e8cc6f35..1260d1e5d 100644 --- a/grovedb/src/tests/commitment_tree_tests.rs +++ b/grovedb/src/tests/commitment_tree_tests.rs @@ -1516,7 +1516,7 @@ fn test_commitment_tree_prove_query_v1_empty() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof for empty commitment tree"); @@ -1615,7 +1615,7 @@ fn test_commitment_tree_prove_query_v1_buffer_only() { // Generate V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof for commitment tree"); @@ -1737,7 +1737,7 @@ fn test_commitment_tree_prove_query_v1_with_chunks() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof with chunks"); @@ -1852,7 +1852,7 @@ fn test_commitment_tree_prove_query_v1_partial_range() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof for partial range"); diff --git a/grovedb/src/tests/dense_tree_tests.rs b/grovedb/src/tests/dense_tree_tests.rs index 39bb62f2a..2aeb905a8 100644 --- a/grovedb/src/tests/dense_tree_tests.rs +++ b/grovedb/src/tests/dense_tree_tests.rs @@ -1158,7 +1158,7 @@ fn test_dense_tree_v1_proof_range_query() { // Generate V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof for dense tree range"); @@ -1265,7 +1265,7 @@ fn test_dense_tree_v1_proof_single_position() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -1353,7 +1353,7 @@ fn test_dense_tree_v1_proof_multiple_disjoint_positions() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -1463,7 +1463,7 @@ fn test_dense_tree_v1_proof_nested_in_tree() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof for nested dense tree"); @@ -1551,7 +1551,7 @@ fn test_dense_tree_v1_proof_with_limit() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof with limit"); diff --git a/grovedb/src/tests/mmr_tree_tests.rs b/grovedb/src/tests/mmr_tree_tests.rs index 28d670d1a..57369530f 100644 --- a/grovedb/src/tests/mmr_tree_tests.rs +++ b/grovedb/src/tests/mmr_tree_tests.rs @@ -1622,7 +1622,7 @@ fn test_mmr_tree_v1_proof_empty() { // Proving an empty MmrTree should succeed with an empty result set. let proof = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("prove_query_v1 should succeed for empty MmrTree"); diff --git a/grovedb/src/tests/mod.rs b/grovedb/src/tests/mod.rs index 76eb4b881..98fda7421 100644 --- a/grovedb/src/tests/mod.rs +++ b/grovedb/src/tests/mod.rs @@ -49,7 +49,7 @@ use std::{ option::Option::None, }; -use grovedb_version::version::GroveVersion; +use grovedb_version::version::{v2::GROVE_V2, GroveVersion}; use grovedb_visualize::{Drawer, Visualize}; use tempfile::TempDir; @@ -1872,8 +1872,8 @@ mod general_tests { } #[test] - fn test_path_query_proofs_without_subquery_with_reference() { - let grove_version = GroveVersion::latest(); + fn test_path_query_proofs_without_subquery_with_reference_for_version_2() { + let grove_version = &GROVE_V2; // Tree Structure // root // test_leaf @@ -2059,7 +2059,7 @@ mod general_tests { } #[test] - fn test_path_query_proofs_without_subquery() { + fn test_path_query_proofs_without_subquery_with_reference() { let grove_version = GroveVersion::latest(); // Tree Structure // root @@ -2071,6 +2071,181 @@ mod general_tests { // another_test_leaf // innertree2 // k3,v3 + // k4, reference to k1 in innertree + // k5, reference to k4 in innertree3 + // innertree3 + // k4,v4 + + // Insert elements into grovedb instance + let temp_db = make_test_grovedb(grove_version); + // Insert level 1 nodes + temp_db + .insert( + [TEST_LEAF].as_ref(), + b"innertree", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF].as_ref(), + b"innertree2", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF].as_ref(), + b"innertree3", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + // Insert level 2 nodes + temp_db + .insert( + [TEST_LEAF, b"innertree"].as_ref(), + b"key1", + Element::new_item(b"value1".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [TEST_LEAF, b"innertree"].as_ref(), + b"key2", + Element::new_item(b"value2".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [TEST_LEAF, b"innertree"].as_ref(), + b"key3", + Element::new_item(b"value3".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF, b"innertree2"].as_ref(), + b"key3", + Element::new_item(b"value3".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF, b"innertree2"].as_ref(), + b"key4", + Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ + TEST_LEAF.to_vec(), + b"innertree".to_vec(), + b"key1".to_vec(), + ])), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF, b"innertree3"].as_ref(), + b"key4", + Element::new_item(b"value4".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF, b"innertree2"].as_ref(), + b"key5", + Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ + ANOTHER_TEST_LEAF.to_vec(), + b"innertree3".to_vec(), + b"key4".to_vec(), + ])), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + + // Single key query + let mut query = Query::new(); + query.insert_range_from(b"key4".to_vec()..); + + let path_query = PathQuery::new_unsized( + vec![ANOTHER_TEST_LEAF.to_vec(), b"innertree2".to_vec()], + query, + ); + + let proof = temp_db + .prove_query(&path_query, None, grove_version) + .unwrap() + .unwrap(); + let (hash, result_set) = + GroveDb::verify_query_raw(proof.as_slice(), &path_query, grove_version) + .expect("should execute proof"); + + assert_eq!( + hash, + temp_db.root_hash(None, grove_version).unwrap().unwrap() + ); + let r1 = Element::new_item(b"value1".to_vec()) + .serialize(grove_version) + .unwrap(); + let r2 = Element::new_item(b"value4".to_vec()) + .serialize(grove_version) + .unwrap(); + + compare_result_tuples( + result_set, + vec![(b"key4".to_vec(), r1), (b"key5".to_vec(), r2)], + ); + } + + #[test] + fn test_path_query_proofs_without_subquery_for_version_2() { + let grove_version = &GROVE_V2; + // Tree Structure + // root + // test_leaf + // innertree + // k1,v1 + // k2,v2 + // k3,v3 + // another_test_leaf + // innertree2 + // k3,v3 // innertree3 // k4,v4 @@ -2258,6 +2433,196 @@ mod general_tests { ); } + #[test] + fn test_path_query_proofs_without_subquery() { + let grove_version = GroveVersion::latest(); + // Tree Structure + // root + // test_leaf + // innertree + // k1,v1 + // k2,v2 + // k3,v3 + // another_test_leaf + // innertree2 + // k3,v3 + // innertree3 + // k4,v4 + + // Insert elements into grovedb instance + let temp_db = make_test_grovedb(grove_version); + // Insert level 1 nodes + temp_db + .insert( + [TEST_LEAF].as_ref(), + b"innertree", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF].as_ref(), + b"innertree2", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF].as_ref(), + b"innertree3", + Element::empty_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + // Insert level 2 nodes + temp_db + .insert( + [TEST_LEAF, b"innertree"].as_ref(), + b"key1", + Element::new_item(b"value1".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [TEST_LEAF, b"innertree"].as_ref(), + b"key2", + Element::new_item(b"value2".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [TEST_LEAF, b"innertree"].as_ref(), + b"key3", + Element::new_item(b"value3".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF, b"innertree2"].as_ref(), + b"key3", + Element::new_item(b"value3".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + temp_db + .insert( + [ANOTHER_TEST_LEAF, b"innertree3"].as_ref(), + b"key4", + Element::new_item(b"value4".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .expect("successful subtree insert"); + + // Single key query + let mut query = Query::new(); + query.insert_key(b"key1".to_vec()); + + let path_query = + PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"innertree".to_vec()], query); + + let proof = temp_db + .prove_query(&path_query, None, grove_version) + .unwrap() + .unwrap(); + let (hash, result_set) = + GroveDb::verify_query_raw(proof.as_slice(), &path_query, grove_version) + .expect("should execute proof"); + + assert_eq!( + hash, + temp_db.root_hash(None, grove_version).unwrap().unwrap() + ); + let r1 = Element::new_item(b"value1".to_vec()) + .serialize(grove_version) + .unwrap(); + compare_result_tuples(result_set, vec![(b"key1".to_vec(), r1)]); + + // Range query + limit + let mut query = Query::new(); + query.insert_range_after(b"key1".to_vec()..); + let path_query = PathQuery::new( + vec![TEST_LEAF.to_vec(), b"innertree".to_vec()], + SizedQuery::new(query, Some(1), None), + ); + + let proof = temp_db + .prove_query(&path_query, None, grove_version) + .unwrap() + .unwrap(); + let (hash, result_set) = + GroveDb::verify_query_raw(proof.as_slice(), &path_query, grove_version) + .expect("should execute proof"); + + assert_eq!( + hash, + temp_db.root_hash(None, grove_version).unwrap().unwrap() + ); + let r1 = Element::new_item(b"value2".to_vec()) + .serialize(grove_version) + .unwrap(); + compare_result_tuples(result_set, vec![(b"key2".to_vec(), r1)]); + + // Range query + direction + limit + let mut query = Query::new_with_direction(false); + query.insert_all(); + let path_query = PathQuery::new( + vec![TEST_LEAF.to_vec(), b"innertree".to_vec()], + SizedQuery::new(query, Some(2), None), + ); + + let proof = temp_db + .prove_query(&path_query, None, grove_version) + .unwrap() + .unwrap(); + let (hash, result_set) = + GroveDb::verify_query_raw(proof.as_slice(), &path_query, grove_version) + .expect("should execute proof"); + + assert_eq!( + hash, + temp_db.root_hash(None, grove_version).unwrap().unwrap() + ); + let r1 = Element::new_item(b"value3".to_vec()) + .serialize(grove_version) + .unwrap(); + let r2 = Element::new_item(b"value2".to_vec()) + .serialize(grove_version) + .unwrap(); + compare_result_tuples( + result_set, + vec![(b"key3".to_vec(), r1), (b"key2".to_vec(), r2)], + ); + } + #[test] fn test_path_query_proofs_with_default_subquery() { let grove_version = GroveVersion::latest(); diff --git a/grovedb/src/tests/proof_coverage_tests.rs b/grovedb/src/tests/proof_coverage_tests.rs index d2635806c..a174854cd 100644 --- a/grovedb/src/tests/proof_coverage_tests.rs +++ b/grovedb/src/tests/proof_coverage_tests.rs @@ -14,7 +14,7 @@ mod tests { query::{QueryItem, SubqueryBranch, VerifyOptions}, Query, }; - use grovedb_version::version::GroveVersion; + use grovedb_version::version::{v2::GROVE_V2, GroveVersion}; use indexmap::IndexMap; use crate::{ @@ -574,7 +574,7 @@ mod tests { #[test] fn grovedb_proof_display_v0() { // Exercise Display for GroveDBProofV0 and GroveDBProof - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_test_grovedb(grove_version); db.insert( @@ -644,7 +644,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"tree1".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1"); @@ -1101,7 +1101,7 @@ mod tests { // Generate V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should generate V1 proof"); @@ -1181,7 +1181,7 @@ mod tests { // V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should generate V1 proof with add_parent_tree_on_subquery"); @@ -1245,7 +1245,7 @@ mod tests { ); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1"); @@ -1317,7 +1317,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"t".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1"); @@ -1384,7 +1384,7 @@ mod tests { SizedQuery::new(query, Some(0), None), ); - let result = db.prove_query_v1(&path_query, None, grove_version).unwrap(); + let result = db.prove_query(&path_query, None, grove_version).unwrap(); assert!(result.is_err(), "prove_query_v1 with limit 0 should error"); } @@ -1400,7 +1400,7 @@ mod tests { SizedQuery::new(query, None, Some(3)), ); - let result = db.prove_query_v1(&path_query, None, grove_version).unwrap(); + let result = db.prove_query(&path_query, None, grove_version).unwrap(); assert!( result.is_err(), "prove_query_v1 with non-zero offset should error" @@ -1410,7 +1410,7 @@ mod tests { #[test] fn prove_v0_on_mmr_tree_errors() { // V0 proofs should error when encountering MmrTree with subquery - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_empty_grovedb(); db.insert( @@ -1729,7 +1729,7 @@ mod tests { PathQuery::new_unsized(vec![b"deep_leaf".to_vec(), b"deep_node_2".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 multi-subtree"); @@ -1855,7 +1855,7 @@ mod tests { ); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 rtl"); @@ -1917,7 +1917,7 @@ mod tests { #[test] fn prove_v1_non_serialized() { - // Exercise prove_query_v1_non_serialized directly + // Exercise prove_query_non_serialized_v1 directly let grove_version = GroveVersion::latest(); let db = make_empty_grovedb(); @@ -1948,7 +1948,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"t".to_vec()], query); let grovedb_proof = db - .prove_query_v1_non_serialized(&path_query, None, grove_version) + .prove_query_non_serialized_v1(&path_query, None, grove_version) .unwrap() .expect("should prove v1 non-serialized"); @@ -2064,7 +2064,7 @@ mod tests { }; let proof_bytes = db - .prove_query_v1(&path_query, Some(options), grove_version) + .prove_query(&path_query, Some(options), grove_version) .unwrap() .expect("should prove v1 with decrease_limit=false"); @@ -2118,7 +2118,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 for sum tree leaf"); @@ -2201,7 +2201,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 mixed types"); @@ -2330,7 +2330,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 with reference"); @@ -2434,7 +2434,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 sum tree with subquery"); @@ -2514,7 +2514,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 count tree with subquery"); @@ -2594,7 +2594,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 count sum tree with subquery"); @@ -2667,7 +2667,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 big sum tree with subquery"); @@ -2755,7 +2755,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 with add_parent_tree on count tree"); @@ -2835,7 +2835,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 with add_parent_tree on big sum tree"); @@ -2918,7 +2918,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 mmr tree with subquery"); @@ -2999,7 +2999,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 bulk append tree with subquery"); @@ -3079,7 +3079,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 dense tree with subquery"); @@ -3320,7 +3320,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 conditional subqueries"); @@ -3414,7 +3414,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 with subquery_path"); @@ -3491,7 +3491,7 @@ mod tests { ); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 with limit across subtrees"); @@ -3578,7 +3578,7 @@ mod tests { ); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 rtl with subquery"); @@ -3655,7 +3655,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec(), b"sums".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 for parent tree info"); @@ -3731,7 +3731,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec(), b"counts".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove"); @@ -4022,7 +4022,7 @@ mod tests { ); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 range with limit"); @@ -4110,7 +4110,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 empty tree no subquery"); @@ -4207,7 +4207,7 @@ mod tests { ); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 absence with subquery"); @@ -4239,7 +4239,7 @@ mod tests { #[test] fn prove_v0_on_bulk_append_tree_errors() { // V0 proofs should error when encountering BulkAppendTree with subquery - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_empty_grovedb(); db.insert( @@ -4288,7 +4288,7 @@ mod tests { #[test] fn prove_v0_on_dense_tree_errors() { // V0 proofs should error when encountering DenseTree with subquery - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_empty_grovedb(); db.insert( @@ -4379,7 +4379,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 mmr tree no subquery"); @@ -4447,7 +4447,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 bat no subquery"); @@ -4540,7 +4540,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 mixed subtrees"); @@ -4710,7 +4710,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 ref in subtree"); @@ -4902,7 +4902,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"root".to_vec()], outer); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1 with item_with_sum_item"); @@ -4999,7 +4999,7 @@ mod tests { let path_query = PathQuery::new_unsized(vec![b"tree".to_vec()], query); let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("should prove v1"); @@ -5056,7 +5056,7 @@ mod tests { let full_pq = PathQuery::new_unsized(vec![b"tree".to_vec()], full_query); let proof_bytes = db - .prove_query_v1(&full_pq, None, grove_version) + .prove_query(&full_pq, None, grove_version) .unwrap() .expect("should prove v1 full"); @@ -5194,7 +5194,7 @@ mod tests { grove_version: &GroveVersion, ) -> usize { let proof_bytes = db - .prove_query_v1(path_query, None, grove_version) + .prove_query(path_query, None, grove_version) .unwrap() .expect("should prove"); @@ -5373,7 +5373,7 @@ mod tests { grove_version: &GroveVersion, ) -> usize { let proof_bytes = db - .prove_query_v1(path_query, None, grove_version) + .prove_query(path_query, None, grove_version) .unwrap() .expect("should generate v1 proof"); @@ -5885,7 +5885,7 @@ mod tests { }; let proof_bytes = db - .prove_query_v1(&path_query, Some(options), grove_version) + .prove_query(&path_query, Some(options), grove_version) .unwrap() .expect("should prove v1 with limit=1 and empty subtrees"); diff --git a/grovedb/src/tests/provable_count_sum_tree_tests.rs b/grovedb/src/tests/provable_count_sum_tree_tests.rs index eb0f557e2..da1735d95 100644 --- a/grovedb/src/tests/provable_count_sum_tree_tests.rs +++ b/grovedb/src/tests/provable_count_sum_tree_tests.rs @@ -16,11 +16,11 @@ mod tests { TreeFeatureType, }; use grovedb_storage::StorageBatch; - use grovedb_version::version::GroveVersion; + use grovedb_version::version::{v2::GROVE_V2, GroveVersion}; use crate::{ batch::QualifiedGroveDbOp, - operations::proof::GroveDBProof, + operations::proof::{GroveDBProof, ProofBytes}, query::SizedQuery, tests::{make_test_grovedb, TEST_LEAF}, Element, GroveDb, PathQuery, @@ -1429,11 +1429,11 @@ mod tests { } #[test] - fn test_provable_count_sum_tree_avl_rotations() { + fn test_provable_count_sum_tree_avl_rotations_for_version_2() { // This test inserts items in a specific order to trigger AVL rotations // and verifies that aggregate data (count and sum) remains correct after // rebalancing. It also verifies each proof node has the correct count. - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_test_grovedb(grove_version); db.insert( @@ -1600,10 +1600,10 @@ mod tests { } #[test] - fn test_provable_count_sum_tree_many_items_rotation_stress() { + fn test_provable_count_sum_tree_many_items_rotation_stress_for_version_2() { // Stress test: insert many items to trigger multiple rotations // Also verifies proof node counts are correct - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_test_grovedb(grove_version); db.insert( @@ -2131,11 +2131,11 @@ mod tests { } #[test] - fn test_provable_count_sum_tree_absence_proof_uses_kvdigest_count() { + fn test_provable_count_sum_tree_absence_proof_uses_kvdigest_count_for_version_2() { // This test verifies that absence proofs in ProvableCountSumTree use // KVDigestCount nodes (not just KVDigest) so that the count information // is available for hash verification. - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_test_grovedb(grove_version); // Create a ProvableCountSumTree with multiple items @@ -2274,4 +2274,413 @@ mod tests { _ => panic!("expected V0 proof"), } } + + #[test] + fn test_provable_count_sum_tree_avl_rotations() { + // V1 version: same test as test_provable_count_sum_tree_avl_rotations_for_version_2 + // but using GroveVersion::latest() which produces V1 proofs. + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + + db.insert( + [TEST_LEAF].as_ref(), + b"rotation_tree", + Element::new_provable_count_sum_tree(None), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + let items = [ + (b"a".to_vec(), 10i64), + (b"b".to_vec(), 20), + (b"c".to_vec(), 30), + (b"d".to_vec(), 40), + (b"e".to_vec(), 50), + (b"f".to_vec(), 60), + (b"g".to_vec(), 70), + ]; + + let mut expected_count = 0u64; + let mut expected_sum = 0i64; + + for (key, sum_value) in &items { + db.insert( + [TEST_LEAF, b"rotation_tree"].as_ref(), + key, + Element::new_sum_item(*sum_value), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + expected_count += 1; + expected_sum += sum_value; + + let batch = StorageBatch::new(); + let transaction = db.start_transaction(); + + let merk = db + .open_transactional_merk_at_path( + [TEST_LEAF, b"rotation_tree"].as_ref().into(), + &transaction, + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + + let aggregate = merk.aggregate_data().expect("should get aggregate"); + assert_eq!( + aggregate, + AggregateData::ProvableCountAndSum(expected_count, expected_sum), + "Aggregate mismatch after inserting {:?}", + String::from_utf8_lossy(key) + ); + } + + assert_eq!(expected_count, 7); + assert_eq!(expected_sum, 280); + + let mut query = Query::new(); + query.insert_all(); + let path_query = + PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"rotation_tree".to_vec()], query); + + let grovedb_proof = db + .prove_query_non_serialized(&path_query, None, grove_version) + .unwrap() + .expect("should generate proof"); + + let GroveDBProof::V1(proof_v1) = &grovedb_proof else { + panic!("expected V1 proof"); + }; + let root_layer = &proof_v1.root_layer; + + let test_leaf_layer = root_layer + .lower_layers + .get(TEST_LEAF) + .expect("should have TEST_LEAF layer"); + + let rotation_tree_layer = test_leaf_layer + .lower_layers + .get(b"rotation_tree".as_slice()) + .expect("should have rotation_tree layer"); + + let merk_proof_bytes = match &rotation_tree_layer.merk_proof { + ProofBytes::Merk(bytes) => bytes.as_slice(), + other => panic!( + "expected Merk proof bytes, got {:?}", + std::mem::discriminant(other) + ), + }; + + let proof_tree = execute_merk_proof(merk_proof_bytes).expect("should execute proof"); + + let tree_nodes = collect_tree_node_counts(&proof_tree); + + assert_eq!( + tree_nodes.len(), + 7, + "Should have 7 nodes with counts in proof tree" + ); + + let root_count = get_node_count(&proof_tree.node).expect("Root should have count data"); + assert_eq!( + root_count, 7, + "Root node should have count=7, got count={}", + root_count + ); + + for (key, count) in &tree_nodes { + assert!( + *count >= 1, + "Node {:?} should have count >= 1, got count={}", + String::from_utf8_lossy(key), + count + ); + } + + fn verify_tree_counts(tree: &grovedb_merk::proofs::tree::Tree) -> u64 { + let node_count = get_node_count(&tree.node).unwrap_or(0); + let left_count = tree + .left + .as_ref() + .map(|c| verify_tree_counts(&c.tree)) + .unwrap_or(0); + let right_count = tree + .right + .as_ref() + .map(|c| verify_tree_counts(&c.tree)) + .unwrap_or(0); + + let expected_count = 1 + left_count + right_count; + assert_eq!( + node_count, expected_count, + "Node count {} should equal 1 + {} + {} = {}", + node_count, left_count, right_count, expected_count + ); + + node_count + } + + let total_verified = verify_tree_counts(&proof_tree); + assert_eq!(total_verified, 7, "Total tree count should be 7"); + } + + #[test] + fn test_provable_count_sum_tree_many_items_rotation_stress() { + // V1 version: same stress test but using GroveVersion::latest() + // which produces V1 proofs. + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + + db.insert( + [TEST_LEAF].as_ref(), + b"stress_tree", + Element::new_provable_count_sum_tree(None), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + let item_count = 50u64; + let mut expected_sum = 0i64; + + for i in 0..item_count { + let key = format!("key_{:03}", i); + let sum_value = (i as i64) * 10 - 250; + + db.insert( + [TEST_LEAF, b"stress_tree"].as_ref(), + key.as_bytes(), + Element::new_sum_item(sum_value), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + + expected_sum += sum_value; + } + + let batch = StorageBatch::new(); + let transaction = db.start_transaction(); + + let merk = db + .open_transactional_merk_at_path( + [TEST_LEAF, b"stress_tree"].as_ref().into(), + &transaction, + Some(&batch), + grove_version, + ) + .unwrap() + .expect("should open tree"); + + let aggregate = merk.aggregate_data().expect("should get aggregate"); + assert_eq!( + aggregate, + AggregateData::ProvableCountAndSum(item_count, expected_sum) + ); + drop(merk); + drop(transaction); + + let mut query = Query::new(); + query.insert_all(); + let path_query = + PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"stress_tree".to_vec()], query); + + let grovedb_proof = db + .prove_query_non_serialized(&path_query, None, grove_version) + .unwrap() + .expect("should generate proof"); + + let GroveDBProof::V1(proof_v1) = &grovedb_proof else { + panic!("expected V1 proof"); + }; + let root_layer = &proof_v1.root_layer; + + let test_leaf_layer = root_layer + .lower_layers + .get(TEST_LEAF) + .expect("should have TEST_LEAF layer"); + + let stress_tree_layer = test_leaf_layer + .lower_layers + .get(b"stress_tree".as_slice()) + .expect("should have stress_tree layer"); + + let merk_proof_bytes = match &stress_tree_layer.merk_proof { + ProofBytes::Merk(bytes) => bytes.as_slice(), + other => panic!( + "expected Merk proof bytes, got {:?}", + std::mem::discriminant(other) + ), + }; + + let proof_tree = execute_merk_proof(merk_proof_bytes).expect("should execute proof"); + + let tree_nodes = collect_tree_node_counts(&proof_tree); + + assert_eq!( + tree_nodes.len(), + item_count as usize, + "Should have {} nodes with counts in proof tree", + item_count + ); + + let root_count = get_node_count(&proof_tree.node).expect("Root should have count data"); + assert_eq!( + root_count, item_count, + "Root node should have count={}, got count={}", + item_count, root_count + ); + + fn verify_tree_counts(tree: &grovedb_merk::proofs::tree::Tree) -> u64 { + let node_count = get_node_count(&tree.node).unwrap_or(0); + let left_count = tree + .left + .as_ref() + .map(|c| verify_tree_counts(&c.tree)) + .unwrap_or(0); + let right_count = tree + .right + .as_ref() + .map(|c| verify_tree_counts(&c.tree)) + .unwrap_or(0); + + let expected_count = 1 + left_count + right_count; + assert_eq!( + node_count, expected_count, + "Node count {} should equal 1 + {} + {} = {}", + node_count, left_count, right_count, expected_count + ); + + node_count + } + + let total_verified = verify_tree_counts(&proof_tree); + assert_eq!( + total_verified, item_count, + "Total tree count should be {}", + item_count + ); + } + + #[test] + fn test_provable_count_sum_tree_absence_proof_uses_kvdigest_count() { + // V1 version: same absence proof test but using GroveVersion::latest() + // which produces V1 proofs. + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + + db.insert( + [TEST_LEAF].as_ref(), + b"counted_tree", + Element::empty_provable_count_sum_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + for (key, value) in [ + (b"aaa".as_slice(), 10i64), + (b"ccc".as_slice(), 20i64), + (b"eee".as_slice(), 30i64), + ] { + db.insert( + [TEST_LEAF, b"counted_tree"].as_ref(), + key, + Element::new_sum_item(value), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + } + + let mut query = Query::new(); + query.insert_key(b"bbb".to_vec()); + let path_query = + PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"counted_tree".to_vec()], query); + + let proof = db + .prove_query(&path_query, None, grove_version) + .unwrap() + .expect("should generate absence proof"); + + let (root_hash, proved_values) = + GroveDb::verify_query_raw(&proof, &path_query, grove_version) + .expect("should verify absence proof"); + + let actual_root_hash = db + .root_hash(None, grove_version) + .unwrap() + .expect("should get root hash"); + + assert_eq!(root_hash, actual_root_hash, "Root hash should match"); + assert_eq!( + proved_values.len(), + 0, + "Should have no proved values for absence" + ); + + let grove_proof: GroveDBProof = + bincode::decode_from_slice(&proof, bincode::config::standard()) + .expect("should decode proof") + .0; + + fn has_kvdigest_count(ops: &[Op]) -> bool { + ops.iter().any(|op| { + matches!( + op, + Op::Push(Node::KVDigestCount(..)) | Op::PushInverted(Node::KVDigestCount(..)) + ) + }) + } + + match grove_proof { + GroveDBProof::V1(proof_v1) => { + let mut found_kvdigest_count = false; + + fn check_v1_layer_proof( + layer: &crate::operations::proof::LayerProof, + found_kvdigest_count: &mut bool, + ) { + let merk_bytes = match &layer.merk_proof { + ProofBytes::Merk(bytes) => bytes.as_slice(), + _ => return, + }; + let decoder = Decoder::new(merk_bytes); + let ops: Vec = decoder.collect::, _>>().unwrap_or_default(); + + if has_kvdigest_count(&ops) { + *found_kvdigest_count = true; + } + + for lower_layer in layer.lower_layers.values() { + check_v1_layer_proof(lower_layer, found_kvdigest_count); + } + } + + check_v1_layer_proof(&proof_v1.root_layer, &mut found_kvdigest_count); + + assert!( + found_kvdigest_count, + "V1 absence proof should contain KVDigestCount nodes for ProvableCountSumTree" + ); + } + _ => panic!("expected V1 proof"), + } + } } diff --git a/grovedb/src/tests/provable_count_tree_comprehensive_test.rs b/grovedb/src/tests/provable_count_tree_comprehensive_test.rs index d45f07fbd..7b30d728a 100644 --- a/grovedb/src/tests/provable_count_tree_comprehensive_test.rs +++ b/grovedb/src/tests/provable_count_tree_comprehensive_test.rs @@ -3,12 +3,14 @@ #[cfg(test)] mod tests { use grovedb_merk::proofs::{Decoder, Node, Op}; - use grovedb_version::version::GroveVersion; + use grovedb_version::version::{v2::GROVE_V2, GroveVersion}; use crate::{ - batch::QualifiedGroveDbOp, operations::proof::util::ProvedPathKeyValue, - query_result_type::QueryResultType, tests::make_test_grovedb, Element, GroveDb, PathQuery, - Query, SizedQuery, + batch::QualifiedGroveDbOp, + operations::proof::{util::ProvedPathKeyValue, ProofBytes}, + query_result_type::QueryResultType, + tests::make_test_grovedb, + Element, GroveDb, PathQuery, Query, SizedQuery, }; #[test] @@ -84,8 +86,8 @@ mod tests { } #[test] - fn test_provable_count_tree_proof_contains_count_nodes() { - let grove_version = GroveVersion::latest(); + fn test_provable_count_tree_proof_contains_count_nodes_for_version_2() { + let grove_version = &GROVE_V2; let db = make_test_grovedb(grove_version); // Create a single ProvableCountTree @@ -713,4 +715,121 @@ mod tests { assert_eq!(proved_values.len(), 2, "Should have exactly 2 items"); } + + #[test] + fn test_provable_count_tree_proof_contains_count_nodes() { + // V1 version: same test as test_provable_count_tree_proof_contains_count_nodes + // but using GroveVersion::latest() which produces V1 proofs. + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + + db.insert( + &[] as &[&[u8]], + b"counts", + Element::empty_provable_count_tree(), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert tree"); + + for i in 0..3 { + let key = format!("item{}", i).into_bytes(); + let value = format!("value{}", i).into_bytes(); + db.insert( + &[b"counts"], + &key, + Element::new_item(value), + None, + None, + grove_version, + ) + .unwrap() + .expect("should insert item"); + } + + let query = Query::new(); + let path_query = PathQuery::new_unsized(vec![b"counts".to_vec()], query); + + let proof = db + .prove_query(&path_query, None, grove_version) + .unwrap() + .expect("should generate proof"); + + let config = bincode::config::standard() + .with_big_endian() + .with_no_limit(); + let grovedb_proof: crate::operations::proof::GroveDBProof = + bincode::decode_from_slice(&proof, config) + .expect("should deserialize proof") + .0; + + let has_lower_layers = match &grovedb_proof { + crate::operations::proof::GroveDBProof::V1(proof_v1) => { + !proof_v1.root_layer.lower_layers.is_empty() + } + _ => panic!("expected V1 proof"), + }; + + assert!( + has_lower_layers, + "Proof should have lower layers for ProvableCountTree" + ); + + let merk_proof = match &grovedb_proof { + crate::operations::proof::GroveDBProof::V1(proof_v1) => { + let layer = proof_v1 + .root_layer + .lower_layers + .get(b"counts".as_slice()) + .expect("should have counts layer"); + match &layer.merk_proof { + ProofBytes::Merk(bytes) => bytes.clone(), + other => panic!( + "expected Merk proof bytes, got {:?}", + std::mem::discriminant(other) + ), + } + } + _ => panic!("expected V1 proof"), + }; + + let decoder = Decoder::new(&merk_proof); + let mut found_count_node = false; + + for res in decoder { + match res { + Err(e) => panic!("failed to decode proof op: {e}"), + Ok(op) => { + if let Op::Push(node) | Op::PushInverted(node) = op { + match node { + Node::KVCount(k, _, c) => { + eprintln!( + "Found KVCount node: key={}, count={}", + hex::encode(k), + c + ); + found_count_node = true; + break; + } + Node::KVHashCount(_, c) => { + eprintln!("Found KVHashCount node: count={}", c); + found_count_node = true; + break; + } + n => { + eprintln!("Found node: {:?}", n); + } + } + } + } + } + } + + assert!( + found_count_node, + "V1 proof should contain at least one count node" + ); + } } diff --git a/grovedb/src/tests/v1_proof_tests.rs b/grovedb/src/tests/v1_proof_tests.rs index 70d34f044..8270b679d 100644 --- a/grovedb/src/tests/v1_proof_tests.rs +++ b/grovedb/src/tests/v1_proof_tests.rs @@ -7,7 +7,7 @@ use grovedb_merk::proofs::{ query::{QueryItem, SubqueryBranch}, Query, }; -use grovedb_version::version::GroveVersion; +use grovedb_version::version::{v2::GROVE_V2, GroveVersion}; use crate::{ operations::proof::GroveDBProof, @@ -71,7 +71,7 @@ fn test_mmr_tree_v1_proof_single_leaf() { // Generate V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -158,7 +158,7 @@ fn test_mmr_tree_v1_proof_multiple_leaves() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -245,7 +245,7 @@ fn test_mmr_tree_v1_proof_wrong_root_detection() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -320,7 +320,7 @@ fn test_bulk_append_tree_v1_proof_buffer_range() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -405,7 +405,7 @@ fn test_bulk_append_tree_v1_proof_chunk_and_buffer() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -501,7 +501,7 @@ fn test_v1_proof_nested_path_with_mmr() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -581,7 +581,7 @@ fn test_v1_proof_serialization_roundtrip() { // Generate and serialize V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -669,7 +669,7 @@ fn test_dense_tree_v1_proof_serialization_roundtrip() { // 4. Generate V1 proof let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof for dense tree"); @@ -800,7 +800,7 @@ fn test_bulk_append_tree_v1_proof_disjoint_query_succinctness() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -902,7 +902,7 @@ fn test_dense_tree_v1_proof_disjoint_query_succinctness() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -1003,7 +1003,7 @@ fn test_mmr_tree_v1_proof_disjoint_query_succinctness() { }; let proof_bytes = db - .prove_query_v1(&path_query, None, grove_version) + .prove_query(&path_query, None, grove_version) .unwrap() .expect("generate V1 proof"); @@ -1058,7 +1058,7 @@ fn test_mmr_tree_v1_proof_disjoint_query_succinctness() { /// PathQuery whose subquery targets an MmrTree element. #[test] fn test_v0_proof_rejects_mmr_tree_subquery() { - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_empty_grovedb(); // Create parent tree containing an MmrTree child @@ -1138,7 +1138,7 @@ fn test_v0_proof_rejects_mmr_tree_subquery() { /// V0 proofs cannot descend into BulkAppendTree subtrees. #[test] fn test_v0_proof_rejects_bulk_append_tree_subquery() { - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_empty_grovedb(); db.insert( @@ -1216,7 +1216,7 @@ fn test_v0_proof_rejects_bulk_append_tree_subquery() { /// V0 proofs cannot descend into DenseAppendOnlyFixedSizeTree subtrees. #[test] fn test_v0_proof_rejects_dense_tree_subquery() { - let grove_version = GroveVersion::latest(); + let grove_version = &GROVE_V2; let db = make_empty_grovedb(); db.insert(