From fbf64e194d0332351dc66c353d05341584455360 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 21 May 2026 07:13:21 +0700 Subject: [PATCH 1/2] Check size arithmetic in cost helpers --- costs/src/error.rs | 4 + costs/src/lib.rs | 99 ++++++++++++++----- costs/src/storage_cost/key_value_cost.rs | 39 +++++--- costs/src/storage_cost/mod.rs | 6 +- costs/tests/coverage_regression.rs | 39 +++++++- grovedb-element/src/element/helpers.rs | 55 ++++++++--- grovedb-element/src/error.rs | 4 + .../tests/element_constructors_helpers.rs | 25 +++++ grovedb/src/batch/mod.rs | 42 +++++--- merk/src/merk/mod.rs | 16 +-- 10 files changed, 254 insertions(+), 75 deletions(-) diff --git a/costs/src/error.rs b/costs/src/error.rs index 7f94e1aa8..27b8165a9 100644 --- a/costs/src/error.rs +++ b/costs/src/error.rs @@ -31,6 +31,10 @@ use crate::StorageCost; /// An Error coming from costs #[derive(Debug, thiserror::Error)] pub enum Error { + /// Arithmetic overflow + #[error("overflow error: {0}")] + Overflow(&'static str), + /// Storage Cost Value mismatch #[error("storage_cost cost mismatch added: {0} replaced: {1} actual:{actual_total_bytes}", expected.added_bytes, expected.replaced_bytes)] diff --git a/costs/src/lib.rs b/costs/src/lib.rs index 409509e36..1d9787d68 100644 --- a/costs/src/lib.rs +++ b/costs/src/lib.rs @@ -117,6 +117,19 @@ pub struct OperationCost { } impl OperationCost { + fn checked_len_with_required_space(len: u32, context: &'static str) -> Result { + len.checked_add(len.required_space() as u32) + .ok_or(Error::Overflow(context)) + } + + fn checked_add(left: u32, right: u32, context: &'static str) -> Result { + left.checked_add(right).ok_or(Error::Overflow(context)) + } + + fn checked_sub(left: u32, right: u32, context: &'static str) -> Result { + left.checked_sub(right).ok_or(Error::Overflow(context)) + } + /// Is Nothing pub fn is_nothing(&self) -> bool { self == &Self::default() @@ -191,43 +204,63 @@ impl OperationCost { children_sizes: ChildrenSizesWithIsSumTree, storage_cost_info: Option, ) -> Result<(), Error> { - let paid_key_len = key_len + key_len.required_space() as u32; - - let doesnt_need_verification = storage_cost_info - .as_ref() - .map(|key_value_storage_cost| { - if !key_value_storage_cost.needs_value_verification { - Some( - key_value_storage_cost.value_storage_cost.added_bytes - + key_value_storage_cost.value_storage_cost.replaced_bytes, - ) - } else { - None - } - }) - .unwrap_or(None); + let paid_key_len = + Self::checked_len_with_required_space(key_len, "key length required space overflow")?; + + let doesnt_need_verification = match storage_cost_info.as_ref() { + Some(key_value_storage_cost) if !key_value_storage_cost.needs_value_verification => { + Some(Self::checked_add( + key_value_storage_cost.value_storage_cost.added_bytes, + key_value_storage_cost.value_storage_cost.replaced_bytes, + "value storage cost overflow", + )?) + } + _ => None, + }; let final_paid_value_len = if let Some(value_cost_len) = doesnt_need_verification { value_cost_len } else { let mut paid_value_len = value_len; // We need to remove the child sizes if they exist if let Some((in_sum_tree, left_child, right_child)) = children_sizes { - paid_value_len = paid_value_len.saturating_sub(2); // for the child options + paid_value_len = + Self::checked_sub(paid_value_len, 2, "value length child option underflow")?; // for the child options // We need to remove the costs of the children if let Some((left_child_len, left_child_sum_len)) = left_child { - paid_value_len = paid_value_len.saturating_sub(left_child_len); - paid_value_len = paid_value_len.saturating_sub(left_child_sum_len); + paid_value_len = Self::checked_sub( + paid_value_len, + left_child_len, + "left child length underflow", + )?; + paid_value_len = Self::checked_sub( + paid_value_len, + left_child_sum_len, + "left child sum length underflow", + )?; } if let Some((right_child_len, right_child_sum_len)) = right_child { - paid_value_len = paid_value_len.saturating_sub(right_child_len); - paid_value_len = paid_value_len.saturating_sub(right_child_sum_len); + paid_value_len = Self::checked_sub( + paid_value_len, + right_child_len, + "right child length underflow", + )?; + paid_value_len = Self::checked_sub( + paid_value_len, + right_child_sum_len, + "right child sum length underflow", + )?; } let sum_tree_node_size = if let Some((tree_cost_type, sum_tree_len)) = in_sum_tree { let cost_size = tree_cost_type.cost_size(); - paid_value_len = paid_value_len.saturating_sub(sum_tree_len); - paid_value_len += cost_size; + paid_value_len = Self::checked_sub( + paid_value_len, + sum_tree_len, + "sum tree length underflow", + )?; + paid_value_len = + Self::checked_add(paid_value_len, cost_size, "sum tree cost overflow")?; cost_size } else { 0 @@ -235,7 +268,10 @@ impl OperationCost { // This is the moment we need to add the required space (after removing // children) but before adding the parent to child hook - paid_value_len += paid_value_len.required_space() as u32; + paid_value_len = Self::checked_len_with_required_space( + paid_value_len, + "value length required space overflow", + )?; // Now we are the parent to child hook @@ -244,9 +280,22 @@ impl OperationCost { // So we need to remove it and then add a hash length // For the parent ref + 4 (2 for child sizes, 1 for key_len, 1 for sum option) - paid_value_len += key_len + 4 + sum_tree_node_size; + let parent_hook_len = Self::checked_add(key_len, 4, "parent hook length overflow")?; + let parent_hook_len = Self::checked_add( + parent_hook_len, + sum_tree_node_size, + "parent hook sum size overflow", + )?; + paid_value_len = Self::checked_add( + paid_value_len, + parent_hook_len, + "parent hook value length overflow", + )?; } else { - paid_value_len += paid_value_len.required_space() as u32; + paid_value_len = Self::checked_len_with_required_space( + paid_value_len, + "value length required space overflow", + )?; } paid_value_len }; diff --git a/costs/src/storage_cost/key_value_cost.rs b/costs/src/storage_cost/key_value_cost.rs index e62dc60f8..1e6448e09 100644 --- a/costs/src/storage_cost/key_value_cost.rs +++ b/costs/src/storage_cost/key_value_cost.rs @@ -34,12 +34,13 @@ use std::{ use integer_encoding::VarInt; use crate::{ + error::Error, storage_cost::{removal::StorageRemovedBytes::NoStorageRemoval, StorageCost}, BasicStorageRemoval, StorageRemovedBytes, }; /// Storage only operation costs separated by key and value -#[derive(PartialEq, Clone, Eq, Default)] +#[derive(Debug, PartialEq, Clone, Eq, Default)] pub struct KeyValueStorageCost { /// Key storage_cost costs pub key_storage_cost: StorageCost, @@ -54,22 +55,34 @@ pub struct KeyValueStorageCost { impl KeyValueStorageCost { /// Convenience method for getting the cost of updating the key of the root /// of each merk - pub fn for_updated_root_cost(old_tree_key_len: Option, tree_key_len: u32) -> Self { + pub fn for_updated_root_cost( + old_tree_key_len: Option, + tree_key_len: u32, + ) -> Result { + fn with_required_space(len: u32) -> Result { + len.checked_add(len.required_space() as u32) + .ok_or(Error::Overflow("root key length required space overflow")) + } + if let Some(old_tree_key_len) = old_tree_key_len { let key_storage_cost = StorageCost { added_bytes: 0, replaced_bytes: 34, // prefix + 1 for 'r' + 1 required space removed_bytes: NoStorageRemoval, }; - let new_bytes = tree_key_len + tree_key_len.required_space() as u32; + let new_bytes = with_required_space(tree_key_len)?; let value_storage_cost = match tree_key_len.cmp(&old_tree_key_len) { Ordering::Less => { // we removed bytes - let old_bytes = old_tree_key_len + old_tree_key_len.required_space() as u32; + let old_bytes = with_required_space(old_tree_key_len)?; StorageCost { added_bytes: 0, replaced_bytes: new_bytes, - removed_bytes: BasicStorageRemoval(old_bytes - new_bytes), + removed_bytes: BasicStorageRemoval( + old_bytes + .checked_sub(new_bytes) + .ok_or(Error::Overflow("root key removed bytes underflow"))?, + ), } } Ordering::Equal => StorageCost { @@ -78,35 +91,37 @@ impl KeyValueStorageCost { removed_bytes: NoStorageRemoval, }, Ordering::Greater => { - let old_bytes = old_tree_key_len + old_tree_key_len.required_space() as u32; + let old_bytes = with_required_space(old_tree_key_len)?; StorageCost { - added_bytes: new_bytes - old_bytes, + added_bytes: new_bytes + .checked_sub(old_bytes) + .ok_or(Error::Overflow("root key added bytes underflow"))?, replaced_bytes: old_bytes, removed_bytes: NoStorageRemoval, } } }; - KeyValueStorageCost { + Ok(KeyValueStorageCost { key_storage_cost, value_storage_cost, new_node: false, needs_value_verification: false, - } + }) } else { - KeyValueStorageCost { + Ok(KeyValueStorageCost { key_storage_cost: StorageCost { added_bytes: 34, // prefix + 1 for 'r' + 1 required space replaced_bytes: 0, removed_bytes: NoStorageRemoval, }, value_storage_cost: StorageCost { - added_bytes: tree_key_len + tree_key_len.required_space() as u32, + added_bytes: with_required_space(tree_key_len)?, replaced_bytes: 0, removed_bytes: NoStorageRemoval, }, new_node: true, needs_value_verification: false, - } + }) } } diff --git a/costs/src/storage_cost/mod.rs b/costs/src/storage_cost/mod.rs index 41bafb36c..3fe5b44e5 100644 --- a/costs/src/storage_cost/mod.rs +++ b/costs/src/storage_cost/mod.rs @@ -74,7 +74,11 @@ impl AddAssign for StorageCost { impl StorageCost { /// Verify that the len of the item matches the given storage_cost cost pub fn verify(&self, len: u32) -> Result<(), Error> { - if self.added_bytes + self.replaced_bytes == len { + let total_bytes = self + .added_bytes + .checked_add(self.replaced_bytes) + .ok_or(Error::Overflow("storage cost verify overflow"))?; + if total_bytes == len { Ok(()) } else { Err(Error::StorageCostMismatch { diff --git a/costs/tests/coverage_regression.rs b/costs/tests/coverage_regression.rs index 3d9792f9b..2ef0d69d0 100644 --- a/costs/tests/coverage_regression.rs +++ b/costs/tests/coverage_regression.rs @@ -162,6 +162,7 @@ fn add_key_value_storage_costs_respects_verification_flags_and_errors() { assert_eq!(expected.added_bytes, paid_key_len + 1); assert_eq!(actual_total_bytes, paid_key_len); } + other => panic!("expected StorageCostMismatch, got {other:?}"), } let mut update_node = OperationCost::default(); @@ -497,12 +498,12 @@ fn storage_cost_verify_and_transition_paths() { #[test] fn key_value_storage_cost_paths_are_exercised() { - let insert = KeyValueStorageCost::for_updated_root_cost(None, 20); + let insert = KeyValueStorageCost::for_updated_root_cost(None, 20).unwrap(); assert!(insert.new_node); assert_eq!(insert.key_storage_cost.added_bytes, 34); assert_eq!(insert.value_storage_cost.added_bytes, 21); - let less = KeyValueStorageCost::for_updated_root_cost(Some(20), 10); + let less = KeyValueStorageCost::for_updated_root_cost(Some(20), 10).unwrap(); assert!(!less.new_node); assert_eq!(less.key_storage_cost.replaced_bytes, 34); assert_eq!(less.value_storage_cost.added_bytes, 0); @@ -512,11 +513,11 @@ fn key_value_storage_cost_paths_are_exercised() { BasicStorageRemoval(10) ); - let equal = KeyValueStorageCost::for_updated_root_cost(Some(10), 10); + let equal = KeyValueStorageCost::for_updated_root_cost(Some(10), 10).unwrap(); assert_eq!(equal.value_storage_cost.replaced_bytes, 11); assert_eq!(equal.value_storage_cost.removed_bytes, NoStorageRemoval); - let greater = KeyValueStorageCost::for_updated_root_cost(Some(10), 20); + let greater = KeyValueStorageCost::for_updated_root_cost(Some(10), 20).unwrap(); assert_eq!(greater.value_storage_cost.added_bytes, 10); assert_eq!(greater.value_storage_cost.replaced_bytes, 11); @@ -576,6 +577,36 @@ fn key_value_storage_cost_paths_are_exercised() { assert!(a == sum); } +#[test] +fn checked_storage_cost_arithmetic_rejects_overflow() { + let err = StorageCost { + added_bytes: u32::MAX, + replaced_bytes: 1, + removed_bytes: NoStorageRemoval, + } + .verify(0) + .unwrap_err(); + assert!(matches!( + err, + Error::Overflow("storage cost verify overflow") + )); + + let err = KeyValueStorageCost::for_updated_root_cost(None, u32::MAX).unwrap_err(); + assert!(matches!( + err, + Error::Overflow("root key length required space overflow") + )); + + let mut cost = OperationCost::default(); + let err = cost + .add_key_value_storage_costs(1, 1, Some((None, None, None)), None) + .unwrap_err(); + assert!(matches!( + err, + Error::Overflow("value length child option underflow") + )); +} + #[test] fn storage_removed_bytes_add_and_add_assign_paths_are_exercised() { let identifier_a: Identifier = [1; 32]; diff --git a/grovedb-element/src/element/helpers.rs b/grovedb-element/src/element/helpers.rs index 66a0b3de1..a2f1f728d 100644 --- a/grovedb-element/src/element/helpers.rs +++ b/grovedb-element/src/element/helpers.rs @@ -11,6 +11,17 @@ use crate::{ }; impl Element { + fn checked_required_space_sum( + context: &'static str, + parts: impl IntoIterator, + ) -> Result { + parts.into_iter().try_fold(0u32, |total, part| { + total + .checked_add(part) + .ok_or(ElementError::Overflow(context)) + }) + } + /// Returns `true` if this element is wrapped in `Element::NonCounted`. /// The wrapper suppresses count propagation to the parent count tree but /// leaves all other behavior (storage, hashing, sum propagation, internal @@ -619,7 +630,16 @@ impl Element { "required_item_space", grove_version.grovedb_versions.element.required_item_space ); - Ok(len + len.required_space() as u32 + flag_len + flag_len.required_space() as u32 + 1) + Self::checked_required_space_sum( + "required_item_space", + [ + len, + len.required_space() as u32, + flag_len, + flag_len.required_space() as u32, + 1, + ], + ) } /// Worst-case serialized-storage cost of an @@ -644,12 +664,16 @@ impl Element { .element .required_item_with_sum_item_space ); - Ok( - len + len.required_space() as u32 - + flag_len - + flag_len.required_space() as u32 - + 10 - + 1, + Self::checked_required_space_sum( + "required_item_with_sum_item_space", + [ + len, + len.required_space() as u32, + flag_len, + flag_len.required_space() as u32, + 10, + 1, + ], ) } @@ -678,12 +702,17 @@ impl Element { .element .required_reference_with_sum_item_space ); - Ok(path_len - + path_len.required_space() as u32 - + flag_len - + flag_len.required_space() as u32 - + 10 - + 1) + Self::checked_required_space_sum( + "required_reference_with_sum_item_space", + [ + path_len, + path_len.required_space() as u32, + flag_len, + flag_len.required_space() as u32, + 10, + 1, + ], + ) } /// Convert the reference to an absolute reference. Looks through a diff --git a/grovedb-element/src/error.rs b/grovedb-element/src/error.rs index 1cdaf1f41..22e5162ed 100644 --- a/grovedb-element/src/error.rs +++ b/grovedb-element/src/error.rs @@ -12,6 +12,10 @@ pub enum ElementError { /// Invalid input InvalidInput(&'static str), + #[error("overflow error: {0}")] + /// Arithmetic overflow + Overflow(&'static str), + /// The corrupted path represents a consistency error in internal groveDB /// logic #[error("corrupted path: {0}")] diff --git a/grovedb-element/tests/element_constructors_helpers.rs b/grovedb-element/tests/element_constructors_helpers.rs index acb4e07e4..7181bac31 100644 --- a/grovedb-element/tests/element_constructors_helpers.rs +++ b/grovedb-element/tests/element_constructors_helpers.rs @@ -479,6 +479,31 @@ fn required_item_space_matches_manual_formula() { assert_eq!(required, expected); } +#[test] +fn required_space_helpers_reject_u32_overflow() { + let grove_version = GroveVersion::latest(); + + let item_err = Element::required_item_space(u32::MAX, 0, grove_version).unwrap_err(); + assert!(matches!( + item_err, + ElementError::Overflow("required_item_space") + )); + + let item_with_sum_err = + Element::required_item_with_sum_item_space(u32::MAX, 0, grove_version).unwrap_err(); + assert!(matches!( + item_with_sum_err, + ElementError::Overflow("required_item_with_sum_item_space") + )); + + let reference_with_sum_err = + Element::required_reference_with_sum_item_space(u32::MAX, 0, grove_version).unwrap_err(); + assert!(matches!( + reference_with_sum_err, + ElementError::Overflow("required_reference_with_sum_item_space") + )); +} + /// Exercise the `check_grovedb_v0!` mismatch arm for both new helpers /// so the macro-expanded error path is covered. The helpers only know /// version `0`; flipping the version field to `1` must return a diff --git a/grovedb/src/batch/mod.rs b/grovedb/src/batch/mod.rs index 6742a8873..60902bda2 100644 --- a/grovedb/src/batch/mod.rs +++ b/grovedb/src/batch/mod.rs @@ -2907,6 +2907,14 @@ where // in `merk/src/element/costs.rs`). let wrapper_overhead = if new_element.is_wrapped() { 1u32 } else { 0 }; + let checked_value_defined_cost = + |parts: &[u32]| -> Result { + parts.iter().try_fold(0u32, |total, part| { + total.checked_add(*part).ok_or(MerkError::Overflow( + "value defined cost overflow", + )) + }) + }; match new_element.underlying() { Element::Tree(..) | Element::SumTree(..) @@ -2925,17 +2933,21 @@ where .tree_type() .expect("tree_type guaranteed by match arm"); let tree_cost_size = tree_type.cost_size(); - let tree_value_cost = tree_cost_size - + flags_len - + flags_len.required_space() as u32 - + wrapper_overhead; + let tree_value_cost = checked_value_defined_cost(&[ + tree_cost_size, + flags_len, + flags_len.required_space() as u32, + wrapper_overhead, + ])?; Ok((true, Some(LayeredValueDefinedCost(tree_value_cost)))) } Element::SumItem(..) => { - let sum_item_value_cost = SUM_ITEM_COST_SIZE - + flags_len - + flags_len.required_space() as u32 - + wrapper_overhead; + let sum_item_value_cost = checked_value_defined_cost(&[ + SUM_ITEM_COST_SIZE, + flags_len, + flags_len.required_space() as u32, + wrapper_overhead, + ])?; Ok(( true, Some(SpecializedValueDefinedCost(sum_item_value_cost)), @@ -2943,12 +2955,14 @@ where } Element::ItemWithSumItem(item_value, ..) => { let item_len = item_value.len() as u32; - let sum_item_value_cost = SUM_ITEM_COST_SIZE - + flags_len - + flags_len.required_space() as u32 - + item_len - + item_len.required_space() as u32 - + wrapper_overhead; + let sum_item_value_cost = checked_value_defined_cost(&[ + SUM_ITEM_COST_SIZE, + flags_len, + flags_len.required_space() as u32, + item_len, + item_len.required_space() as u32, + wrapper_overhead, + ])?; Ok(( true, Some(SpecializedValueDefinedCost(sum_item_value_cost)), diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index 05b812486..666314b82 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -419,12 +419,16 @@ where { let costs = if self.merk_type == StandaloneMerk { // if we are a standalone merk we want real costs - Some(KeyValueStorageCost::for_updated_root_cost( - key_updates - .updated_root_key_from - .as_ref() - .map(|k| k.len() as u32), - tree_key.len() as u32, + Some(cost_return_on_error_no_add!( + inner_cost, + KeyValueStorageCost::for_updated_root_cost( + key_updates + .updated_root_key_from + .as_ref() + .map(|k| k.len() as u32), + tree_key.len() as u32, + ) + .map_err(Error::CostsError) )) } else { // if we are a base merk we estimate these costs are free From 98db1fdc79513bf684c3d3d7b1dc465f9c839fb7 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 21 May 2026 18:52:28 +0700 Subject: [PATCH 2/2] test(costs): cover checked arithmetic failures --- costs/tests/coverage_regression.rs | 101 ++++++++++++++++++++++++++++- merk/src/merk/mod.rs | 33 +++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/costs/tests/coverage_regression.rs b/costs/tests/coverage_regression.rs index 2ef0d69d0..b3d37ea3e 100644 --- a/costs/tests/coverage_regression.rs +++ b/costs/tests/coverage_regression.rs @@ -12,7 +12,7 @@ use grovedb_costs::{ transition::OperationStorageTransitionType, StorageCost, }, - OperationCost, TreeCostType, + ChildrenSizesWithIsSumTree, OperationCost, TreeCostType, }; use integer_encoding::VarInt; use intmap::IntMap; @@ -579,6 +579,22 @@ fn key_value_storage_cost_paths_are_exercised() { #[test] fn checked_storage_cost_arithmetic_rejects_overflow() { + fn add_key_value_storage_overflow( + key_len: u32, + value_len: u32, + children_sizes: ChildrenSizesWithIsSumTree, + storage_cost_info: Option, + ) -> &'static str { + let mut cost = OperationCost::default(); + match cost + .add_key_value_storage_costs(key_len, value_len, children_sizes, storage_cost_info) + .unwrap_err() + { + Error::Overflow(context) => context, + other => panic!("expected overflow, got {other:?}"), + } + } + let err = StorageCost { added_bytes: u32::MAX, replaced_bytes: 1, @@ -605,6 +621,89 @@ fn checked_storage_cost_arithmetic_rejects_overflow() { err, Error::Overflow("value length child option underflow") )); + + assert_eq!( + add_key_value_storage_overflow( + 1, + 1, + None, + Some(KeyValueStorageCost { + key_storage_cost: StorageCost::default(), + value_storage_cost: StorageCost { + added_bytes: u32::MAX, + replaced_bytes: 1, + removed_bytes: NoStorageRemoval, + }, + new_node: false, + needs_value_verification: false, + }), + ), + "value storage cost overflow" + ); + assert_eq!( + add_key_value_storage_overflow(1, 2, Some((None, Some((1, 0)), None)), None), + "left child length underflow" + ); + assert_eq!( + add_key_value_storage_overflow(1, 3, Some((None, Some((1, 1)), None)), None), + "left child sum length underflow" + ); + assert_eq!( + add_key_value_storage_overflow(1, 2, Some((None, None, Some((1, 0)))), None), + "right child length underflow" + ); + assert_eq!( + add_key_value_storage_overflow(1, 3, Some((None, None, Some((1, 1)))), None), + "right child sum length underflow" + ); + assert_eq!( + add_key_value_storage_overflow( + 1, + 2, + Some((Some((TreeCostType::TreeFeatureUses16Bytes, 1)), None, None,)), + None, + ), + "sum tree length underflow" + ); + assert_eq!( + add_key_value_storage_overflow( + 1, + u32::MAX, + Some((Some((TreeCostType::TreeFeatureUses16Bytes, 0)), None, None,)), + None, + ), + "sum tree cost overflow" + ); + assert_eq!( + add_key_value_storage_overflow( + 1, + u32::MAX, + Some(( + Some((TreeCostType::TreeFeatureUsesVarIntCostAs8Bytes, 6)), + None, + None, + )), + None, + ), + "value length required space overflow" + ); + assert_eq!( + add_key_value_storage_overflow( + u32::MAX - 15, + 100, + Some((Some((TreeCostType::TreeFeatureUses16Bytes, 0)), None, None,)), + None, + ), + "parent hook sum size overflow" + ); + assert_eq!( + add_key_value_storage_overflow(u32::MAX - 20, 100, Some((None, None, None)), None), + "parent hook value length overflow" + ); + assert_eq!( + add_key_value_storage_overflow(1, u32::MAX, None, None), + "value length required space overflow" + ); } #[test] diff --git a/merk/src/merk/mod.rs b/merk/src/merk/mod.rs index 666314b82..7101a6eb2 100644 --- a/merk/src/merk/mod.rs +++ b/merk/src/merk/mod.rs @@ -1083,7 +1083,9 @@ mod test { use grovedb_path::SubtreePath; use grovedb_storage::{ - rocksdb_storage::{PrefixedRocksDbTransactionContext, RocksDbStorage}, + rocksdb_storage::{ + test_utils::TempStorage, PrefixedRocksDbTransactionContext, RocksDbStorage, + }, RawIterator, Storage, StorageBatch, StorageContext, }; use grovedb_version::version::GroveVersion; @@ -1123,6 +1125,35 @@ mod test { ); } + #[test] + fn standalone_apply_charges_updated_root_key_storage_cost() { + let grove_version = GroveVersion::latest(); + let storage = TempStorage::new(); + let batch = StorageBatch::new(); + let transaction = storage.start_transaction(); + let mut merk = Merk::open_standalone( + storage + .get_transactional_storage_context(SubtreePath::empty(), Some(&batch), &transaction) + .unwrap(), + TreeType::NormalTree, + None::<&fn(&[u8], &GroveVersion) -> Option>, + grove_version, + ) + .unwrap() + .expect("cannot open standalone merk"); + + merk.apply::<_, Vec<_>>( + &[(b"root".to_vec(), Op::Put(b"value".to_vec(), BasicMerkNode))], + &[], + None, + grove_version, + ) + .unwrap() + .expect("apply failed"); + + assert_eq!(merk.root_key(), Some(b"root".to_vec())); + } + #[test] fn tree_height() { let grove_version = GroveVersion::latest();