Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
51abda2
feat(element,grovedb): add Element::ReferenceWithSumItem variant + Re…
QuantumExplorer May 16, 2026
f86f68d
Merge branch 'develop' into claude/practical-kalam-bc19ed
QuantumExplorer May 16, 2026
12e1172
test: cover query / refresh-untrusted / cost-estimate / proof / aggre…
QuantumExplorer May 16, 2026
718171f
fix(batch): preserve NonCounted wrapper across RefreshReferenceWithSu…
QuantumExplorer May 16, 2026
9384dc0
fix(batch): always thread refresh op's reference_path_type into proce…
QuantumExplorer May 16, 2026
1d8498b
test(refresh-with-sum-item): cover op tag, debug format, wrapper-pres…
QuantumExplorer May 16, 2026
4330362
fix(batch): enforce parent-tree invariant on refresh-with-sum-item; c…
QuantumExplorer May 16, 2026
bc421eb
fix(batch): remove buggy process_reference fast path that committed s…
QuantumExplorer May 16, 2026
d4055e7
test: pin exact GroveOp::to_u8 sort tag for RefreshReferenceWithSumItem
QuantumExplorer May 16, 2026
9c38ade
fix(cost): InsertTreeWithRootHash / InsertNonMerkTree honor wrapper byte
QuantumExplorer May 16, 2026
c8949a7
fix(cost): InsertTreeWithRootHash also honors not_counted_or_summed w…
QuantumExplorer May 16, 2026
b5b48fc
test(cost): tighten non_counted wrapper-byte assertions from >= to >
QuantumExplorer May 16, 2026
4a1a73c
revert(batch): restore process_reference fast path under well-formed-…
QuantumExplorer May 16, 2026
36de3eb
refactor(batch): RefreshReferenceWithSumItem trust=false now refreshe…
QuantumExplorer May 16, 2026
d0a5cc6
docs(batch): document trust=true cross-type silent-coercion contract …
QuantumExplorer May 16, 2026
2bf33b2
refactor(batch): scope merk binding to fast-path only in process_refe…
QuantumExplorer May 16, 2026
9a98fb8
feat(grovedbg-types,debugger): dedicated wire variant for ReferenceWi…
QuantumExplorer May 16, 2026
e75fe09
refactor(batch): unify RefreshReference + RefreshReferenceWithSumItem…
QuantumExplorer May 16, 2026
de98356
refactor(batch): encode trust mode into RefreshReferenceMode (5 varia…
QuantumExplorer May 17, 2026
998a78f
refactor(batch): extract RefreshReferenceMode into its own module
QuantumExplorer May 17, 2026
11d9f71
chore(batch): drop the do-not-reuse-17 comment from GroveOp::to_u8
QuantumExplorer May 17, 2026
92b7599
refactor(batch): add non_counted parameter to refresh_reference_op
QuantumExplorer May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions grovedb-element/src/element/constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,45 @@ impl Element {
Element::Reference(reference_path, max_reference_hop, flags)
}

/// Set element to a reference-with-sum-item without flags or max hops.
///
/// `sum_value` is the explicit weight that propagates to a sum-bearing
/// parent — independent of whatever the reference resolves to.
pub fn new_reference_with_sum_item(
reference_path: ReferencePathType,
sum_value: SumValue,
) -> Self {
Element::ReferenceWithSumItem(reference_path, None, sum_value, None)
}

/// Set element to a reference-with-sum-item with flags.
pub fn new_reference_with_sum_item_with_flags(
reference_path: ReferencePathType,
sum_value: SumValue,
flags: Option<ElementFlags>,
) -> Self {
Element::ReferenceWithSumItem(reference_path, None, sum_value, flags)
}

/// Set element to a reference-with-sum-item with max hops, no flags.
pub fn new_reference_with_sum_item_with_hops(
reference_path: ReferencePathType,
max_reference_hop: MaxReferenceHop,
sum_value: SumValue,
) -> Self {
Element::ReferenceWithSumItem(reference_path, max_reference_hop, sum_value, None)
}

/// Set element to a reference-with-sum-item with max hops and flags.
pub fn new_reference_with_sum_item_with_max_hops_and_flags(
reference_path: ReferencePathType,
max_reference_hop: MaxReferenceHop,
sum_value: SumValue,
flags: Option<ElementFlags>,
) -> Self {
Element::ReferenceWithSumItem(reference_path, max_reference_hop, sum_value, flags)
}

/// Set element to a tree without flags
pub fn new_tree(maybe_root_key: Option<Vec<u8>>) -> Self {
Element::Tree(maybe_root_key, None)
Expand Down
83 changes: 69 additions & 14 deletions grovedb-element/src/element/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ impl Element {
///
/// `NonCounted` delegates to its inner element — sums still propagate
/// when the wrapper is inserted into a sum-bearing parent.
/// `NotSummed` and `NotCountedOrSummed` return 0 — the wrappers'
/// `NotSummed` and `NotCountedOrSummed` return 0 — those wrappers'
/// purpose is to contribute nothing to the parent sum tree.
/// `ReferenceWithSumItem` returns the explicit sum value carried on the
/// variant — independent of the resolved target's value.
pub fn sum_value_or_default(&self) -> i64 {
match self {
Element::NonCounted(inner) => inner.sum_value_or_default(),
Expand All @@ -97,7 +99,8 @@ impl Element {
| Element::ItemWithSumItem(_, sum_value, _)
| Element::SumTree(_, sum_value, _)
| Element::CountSumTree(_, _, sum_value, _)
| Element::ProvableCountSumTree(_, _, sum_value, _) => *sum_value,
| Element::ProvableCountSumTree(_, _, sum_value, _)
| Element::ReferenceWithSumItem(_, _, sum_value, _) => *sum_value,
_ => 0,
}
}
Expand Down Expand Up @@ -128,14 +131,17 @@ impl Element {
/// `NotSummed` returns `(inner_count, 0)` — sum is suppressed, count
/// still propagates.
/// `NotCountedOrSummed` returns `(0, 0)` — both are suppressed.
/// `ReferenceWithSumItem` returns `(1, sum_value)` — counts as one
/// element like a plain reference, contributes its explicit sum.
pub fn count_sum_value_or_default(&self) -> (u64, i64) {
match self {
Element::NonCounted(inner) => (0, inner.sum_value_or_default()),
Element::NotSummed(inner) => (inner.count_value_or_default(), 0),
Element::NotCountedOrSummed(_) => (0, 0),
Element::SumItem(sum_value, _)
| Element::ItemWithSumItem(_, sum_value, _)
| Element::SumTree(_, sum_value, _) => (1, *sum_value),
| Element::SumTree(_, sum_value, _)
| Element::ReferenceWithSumItem(_, _, sum_value, _) => (1, *sum_value),
Element::CountTree(_, count_value, _) => (*count_value, 0),
Element::CountSumTree(_, count_value, sum_value, _)
| Element::ProvableCountSumTree(_, count_value, sum_value, _) => {
Expand All @@ -148,7 +154,8 @@ impl Element {

/// Decoded the integer value in the SumItem element type, returns 0 for
/// everything else. `NonCounted` delegates to its inner. `NotSummed`
/// and `NotCountedOrSummed` return 0.
/// and `NotCountedOrSummed` return 0. `ReferenceWithSumItem` returns
/// its explicit i64 sum cast to i128.
pub fn big_sum_value_or_default(&self) -> i128 {
match self {
Element::NonCounted(inner) => inner.big_sum_value_or_default(),
Expand All @@ -157,28 +164,33 @@ impl Element {
| Element::ItemWithSumItem(_, sum_value, _)
| Element::SumTree(_, sum_value, _)
| Element::CountSumTree(_, _, sum_value, _)
| Element::ProvableCountSumTree(_, _, sum_value, _) => *sum_value as i128,
| Element::ProvableCountSumTree(_, _, sum_value, _)
| Element::ReferenceWithSumItem(_, _, sum_value, _) => *sum_value as i128,
Element::BigSumTree(_, sum_value, _) => *sum_value,
_ => 0,
}
}

/// Decoded the integer value in the SumItem element type. Looks through
/// a `NonCounted` wrapper.
/// a `NonCounted` wrapper. Also returns the explicit sum from
/// `ReferenceWithSumItem`.
pub fn as_sum_item_value(&self) -> Result<i64, ElementError> {
match self.underlying() {
Element::SumItem(value, _) => Ok(*value),
Element::ItemWithSumItem(_, value, _) => Ok(*value),
Element::ReferenceWithSumItem(_, _, value, _) => Ok(*value),
_ => Err(ElementError::WrongElementType("expected a sum item")),
}
}

/// Decoded the integer value in the SumItem element type. Looks through
/// a `NonCounted` wrapper.
/// a `NonCounted` wrapper. Also returns the explicit sum from
/// `ReferenceWithSumItem`.
pub fn into_sum_item_value(self) -> Result<i64, ElementError> {
match self.into_underlying() {
Element::SumItem(value, _) => Ok(value),
Element::ItemWithSumItem(_, value, _) => Ok(value),
Element::ReferenceWithSumItem(_, _, value, _) => Ok(value),
_ => Err(ElementError::WrongElementType("expected a sum item")),
}
}
Expand Down Expand Up @@ -222,10 +234,12 @@ impl Element {
}

/// Gives the reference path type in the Reference element type. Looks
/// through a `NonCounted` wrapper.
/// through a `NonCounted` wrapper. Accepts both `Reference` and
/// `ReferenceWithSumItem`.
pub fn into_reference_path_type(self) -> Result<ReferencePathType, ElementError> {
match self.into_underlying() {
Element::Reference(value, ..) => Ok(value),
Element::ReferenceWithSumItem(value, ..) => Ok(value),
_ => Err(ElementError::WrongElementType("expected a reference")),
}
}
Expand Down Expand Up @@ -362,9 +376,24 @@ impl Element {
)
}

/// Check if the element is a reference. Looks through `NonCounted`.
/// Check if the element is a reference. Looks through `NonCounted`. Both
/// `Reference` and `ReferenceWithSumItem` are references — they share
/// the resolution path and combined-value-hash proof shape; the only
/// difference is that `ReferenceWithSumItem` carries an additional
/// `SumValue` that propagates into sum-bearing parents.
pub fn is_reference(&self) -> bool {
matches!(self.underlying(), Element::Reference(..))
matches!(
self.underlying(),
Element::Reference(..) | Element::ReferenceWithSumItem(..)
)
}

/// Check if the element is specifically a `ReferenceWithSumItem`. Looks
/// through `NonCounted`. Use `is_reference` when you only care that the
/// element is some kind of reference; use this when you need to
/// distinguish the sum-bearing variant.
pub fn is_reference_with_sum_item(&self) -> bool {
matches!(self.underlying(), Element::ReferenceWithSumItem(..))
}

/// Check if the element is an item. Looks through `NonCounted`.
Expand Down Expand Up @@ -421,7 +450,8 @@ impl Element {
| Element::CommitmentTree(.., flags)
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags) => flags,
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => flags,
Element::NonCounted(inner)
| Element::NotSummed(inner)
| Element::NotCountedOrSummed(inner) => inner.get_flags(),
Expand All @@ -446,7 +476,8 @@ impl Element {
| Element::CommitmentTree(.., flags)
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags) => flags,
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => flags,
Element::NonCounted(inner)
| Element::NotSummed(inner)
| Element::NotCountedOrSummed(inner) => inner.get_flags_owned(),
Expand All @@ -471,7 +502,8 @@ impl Element {
| Element::CommitmentTree(.., flags)
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags) => flags,
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => flags,
Element::NonCounted(inner)
| Element::NotSummed(inner)
| Element::NotCountedOrSummed(inner) => inner.get_flags_mut(),
Expand All @@ -496,7 +528,8 @@ impl Element {
| Element::CommitmentTree(.., flags)
| Element::MmrTree(.., flags)
| Element::BulkAppendTree(.., flags)
| Element::DenseAppendOnlyFixedSizeTree(.., flags) => *flags = new_flags,
| Element::DenseAppendOnlyFixedSizeTree(.., flags)
| Element::ReferenceWithSumItem(.., flags) => *flags = new_flags,
Element::NonCounted(inner)
| Element::NotSummed(inner)
| Element::NotCountedOrSummed(inner) => inner.set_flags(new_flags),
Expand Down Expand Up @@ -545,6 +578,28 @@ impl Element {
}
}
}
Element::ReferenceWithSumItem(
ref reference_path_type,
max_hop,
sum_value,
ref flags,
) => {
match reference_path_type {
ReferencePathType::AbsolutePathReference(..) => self,
_ => {
// Mirror the Reference arm: rebuild as absolute,
// preserving the sum value.
let absolute_path =
path_from_reference_path_type(reference_path_type.clone(), path, key)?;
Element::ReferenceWithSumItem(
ReferencePathType::AbsolutePathReference(absolute_path),
max_hop,
sum_value,
flags.clone(),
)
}
}
}
Element::NonCounted(inner) => Element::NonCounted(Box::new(
inner.convert_if_reference_to_absolute_reference(path, key)?,
)),
Expand Down
52 changes: 52 additions & 0 deletions grovedb-element/src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,35 @@ pub enum Element {
/// - A `NotCountedOrSummed` may not wrap any other wrapper or any
/// non-tree element.
NotCountedOrSummed(Box<Element>),
/// A reference that simultaneously carries an explicit `SumValue`.
///
/// Resolves like `Element::Reference` on `get()` / `follow_reference()`
/// (hop-limited, cycle-detected, combined value hash) AND contributes
/// `sum_value` to any sum-bearing parent tree like `Element::SumItem` /
/// `Element::ItemWithSumItem`. The `sum_value` is **independent of the
/// resolved target's value** — it is the caller-supplied weight or amount
/// associated with the link itself.
///
/// Use case: ranked / sortable index entries where the key encodes the
/// rank, the reference points to a canonical record elsewhere, and the
/// sum is the entry's monetary weight that aggregates into the parent's
/// total.
///
/// May be inserted into any parent tree type. In non-sum parents (Tree,
/// CountTree, ProvableCountTree) the `sum_value` is silently ignored,
/// the same rule `Element::ItemWithSumItem` follows.
///
/// Wrapper compatibility:
/// - **May** be wrapped in `NonCounted` to opt out of count propagation.
/// - **May NOT** be wrapped in `NotSummed` or `NotCountedOrSummed` —
/// those whitelists accept only the four sum-tree variants, not
/// item-like or reference-like base variants.
ReferenceWithSumItem(
ReferencePathType,
MaxReferenceHop,
SumValue,
Option<ElementFlags>,
),
}

pub fn hex_to_ascii(hex_value: &[u8]) -> String {
Expand Down Expand Up @@ -372,6 +401,18 @@ impl fmt::Display for Element {
Element::NotCountedOrSummed(inner) => {
write!(f, "NotCountedOrSummed({})", inner)
}
Element::ReferenceWithSumItem(path, max_hop, sum_value, flags) => {
write!(
f,
"ReferenceWithSumItem({}, max_hop: {}, sum: {}{})",
path,
max_hop.map_or("None".to_string(), |h| h.to_string()),
sum_value,
flags
.as_ref()
.map_or(String::new(), |f| format!(", flags: {:?}", f))
)
}
}
}
}
Expand Down Expand Up @@ -400,6 +441,7 @@ impl Element {
Element::MmrTree(..) => ElementType::MmrTree,
Element::BulkAppendTree(..) => ElementType::BulkAppendTree,
Element::DenseAppendOnlyFixedSizeTree(..) => ElementType::DenseAppendOnlyFixedSizeTree,
Element::ReferenceWithSumItem(..) => ElementType::ReferenceWithSumItem,
Element::NonCounted(inner) => match inner.element_type() {
ElementType::Item => ElementType::NonCountedItem,
ElementType::Reference => ElementType::NonCountedReference,
Expand All @@ -418,6 +460,7 @@ impl Element {
ElementType::DenseAppendOnlyFixedSizeTree => {
ElementType::NonCountedDenseAppendOnlyFixedSizeTree
}
ElementType::ReferenceWithSumItem => ElementType::NonCountedReferenceWithSumItem,
// Inner is always a base type — nested wrappers are
// forbidden at construction and (de)serialization.
already_non_counted => already_non_counted,
Expand Down Expand Up @@ -570,6 +613,12 @@ mod serde_impl {
NonCounted(Box<ElementShadow>),
NotSummed(Box<ElementShadow>),
NotCountedOrSummed(Box<ElementShadow>),
ReferenceWithSumItem(
ReferencePathType,
MaxReferenceHop,
SumValue,
Option<ElementFlags>,
),
}

impl From<ElementShadow> for Element {
Expand Down Expand Up @@ -603,6 +652,9 @@ mod serde_impl {
ElementShadow::NotCountedOrSummed(inner) => {
Element::NotCountedOrSummed(Box::new(Element::from(*inner)))
}
ElementShadow::ReferenceWithSumItem(p, h, s, f) => {
Element::ReferenceWithSumItem(p, h, s, f)
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions grovedb-element/src/element/visualize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ impl Visualize for Element {
drawer = inner.visualize(drawer)?;
drawer.write(b")")?;
}
Element::ReferenceWithSumItem(_ref, _max_hop, sum_value, flags) => {
drawer.write(format!("ref_with_sum_item: {sum_value}").as_bytes())?;
if let Some(f) = flags
&& !f.is_empty()
{
drawer = f.visualize(drawer)?;
}
}
}
Ok(drawer)
}
Expand Down
Loading
Loading