Skip to content

feat: use transactional storage context for all specialized trees - #638

Merged
QuantumExplorer merged 9 commits into
developfrom
fix/specialized-trees-use-transactional-context
Mar 10, 2026
Merged

feat: use transactional storage context for all specialized trees#638
QuantumExplorer merged 9 commits into
developfrom
fix/specialized-trees-use-transactional-context

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Mar 10, 2026

Copy link
Copy Markdown
Member

Summary

  • Specialized trees (DenseFixedSizedMerkleTree, MMR, BulkAppendTree, CommitmentTree) previously used immediate storage context, bypassing the StorageBatch pipeline. This meant commit_batch cost tracking (fix: version-gate commit_batch accumulated costs #637) had no effect on these code paths.
  • Adds in-memory caches that provide read-after-write visibility, enabling transactional storage context where writes are deferred to a StorageBatch:
    • DenseFixedSizedMerkleTree: write-through cache serves reads from memory, falling back to storage for pre-session values
    • MMRBatch: overlay persistence across compaction cycles so cross-epoch MMR reads find nodes without storage round-trips
    • BulkAppendTree/CommitmentTree: MMR overlay held in-memory during session, flushed via commit_mmr() at session end
  • All GroveDB operations (dense_tree, mmr_tree, bulk_append_tree, commitment_tree) now use get_transactional_storage_context + commit_multi_context_batch, matching the pattern used by Merk trees

Preprocessing atomicity fix

Preprocessing functions (dense, MMR, bulk append, commitment) previously created their own local StorageBatch and committed it directly to the transaction via commit_multi_context_batch. This meant that if a later operation in the batch failed, the preprocessing data was already written to a borrowed (external) transaction and could not be discarded — leaving orphaned subtree data.

Fix: All preprocessing functions now receive the shared StorageBatch from apply_batch, so writes are only committed atomically at the end alongside all other batch operations. Dropping the batch discards everything, including preprocessing data.

Audit fixes

  • MMR overlay recovery: If mmr.push() or mmr.get_root() fails after std::mem::take drains the overlay, restore it before returning the error
  • Dense tree cache rollback: Clear the cache slot when rolling back count after a compute_root_hash failure

Test plan

  • cargo test -p grovedb-dense-fixed-sized-merkle-tree — 168 tests pass
  • cargo test -p grovedb-merkle-mountain-range — 115 tests pass
  • cargo test -p grovedb-bulk-append-tree — 68 tests pass
  • cargo test -p grovedb-commitment-tree — 1 test passes
  • cargo test -p grovedb --lib — 1422 tests pass
  • cargo clippy -- -D warnings — clean
  • Batch discard tests (9 tests across all 4 tree types): use external (borrowed) transactions and raw subtree storage assertions to verify no preprocessing data leaks on batch failure
  • Regression verified: all 9 batch discard tests fail without the atomicity fix (raw storage checks detect leaked data that count-based checks miss)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • API to flush in-memory MMR overlays to storage.
  • Improvements

    • In-memory MMR overlay to expose uncommitted nodes without extra storage reads.
    • Widespread switch to transactional storage contexts and batched commits for reliable read-after-write visibility.
    • Write-through cache for dense trees to reduce reads and improve consistency.
  • Tests

    • Extensive new tests for batch/transaction semantics, rollback, visibility, compaction, and error paths.

Specialized trees (DenseFixedSizedMerkleTree, MMR, BulkAppendTree,
CommitmentTree) previously used immediate storage context which bypassed
the StorageBatch pipeline. This meant commit_batch cost tracking had no
effect on these code paths.

This change adds in-memory caches that provide read-after-write
visibility, enabling transactional storage context where writes are
deferred to a StorageBatch:

- DenseFixedSizedMerkleTree: write-through cache serves reads from
  memory, falling back to storage for pre-session values
- MMRBatch: overlay persistence across compaction cycles so cross-epoch
  MMR reads find nodes pushed in previous compactions without storage
- BulkAppendTree/CommitmentTree: MMR overlay held in-memory during
  session, flushed via commit_mmr() at session end

All GroveDB operations (dense_tree, mmr_tree, bulk_append_tree,
commitment_tree) now use get_transactional_storage_context +
commit_multi_context_batch, matching the pattern used by Merk trees.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an in-memory MMR overlay and overlay-aware MMR constructors, threads transactional StorageBatch contexts through many tree/MMR operations for read-after-write visibility, exposes commit_mmr() to flush overlays, and adds a write-through cache to DenseFixedSizedMerkleTree.

Changes

Cohort / File(s) Summary
MMR core
grovedb-merkle-mountain-range/src/mmr.rs, grovedb-merkle-mountain-range/src/mmr_store.rs
Add MMR::new_with_overlay(...), MMRBatch::with_overlay(...), and MMRBatch::take_overlay() to construct MMRs pre-populated with and extract an in-memory overlay.
BulkAppendTree integration
grovedb-bulk-append-tree/src/tree/mod.rs, grovedb-bulk-append-tree/src/tree/append.rs, grovedb-bulk-append-tree/src/tree/fetch.rs, grovedb-bulk-append-tree/src/proof/mod.rs
Introduce mmr_overlay field, switch to MMR::new_with_overlay, read nodes via mmr.batch.element_at_position(...), preserve/restore overlays on errors, and add commit_mmr() to persist overlays.
CommitmentTree API
grovedb-commitment-tree/src/commitment_tree/mod.rs
Expose pub fn commit_mmr(&mut self) -> Result<(), CommitmentTreeError> delegating to the bulk tree to persist MMR overlay.
Transactional storage switch
grovedb/src/operations/bulk_append_tree.rs, grovedb/src/operations/commitment_tree.rs, grovedb/src/operations/dense_tree.rs, grovedb/src/operations/mmr_tree.rs, grovedb/src/operations/proof/generate.rs
Replace many get_immediate_storage_context calls with get_transactional_storage_context(..., Some(&data_batch)/None, tx), introduce per-operation StorageBatchs, commit via commit_multi_context_batch, and flush MMR overlays in-batch for read-after-write visibility.
Batch preprocessing calls
grovedb/src/batch/mod.rs
Update preprocessing invocations to pass &storage_batch into preprocess_* functions (bulk/mmr/commitment/dense) so they operate transactionally against the batch.
DenseFixedSizedMerkleTree cache
grovedb-dense-fixed-sized-merkle-tree/src/tree.rs, grovedb-dense-fixed-sized-merkle-tree/src/tests.rs
Add write-through cache: Vec<Option<Vec<u8>>>, consult cache on reads and update it on writes (put_value now &mut), and rollback cache entries on storage write failures; tests adjusted to initialize state before simulating storage corruption.
Proofs / fetch paths
grovedb-bulk-append-tree/src/proof/mod.rs, grovedb-bulk-append-tree/src/tree/fetch.rs, grovedb/src/operations/proof/generate.rs
Construct MMRs with overlays for proof/chunk reads and access nodes via the MMR batch interface instead of direct store lookups.
GroveDb transactional test helper
grovedb/src/lib.rs
Add test-only helper raw_subtree_get to read raw subtree keys via a transactional storage context for tests.
Tests: transactional/batch semantics
grovedb/src/tests/*_tests.rs
Add extensive tests for BulkAppendTree, CommitmentTree, DenseTree, and MMR trees covering batch lifecycle, transaction commit/rollback, compaction, and visibility semantics (many new test cases).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BulkAppendTree
    participant MMR as MMR (with overlay)
    participant DataBatch
    participant Storage

    Client->>BulkAppendTree: append/push
    BulkAppendTree->>MMR: MMR::new_with_overlay(mmr_size, store, overlay)
    MMR->>MMR: batch preloads overlay in-memory
    BulkAppendTree->>MMR: batch.element_at_position(pos)
    MMR-->>BulkAppendTree: node (overlay or store)
    BulkAppendTree->>MMR: batch.push/update (buffers node in overlay)
    BulkAppendTree->>BulkAppendTree: take_overlay (store locally)

    Client->>BulkAppendTree: commit_mmr()
    BulkAppendTree->>DataBatch: create StorageBatch
    BulkAppendTree->>MMR: MMR::new_with_overlay(..., taken_overlay)
    BulkAppendTree->>MMR: batch.commit() -> write buffered nodes into DataBatch
    MMR->>DataBatch: write nodes into batch
    DataBatch->>Storage: commit_multi_context_batch
    Storage-->>DataBatch: ack
    BulkAppendTree->>BulkAppendTree: clear overlay
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hoarded nodes in a cozy heap,
Buffered my hops instead of a leap,
Batched my crumbs, then gave a cheer,
Flushed them out when the coast was clear —
Hop, commit, and no extra trips!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective: implementing transactional storage context usage across all specialized tree types (Dense, MMR, BulkAppend, Commitment) to align with batch cost tracking and batch-scoped atomicity.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/specialized-trees-use-transactional-context

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Aligns with Merk which always uses transactional storage context for
both reads and writes. The remaining get_immediate_storage_context calls
in grovedb/src/operations/ are now zero — all specialized tree
operations (dense tree, MMR, bulk append, commitment tree, and proof
generation) consistently use get_transactional_storage_context.

Read-only operations pass None as the batch parameter since they don't
write.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
grovedb-dense-fixed-sized-merkle-tree/src/tree.rs (1)

36-52: ⚠️ Potential issue | 🟡 Minor

Address pipeline warning: field cache is never read.

The pipeline reports that cache is never read. This occurs because get_value() and other cache-reading methods are behind #[cfg(feature = "storage")], but the field itself is unconditional. When compiled without the storage feature, the field exists but is unused.

Consider one of:

  1. Gate the field with #[cfg(feature = "storage")] and use a unit type placeholder otherwise
  2. Add #[allow(dead_code)] if non-storage builds are uncommon
Option 1: Feature-gate the field
 pub struct DenseFixedSizedMerkleTree<S> {
     height: u8,
     count: u16,
     /// The underlying storage context.
     pub storage: S,
+    #[cfg(feature = "storage")]
     /// Write-through cache: holds values written in this session.
     /// Indexed by position. `None` means the value has not been written
     /// in this session (fall back to storage).
     cache: Vec<Option<Vec<u8>>>,
 }

Note: This would require adjusting constructors and accessors with matching #[cfg] blocks.

Option 2: Suppress warning
+    #[cfg_attr(not(feature = "storage"), allow(dead_code))]
     /// Write-through cache: holds values written in this session.
     /// Indexed by position. `None` means the value has not been written
     /// in this session (fall back to storage).
     cache: Vec<Option<Vec<u8>>>,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb-dense-fixed-sized-merkle-tree/src/tree.rs` around lines 36 - 52,
Field `cache` on DenseFixedSizedMerkleTree is unused in non-storage builds
causing a dead-code warning; gate the field with #[cfg(feature = "storage")]
(and provide a #[cfg(not(feature = "storage"))] placeholder like () if needed)
and update all places that construct or access it (e.g., the struct
DenseFixedSizedMerkleTree definition, its constructors/initializers and methods
that touch `cache` such as get_value and insert) so the code compiles in both
feature sets; alternatively, if you prefer minimal change, add
#[allow(dead_code)] to the `cache` field but prefer the feature-gate approach
and mirror cfg annotations on constructors/accessors that read/write `cache`.
🧹 Nitpick comments (2)
grovedb-commitment-tree/src/commitment_tree/mod.rs (1)

369-377: Consider returning CostResult for cost tracking consistency.

Most mutating methods in CommitmentTree (e.g., save, open, append_raw) return CostResult for fee estimation. This method returns a plain Result, which means storage costs from the underlying MMR commit won't be tracked.

If the underlying BulkAppendTree::commit_mmr performs storage operations, consider returning CostResult<(), CommitmentTreeError> to maintain consistent cost accumulation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb-commitment-tree/src/commitment_tree/mod.rs` around lines 369 - 377,
commit_mmr currently returns Result<(), CommitmentTreeError> causing MMR storage
costs to be omitted; change it to return CostResult<(), CommitmentTreeError> and
propagate cost from the underlying BulkAppendTree::commit_mmr. Update the
signature of CommitmentTree::commit_mmr to CostResult<(), CommitmentTreeError>,
call self.bulk_tree.commit_mmr() and map its output into the CostResult
(propagating any cost accumulation and mapping errors into CommitmentTreeError
as before), and ensure any callers of CommitmentTree::commit_mmr are adjusted to
handle a CostResult return type.
grovedb/src/operations/bulk_append_tree.rs (1)

156-158: Verify return value is intentional: returning old total_count as position.

The function returns total_count (captured at line 60, before the append) as the global position. This is semantically correct since the 0-based position of a newly appended item equals the previous count. However, the AppendResult from line 86 already contains global_position which would be clearer to use.

Consider using result.global_position for clarity:

♻️ Optional: use result.global_position for clarity
+        let global_position = result.global_position;
+
         let new_state_root = result.state_root;
         let new_total_count = tree.total_count;
         // ... rest of function ...
         tx.commit_local()
-            .map(|()| (new_state_root, total_count))
+            .map(|()| (new_state_root, global_position))
             .wrap_with_cost(cost)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/bulk_append_tree.rs` around lines 156 - 158, The code
currently returns the captured pre-append total_count as the new item's global
position when calling tx.commit_local(). Replace that with the AppendResult's
global_position to make the return value explicit and clearer: use the
result.global_position from the AppendResult produced earlier (instead of
total_count) when mapping the tx.commit_local() result, keeping the returned
tuple shape (global_position, total_count) semantics intact and referencing the
AppendResult variable and tx.commit_local() call in bulk_append_tree.rs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grovedb-bulk-append-tree/src/tree/append.rs`:
- Around line 191-205: The commit_mmr function currently calls
mmr.commit().unwrap().map_err(...) which discards the CostResult cost component;
update commit_mmr to either return CostResult<(), BulkAppendError> and replace
the unwrap/map_err with the cost_return_on_error! macro when handling
mmr.commit() (use symbols: commit_mmr, mmr.commit(), CostResult,
cost_return_on_error!, BulkAppendError, MMR) so costs are propagated per
guidelines, or if discarding cost is intentional add a concise comment above
commit_mmr explaining why cost tracking is intentionally omitted at this
boundary.

---

Outside diff comments:
In `@grovedb-dense-fixed-sized-merkle-tree/src/tree.rs`:
- Around line 36-52: Field `cache` on DenseFixedSizedMerkleTree is unused in
non-storage builds causing a dead-code warning; gate the field with
#[cfg(feature = "storage")] (and provide a #[cfg(not(feature = "storage"))]
placeholder like () if needed) and update all places that construct or access it
(e.g., the struct DenseFixedSizedMerkleTree definition, its
constructors/initializers and methods that touch `cache` such as get_value and
insert) so the code compiles in both feature sets; alternatively, if you prefer
minimal change, add #[allow(dead_code)] to the `cache` field but prefer the
feature-gate approach and mirror cfg annotations on constructors/accessors that
read/write `cache`.

---

Nitpick comments:
In `@grovedb-commitment-tree/src/commitment_tree/mod.rs`:
- Around line 369-377: commit_mmr currently returns Result<(),
CommitmentTreeError> causing MMR storage costs to be omitted; change it to
return CostResult<(), CommitmentTreeError> and propagate cost from the
underlying BulkAppendTree::commit_mmr. Update the signature of
CommitmentTree::commit_mmr to CostResult<(), CommitmentTreeError>, call
self.bulk_tree.commit_mmr() and map its output into the CostResult (propagating
any cost accumulation and mapping errors into CommitmentTreeError as before),
and ensure any callers of CommitmentTree::commit_mmr are adjusted to handle a
CostResult return type.

In `@grovedb/src/operations/bulk_append_tree.rs`:
- Around line 156-158: The code currently returns the captured pre-append
total_count as the new item's global position when calling tx.commit_local().
Replace that with the AppendResult's global_position to make the return value
explicit and clearer: use the result.global_position from the AppendResult
produced earlier (instead of total_count) when mapping the tx.commit_local()
result, keeping the returned tuple shape (global_position, total_count)
semantics intact and referencing the AppendResult variable and tx.commit_local()
call in bulk_append_tree.rs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31da469d-095d-409f-9e39-109a3cb3033d

📥 Commits

Reviewing files that changed from the base of the PR and between 3e42bc0 and 58c5212.

📒 Files selected for processing (13)
  • grovedb-bulk-append-tree/src/proof/mod.rs
  • grovedb-bulk-append-tree/src/tree/append.rs
  • grovedb-bulk-append-tree/src/tree/fetch.rs
  • grovedb-bulk-append-tree/src/tree/mod.rs
  • grovedb-commitment-tree/src/commitment_tree/mod.rs
  • grovedb-dense-fixed-sized-merkle-tree/src/tests.rs
  • grovedb-dense-fixed-sized-merkle-tree/src/tree.rs
  • grovedb-merkle-mountain-range/src/mmr.rs
  • grovedb-merkle-mountain-range/src/mmr_store.rs
  • grovedb/src/operations/bulk_append_tree.rs
  • grovedb/src/operations/commitment_tree.rs
  • grovedb/src/operations/dense_tree.rs
  • grovedb/src/operations/mmr_tree.rs

Comment thread grovedb-bulk-append-tree/src/tree/append.rs
@codecov

codecov Bot commented Mar 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.02439% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.83%. Comparing base (3e42bc0) to head (b6ce60b).
⚠️ Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
grovedb-bulk-append-tree/src/tree/append.rs 65.78% 13 Missing ⚠️
grovedb-dense-fixed-sized-merkle-tree/src/tree.rs 84.00% 4 Missing ⚠️
grovedb/src/lib.rs 93.33% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #638      +/-   ##
===========================================
- Coverage    90.84%   90.83%   -0.02%     
===========================================
  Files          182      182              
  Lines        51781    51898     +117     
===========================================
+ Hits         47043    47142      +99     
- Misses        4738     4756      +18     
Components Coverage Δ
grovedb-core 89.06% <98.66%> (+0.01%) ⬆️
merk 91.94% <ø> (ø)
storage 86.36% <ø> (ø)
commitment-tree 96.43% <100.00%> (+0.01%) ⬆️
mmr 96.76% <100.00%> (+0.03%) ⬆️
bulk-append-tree 89.65% <70.45%> (-1.20%) ⬇️
element 97.56% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- compact_with_value: if mmr.push() or mmr.get_root() fails after
  std::mem::take drains the overlay, restore it before returning the
  error. Previously the overlay data from all prior compaction cycles
  would be permanently lost.

- DenseFixedSizedMerkleTree insert/try_insert: clear the cache slot
  when rolling back count after a compute_root_hash failure. Prevents
  a stale cache entry from diverging from the logical tree state.

- put_value: add debug_assert! that position < cache capacity.

- commit_mmr: add doc comment explaining why cost tracking is
  intentionally omitted at this boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
grovedb/src/operations/commitment_tree.rs (1)

516-547: ⚠️ Potential issue | 🔴 Critical

Use the combined commitment-tree root in the replacement op.

Line 542 stores bulk_state_root directly, but the non-batch path computes compute_commitment_tree_state_root(sinsemilla_root, bulk_state_root) before updating Merk. Batched CommitmentTreeInsert ops will therefore propagate a different child hash than commitment_tree_insert_raw, which breaks the frontier/anchor binding for preprocessed inserts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/commitment_tree.rs` around lines 516 - 547, The
replacement op currently uses bulk_state_root directly; instead compute and use
the combined commitment-tree root via
compute_commitment_tree_state_root(sinsemilla_root, bulk_state_root) so the
batched path produces the same child hash as commitment_tree_insert_raw and
non-batch updates; update the value assigned to the
GroveOp::ReplaceNonMerkTreeRoot hash (the replacement variable) to use that
combined root (ensuring sinsemilla_root is the same upstream variable used in
the non-batched flow).
🧹 Nitpick comments (3)
grovedb/src/operations/dense_tree.rs (1)

96-102: Add context to the new data-batch commits.

These new commit_multi_context_batch calls return bare converted errors, so we lose whether the failure came from the insert path or the preprocessing path. Wrap each commit with phase-specific context before converting it.

As per coding guidelines, "Wrap errors with context using .map_err(|e| Error::CorruptedData(format!("context: {}", e))) pattern in Rust source files"

Also applies to: 437-444

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/dense_tree.rs` around lines 96 - 102, The
commit_multi_context_batch calls (e.g., the call using &mut cost,
self.db.commit_multi_context_batch(data_batch, Some(tx.as_ref())) ) are mapping
errors directly into a bare conversion and losing phase context; change each to
wrap the error with phase-specific context before conversion using the pattern
.map_err(|e| Error::CorruptedData(format!("commit after <phase>: {}",
e))).map_err(Into::into) so the log indicates whether the failure occurred
during the insert path or the preprocessing path; apply this same wrapping to
the other commit_multi_context_batch invocation around lines 437-444 as well.
grovedb/src/operations/mmr_tree.rs (1)

113-119: Add context to the new data-batch commits.

Both new commit_multi_context_batch calls use Into::into, so a storage failure no longer tells us whether the append path or the preprocess path failed. Please wrap each commit with phase-specific context before converting it.

As per coding guidelines, "Wrap errors with context using .map_err(|e| Error::CorruptedData(format!("context: {}", e))) pattern in Rust source files"

Also applies to: 465-471

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/mmr_tree.rs` around lines 113 - 119, The
commit_multi_context_batch calls currently use .map_err(Into::into) so storage
errors lose phase info; update each call wrapped in cost_return_on_error to map
the error into a CorruptedData with phase context (e.g., replace
.map_err(Into::into) with .map_err(|e| Error::CorruptedData(format!("commit
append batch failed: {}", e))) for the append path, and similarly use "commit
preprocess batch failed" for the preprocess path), keeping the call on
self.db.commit_multi_context_batch and preserving cost_return_on_error usage.
grovedb/src/operations/bulk_append_tree.rs (1)

93-105: Keep the new flush/commit failures phase-specific.

Line 94 and Lines 100-104, plus the preprocessing twin at Lines 540-550, now collapse distinct failure points into generic converted errors. Since bulk append has separate MMR-flush and data-batch-commit phases now, wrap each failure with that phase name before converting it.

As per coding guidelines, "Wrap errors with context using .map_err(|e| Error::CorruptedData(format!("context: {}", e))) pattern in Rust source files"

Also applies to: 539-551

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/operations/bulk_append_tree.rs` around lines 93 - 105, The two
failure points collapse distinct phases into generic errors; update the error
mapping so each phase is wrapped with phase-specific context before converting:
for the MMR flush (tree.commit_mmr()) map the error with .map_err(|e|
Error::CorruptedData(format!("MMR flush failed: {}", e))).then pass that into
map_bulk_err (or incorporate map_bulk_err if it expects the same type), and for
the data batch commit (self.db.commit_multi_context_batch(...)) map the error
with .map_err(|e| Error::CorruptedData(format!("Data batch commit failed: {}",
e))).then convert to the target error type (e.g., .map_err(Into::into));
similarly apply the same wrapping at the preprocessing twin around the same
calls (lines ~539-551) so each phase reports its own context.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@grovedb/src/operations/commitment_tree.rs`:
- Around line 516-547: The replacement op currently uses bulk_state_root
directly; instead compute and use the combined commitment-tree root via
compute_commitment_tree_state_root(sinsemilla_root, bulk_state_root) so the
batched path produces the same child hash as commitment_tree_insert_raw and
non-batch updates; update the value assigned to the
GroveOp::ReplaceNonMerkTreeRoot hash (the replacement variable) to use that
combined root (ensuring sinsemilla_root is the same upstream variable used in
the non-batched flow).

---

Nitpick comments:
In `@grovedb/src/operations/bulk_append_tree.rs`:
- Around line 93-105: The two failure points collapse distinct phases into
generic errors; update the error mapping so each phase is wrapped with
phase-specific context before converting: for the MMR flush (tree.commit_mmr())
map the error with .map_err(|e| Error::CorruptedData(format!("MMR flush failed:
{}", e))).then pass that into map_bulk_err (or incorporate map_bulk_err if it
expects the same type), and for the data batch commit
(self.db.commit_multi_context_batch(...)) map the error with .map_err(|e|
Error::CorruptedData(format!("Data batch commit failed: {}", e))).then convert
to the target error type (e.g., .map_err(Into::into)); similarly apply the same
wrapping at the preprocessing twin around the same calls (lines ~539-551) so
each phase reports its own context.

In `@grovedb/src/operations/dense_tree.rs`:
- Around line 96-102: The commit_multi_context_batch calls (e.g., the call using
&mut cost, self.db.commit_multi_context_batch(data_batch, Some(tx.as_ref())) )
are mapping errors directly into a bare conversion and losing phase context;
change each to wrap the error with phase-specific context before conversion
using the pattern .map_err(|e| Error::CorruptedData(format!("commit after
<phase>: {}", e))).map_err(Into::into) so the log indicates whether the failure
occurred during the insert path or the preprocessing path; apply this same
wrapping to the other commit_multi_context_batch invocation around lines 437-444
as well.

In `@grovedb/src/operations/mmr_tree.rs`:
- Around line 113-119: The commit_multi_context_batch calls currently use
.map_err(Into::into) so storage errors lose phase info; update each call wrapped
in cost_return_on_error to map the error into a CorruptedData with phase context
(e.g., replace .map_err(Into::into) with .map_err(|e|
Error::CorruptedData(format!("commit append batch failed: {}", e))) for the
append path, and similarly use "commit preprocess batch failed" for the
preprocess path), keeping the call on self.db.commit_multi_context_batch and
preserving cost_return_on_error usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62e7b28a-3bec-4fa6-8b38-5b991061d521

📥 Commits

Reviewing files that changed from the base of the PR and between 58c5212 and e90e6fe.

📒 Files selected for processing (5)
  • grovedb/src/operations/bulk_append_tree.rs
  • grovedb/src/operations/commitment_tree.rs
  • grovedb/src/operations/dense_tree.rs
  • grovedb/src/operations/mmr_tree.rs
  • grovedb/src/operations/proof/generate.rs

Preprocessing functions (dense, MMR, bulk append, commitment) previously
created their own StorageBatch and committed it directly to the
transaction. This meant failed batches with borrowed (external)
transactions would leak orphaned preprocessing data into the transaction.

Now all preprocessing functions receive the shared StorageBatch from
apply_batch, so writes are only committed atomically at the end.

Batch discard tests switched from internal (owned) transactions to
external (borrowed) transactions to verify no preprocessing data leaks
into the transaction on batch failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
grovedb/src/tests/mmr_tree_tests.rs (2)

1948-1967: Assert the surviving leaf is real_leaf.

The final leaf_count == 1 assertion does not prove the failed batch left no stale overlay state behind; it only proves one leaf exists. Verifying index 0 or the root against real_leaf would actually pin the behavior this test is documenting.

♻️ Suggested assertion tightening
     let count = db
         .mmr_tree_leaf_count([b"parent"].as_ref(), b"mmr", None, grove_version)
         .unwrap()
         .expect("leaf count");
     assert_eq!(count, 1, "only the second batch's leaf should be present");
+    let value = db
+        .mmr_tree_get_value([b"parent"].as_ref(), b"mmr", 0, None, grove_version)
+        .unwrap()
+        .expect("leaf 0");
+    assert_eq!(value, Some(b"real_leaf".to_vec()));
+    let root = db
+        .mmr_tree_root_hash([b"parent"].as_ref(), b"mmr", None, grove_version)
+        .unwrap()
+        .expect("root");
+    assert_eq!(root, expected_mmr_root(&[b"real_leaf".to_vec()]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/mmr_tree_tests.rs` around lines 1948 - 1967, After
asserting count == 1, also verify the actual surviving leaf equals b"real_leaf":
call mmr_tree_leaf_by_index([b"parent"].as_ref(), b"mmr", 0, None,
grove_version).unwrap().expect("leaf by index") and assert its value equals
b"real_leaf". Alternatively you can compute mmr_tree_root([b"parent"].as_ref(),
b"mmr", None, grove_version) and assert the root matches the expected root when
the single leaf is b"real_leaf"; use mmr_tree_leaf_by_index or mmr_tree_root to
pin the exact surviving leaf content.

1787-1824: Strengthen rollback verification with root/value checks.

This only proves the count went back to 1. It can still miss regressions where rollback leaves leaf_0 or the root hash mutated. Capture the pre-tx root and assert both the root and leaf 0 are unchanged after rollback.

♻️ Suggested assertion tightening
     let count_before = db
         .mmr_tree_leaf_count(EMPTY_PATH, b"mmr", None, grove_version)
         .unwrap()
         .expect("count before");
     assert_eq!(count_before, 1);
+    let root_before = db
+        .mmr_tree_root_hash(EMPTY_PATH, b"mmr", None, grove_version)
+        .unwrap()
+        .expect("root before");

     // Start transaction and append more
     let tx = db.start_transaction();
@@
     let count_after = db
         .mmr_tree_leaf_count(EMPTY_PATH, b"mmr", None, grove_version)
         .unwrap()
         .expect("count after rollback");
     assert_eq!(
         count_after, 1,
         "mmr leaf count should revert after rollback"
     );
+    let root_after = db
+        .mmr_tree_root_hash(EMPTY_PATH, b"mmr", None, grove_version)
+        .unwrap()
+        .expect("root after rollback");
+    assert_eq!(root_after, root_before, "root should revert after rollback");
+    assert_eq!(
+        db.mmr_tree_get_value(EMPTY_PATH, b"mmr", 0, None, grove_version)
+            .unwrap()
+            .expect("leaf 0 after rollback"),
+        Some(b"leaf_0".to_vec())
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/tests/mmr_tree_tests.rs` around lines 1787 - 1824, Before
starting the transaction, call and store the MMR root and the value of leaf 0
(in addition to the existing count) and after rollback re-fetch the root and
leaf 0 and assert they equal the stored pre-tx values; keep the existing use of
start_transaction, mmr_tree_append, mmr_tree_leaf_count and
rollback_transaction, but add calls to the MMR getters (e.g., mmr_tree_root and
the leaf-getter used in this test suite) before the loop and assert equality
after db.rollback_transaction(&tx) alongside the existing count assertion so
both root hash and leaf_0 value are verified unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grovedb-bulk-append-tree/src/tree/append.rs`:
- Around line 209-217: The code currently takes ownership of self.mmr_overlay
with std::mem::take before calling MMR::new_with_overlay and mmr.commit(), so if
commit fails the instance loses its staged MMR nodes; fix by not permanently
removing the overlay until commit succeeds: move the overlay into a local
variable (e.g., let overlay = std::mem::take(&mut self.mmr_overlay)), pass that
local overlay to MMR::new_with_overlay (MMR::new_with_overlay(..., overlay)),
then call mmr.commit(); on success leave self.mmr_overlay cleared, but on Err
restore the overlay into self.mmr_overlay before mapping the error to
BulkAppendError::MmrError so get_mmr_root() and retries see the original staged
nodes.

In `@grovedb-dense-fixed-sized-merkle-tree/src/tree.rs`:
- Around line 49-53: The struct's cache field is always compiled but only read
under #[cfg(feature = "storage")], causing an unused-field warning; add
#[cfg(feature = "storage")] to the cache field declaration and also gate its
initializations in the Tree::new() and Tree::from_state() constructors (or
refactor those methods) so the cache is only created when the "storage" feature
is enabled, and ensure get_value remains inside the same #[cfg(feature =
"storage")] impl block.

In `@grovedb/src/operations/commitment_tree.rs`:
- Around line 167-173: The current code calls
self.db.commit_multi_context_batch(..., Some(tx.as_ref())) via
cost_return_on_error which commits frontier/MMR/bulk writes into tx early,
risking partial commits if later steps fail; modify the direct insert path to
use the shared storage_batch used by preprocess_commitment_tree_ops so all
commitment-tree data and metadata (frontier/MMR/bulk writes) are staged together
and committed atomically with the parent Merk update and propagation.
Concretely, replace the early call to self.db.commit_multi_context_batch(...)
(and its cost_return_on_error wrapper) with logic that appends the same writes
into the existing storage_batch (the same batch object used by
preprocess_commitment_tree_ops) and defer calling commit_multi_context_batch
until the unified batch is ready to be committed with tx, ensuring
CommitmentTree updates, storage_batch, and tx are committed together.

In `@grovedb/src/operations/mmr_tree.rs`:
- Around line 113-119: The commit currently writes MMR node data into tx before
insert_subtree and propagation complete, risking orphaned data if later steps
fail; modify mmr_tree_append to gather subtree node writes and metadata into the
same storage_batch used for insert_subtree (as preprocess_mmr_tree_ops does) and
defer calling self.db.commit_multi_context_batch(tx...) until after
insert_subtree and propagation succeed so both subtree data and Element::MmrTree
size/root updates are committed atomically; locate and replace the early
commit_multi_context_batch call in mmr_tree_append and instead append those
writes into the shared storage_batch passed through insert_subtree/propagate,
then commit that single batch at the end.

---

Nitpick comments:
In `@grovedb/src/tests/mmr_tree_tests.rs`:
- Around line 1948-1967: After asserting count == 1, also verify the actual
surviving leaf equals b"real_leaf": call
mmr_tree_leaf_by_index([b"parent"].as_ref(), b"mmr", 0, None,
grove_version).unwrap().expect("leaf by index") and assert its value equals
b"real_leaf". Alternatively you can compute mmr_tree_root([b"parent"].as_ref(),
b"mmr", None, grove_version) and assert the root matches the expected root when
the single leaf is b"real_leaf"; use mmr_tree_leaf_by_index or mmr_tree_root to
pin the exact surviving leaf content.
- Around line 1787-1824: Before starting the transaction, call and store the MMR
root and the value of leaf 0 (in addition to the existing count) and after
rollback re-fetch the root and leaf 0 and assert they equal the stored pre-tx
values; keep the existing use of start_transaction, mmr_tree_append,
mmr_tree_leaf_count and rollback_transaction, but add calls to the MMR getters
(e.g., mmr_tree_root and the leaf-getter used in this test suite) before the
loop and assert equality after db.rollback_transaction(&tx) alongside the
existing count assertion so both root hash and leaf_0 value are verified
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25313a4f-33bd-4d52-b1be-cb9bf81ba6a6

📥 Commits

Reviewing files that changed from the base of the PR and between e90e6fe and 4baefde.

📒 Files selected for processing (11)
  • grovedb-bulk-append-tree/src/tree/append.rs
  • grovedb-dense-fixed-sized-merkle-tree/src/tree.rs
  • grovedb/src/batch/mod.rs
  • grovedb/src/operations/bulk_append_tree.rs
  • grovedb/src/operations/commitment_tree.rs
  • grovedb/src/operations/dense_tree.rs
  • grovedb/src/operations/mmr_tree.rs
  • grovedb/src/tests/bulk_append_tree_tests.rs
  • grovedb/src/tests/commitment_tree_tests.rs
  • grovedb/src/tests/dense_tree_tests.rs
  • grovedb/src/tests/mmr_tree_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • grovedb/src/operations/dense_tree.rs

Comment thread grovedb-bulk-append-tree/src/tree/append.rs Outdated
Comment thread grovedb-dense-fixed-sized-merkle-tree/src/tree.rs
Comment thread grovedb/src/operations/bulk_append_tree.rs Outdated
Comment thread grovedb/src/operations/commitment_tree.rs Outdated
Comment thread grovedb/src/operations/mmr_tree.rs Outdated
QuantumExplorer and others added 2 commits March 10, 2026 13:46
The count-based checks (dense_tree_count, mmr_tree_leaf_count, bulk_count)
only read the parent element which is properly discarded by the batch. They
don't detect leaked preprocessing data in the actual subtree storage.

Add raw_subtree_get() test helper that reads directly from subtree storage,
bypassing the parent element. Use it in dense, MMR, and bulk append batch
discard tests to verify no data leaks into the transaction at position 0.

Verified: all 7 dense/MMR/bulk tests now fail without the preprocessing
atomicity fix (previously they passed despite data leakage). Combined with
the 2 commitment tree tests that already caught the bug via anchor checks,
all 9 tests properly detect the atomicity issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- commit_mmr(): restore mmr_overlay via take_overlay() if mmr.commit()
  fails, matching the pattern already used in compact_with_value().
  Without this, a failed commit permanently loses staged MMR nodes.

- DenseFixedSizedMerkleTree: gate cache field with #[cfg(feature =
  "storage")] to suppress "field is never read" warning when compiled
  without storage feature. All cache usage is already inside the
  storage-gated impl block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
grovedb/src/lib.rs (1)

1217-1220: Add context before converting raw storage errors.

This helper is meant to diagnose transactional leakage, so a bare Err(e.into()) makes failures much harder to interpret. Please wrap the storage error with at least the probed key (and ideally the subtree path) before returning it.

♻️ Suggested change
-        let result = storage_ctx.get(key).value;
-        match result {
-            Ok(opt) => Ok(opt.map(|v| v.to_vec())),
-            Err(e) => Err(e.into()),
-        }
+        storage_ctx
+            .get(key)
+            .value
+            .map(|opt| opt.map(|v| v.to_vec()))
+            .map_err(|e| {
+                Error::CorruptedData(format!(
+                    "raw subtree get failed for key {}: {}",
+                    hex::encode(key),
+                    e
+                ))
+            })

As per coding guidelines, "Wrap errors with context using .map_err(|e| Error::CorruptedData(format!("context: {}", e))) pattern in Rust source files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb/src/lib.rs` around lines 1217 - 1220, Wrap the raw storage error
returned from storage_ctx.get(key).value with contextual information before
converting: instead of returning Err(e.into()), map the error to include the
probed key (and the current subtree/path variable if available) using the
project's error-wrapping pattern (e.g. .map_err(|e|
Error::CorruptedData(format!("failed to read key {:?}{}: {}", key,
subtree_path_if_present, e)))) so callers can see which key/subtree caused the
failure; apply this change at the site where storage_ctx.get(key).value is
matched (refer to the storage_ctx.get and key symbols in this block).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@grovedb/src/tests/bulk_append_tree_tests.rs`:
- Around line 1900-1906: The test is probing for leftover MMR data using an
8-byte U64 key but compaction writes 4-byte tagged keys via
MmrStore::with_key_size(..., MmrKeySize::U32) (used by BulkAppendTree
compaction), so change the probe to build the MMR lookup key using the same U32
key-size encoding (MmrKeySize::U32) and lookup path (raw_subtree_get) so the
test queries the actual key format produced by the compaction path (use the same
key-construction helper or logic that MmrStore/MmrKeySize::U32 uses) to
correctly detect leaks.

---

Nitpick comments:
In `@grovedb/src/lib.rs`:
- Around line 1217-1220: Wrap the raw storage error returned from
storage_ctx.get(key).value with contextual information before converting:
instead of returning Err(e.into()), map the error to include the probed key (and
the current subtree/path variable if available) using the project's
error-wrapping pattern (e.g. .map_err(|e| Error::CorruptedData(format!("failed
to read key {:?}{}: {}", key, subtree_path_if_present, e)))) so callers can see
which key/subtree caused the failure; apply this change at the site where
storage_ctx.get(key).value is matched (refer to the storage_ctx.get and key
symbols in this block).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53818205-d34c-46b8-a8fa-85f246fc455a

📥 Commits

Reviewing files that changed from the base of the PR and between 4baefde and 58a36f0.

📒 Files selected for processing (4)
  • grovedb/src/lib.rs
  • grovedb/src/tests/bulk_append_tree_tests.rs
  • grovedb/src/tests/dense_tree_tests.rs
  • grovedb/src/tests/mmr_tree_tests.rs

Comment thread grovedb/src/tests/bulk_append_tree_tests.rs Outdated
QuantumExplorer and others added 2 commits March 10, 2026 13:55
Each direct API function (dense_tree_insert, mmr_tree_append,
bulk_append, commitment_tree_insert) commits subtree data to the
transaction before the parent Merk element update. If the Merk update
fails, subtree data is orphaned in the transaction.

This is the same pattern as other direct GroveDB operations — callers
are expected to rollback the transaction on error. The batch path
(preprocess_*_ops) avoids this via a shared StorageBatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The doc comment incorrectly stated this function was "used by batch
preprocessing". In reality, batch preprocessing (preprocess_commitment_tree_ops)
performs its own inline processing with a shared StorageBatch and never calls
this function. This function is only used by the direct API and
apply_operations_without_batching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed

BulkAppendTree compaction writes MMR nodes via MmrKeySize::U32 (4-byte
tagged keys), not U64 (8-byte). The test was probing with an 8-byte key
that would never match, causing a false pass. Use the correct 4-byte
key format: 0x8000_0000u32.to_be_bytes().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
grovedb-bulk-append-tree/src/tree/append.rs (1)

188-188: Consider documenting the clone cost for large overlays.

The self.mmr_overlay.clone() is necessary because get_mmr_root is a &self method and MMR::new_with_overlay takes ownership. For sessions with many compaction cycles, this clone could allocate significant memory on each call.

If get_mmr_root is called frequently, consider caching the root hash after compaction or documenting that callers should minimize repeated calls during large sessions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@grovedb-bulk-append-tree/src/tree/append.rs` at line 188, get_mmr_root
currently calls MMR::new_with_overlay(mmr_size, &mmr_store,
self.mmr_overlay.clone()), which forces cloning potentially-large overlays on
every &self call; to fix, either change get_mmr_root to take self by value or
&mut self so you can move or reuse the overlay, add a cached root field updated
after compactions and return the cached value when valid, or document the clone
cost prominently in the get_mmr_root API docs and call sites; locate uses of
get_mmr_root, MMR::new_with_overlay, and self.mmr_overlay.clone to implement the
chosen approach and update callers/tests accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@grovedb-bulk-append-tree/src/tree/append.rs`:
- Line 188: get_mmr_root currently calls MMR::new_with_overlay(mmr_size,
&mmr_store, self.mmr_overlay.clone()), which forces cloning potentially-large
overlays on every &self call; to fix, either change get_mmr_root to take self by
value or &mut self so you can move or reuse the overlay, add a cached root field
updated after compactions and return the cached value when valid, or document
the clone cost prominently in the get_mmr_root API docs and call sites; locate
uses of get_mmr_root, MMR::new_with_overlay, and self.mmr_overlay.clone to
implement the chosen approach and update callers/tests accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 766ec36d-85e0-44de-8774-31869c5fb267

📥 Commits

Reviewing files that changed from the base of the PR and between 58a36f0 and 1dc465b.

📒 Files selected for processing (6)
  • grovedb-bulk-append-tree/src/tree/append.rs
  • grovedb-dense-fixed-sized-merkle-tree/src/tree.rs
  • grovedb/src/operations/bulk_append_tree.rs
  • grovedb/src/operations/commitment_tree.rs
  • grovedb/src/operations/dense_tree.rs
  • grovedb/src/operations/mmr_tree.rs

@QuantumExplorer
QuantumExplorer merged commit 0f7faf5 into develop Mar 10, 2026
13 checks passed
@QuantumExplorer
QuantumExplorer deleted the fix/specialized-trees-use-transactional-context branch March 10, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant