Skip to content

Add merkle tree interfaces for reading & writing#510

Open
malcolmgreaves wants to merge 1 commit into
mainfrom
mg/merkle_dyn_interfaces
Open

Add merkle tree interfaces for reading & writing#510
malcolmgreaves wants to merge 1 commit into
mainfrom
mg/merkle_dyn_interfaces

Conversation

@malcolmgreaves
Copy link
Copy Markdown
Collaborator

New interface traits: MerkleReader, MerkleWriter, and unifying MerkleStore.

MerkleStore
A MerkleStore is just a MerkleReader + MerkleWriter where their Error type
is the same. Includes a blanket impl. for any type that has a reader & writer impl.
with equal Error types.

MerkleReader
Can retrieve Merkle tree nodes from storage given a hash. Supports existence
checks, retrieving the node, and retrieving a node's children, if applicable.

MerkleWriter
Produces a MerkleWriteSession that allows for writing arbitrary Merkle tree nodes
to the underlying physical store. Within the write session, writing a node creates a
new NodeWriteSession instance. This instance can write a node as well as a node's
children. Both session types have finish, which ensures their writes go to the
underlying physical store.

These tiered write sessions match how the Oxen code writes Merkle tree nodes.
Additionally, this design allows for implementations to be eager, buffer writes in-
memory, or provide transaction semantics.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

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 object-safe Merkle read/write traits and types (MerkleReader, MerkleWriter, MerkleNodeRecord, MerkleStore) and refactors MerkleNodeDB/file-path APIs to accept repository paths (&Path) with widespread call-site updates; several MerkleNodeDB methods/visibility tightened.

Changes

Merkle API: reader/writer/store

Layer / File(s) Summary
Data Shape / Exports
crates/lib/src/model/merkle_tree.rs
Public submodules merkle_reader and merkle_writer added; MerkleReader and MerkleWriter re-exported; new composite pub trait MerkleStore: MerkleReader + MerkleWriter {} with blanket impl for T: MerkleReader + MerkleWriter + ?Sized.
Reader Interface
crates/lib/src/model/merkle_tree/merkle_reader.rs
Adds pub trait MerkleReader: Send + Sync with exists, get_node, get_children; adds pub struct MerkleNodeRecord with constructor, accessors, and into_node.
Writer Interface
crates/lib/src/model/merkle_tree/merkle_writer.rs
Adds pub trait MerkleWriter: Send + Sync plus object-safe session traits MerkleWriteSession and NodeWriteSession supporting begin, create_node, add_child, and consuming finish methods returning Result<_, OxenError>.

MerkleNodeDB and call-site migration to repo paths

Layer / File(s) Summary
API Signature / Helpers
crates/lib/src/core/db/merkle_node/merkle_node_db.rs
node_db_path changed to pub(crate) fn node_db_path(repo_path: &Path, hash: &MerkleHash) -> PathBuf; open_read_only, open_read_write, exists refactored to pub(crate) and now accept repo_path: &Path; internal helpers and some method visibilities tightened; open now requires/stores node_id.
Call-site Wiring
crates/lib/src/core/v_latest/..., crates/lib/src/core/v_old/..., crates/lib/src/model/merkle_tree/node/..., crates/lib/src/repositories/..., crates/lib/src/command/migrate/..., crates/lib/src/repositories/tree.rs
Replaces many MerkleNodeDB::open_*/exists/node_db_path calls to pass &repo.path or &repository.path / borrowed hash where appropriate; preserves existing control flow and behavior.
Migrations / Consumers
crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs, crates/lib/src/core/v_latest/commits.rs, .../entries.rs, .../index/commit_merkle_tree.rs, .../v0_19_0/index/commit_merkle_tree.rs, .../node/merkle_tree_node.rs, .../commit_writer.rs, .../tree.rs
Updated call sites in migration, commit creation, traversal, node loading, commit writing, and node compression to use the new repo-path-based MerkleNodeDB APIs; no behavioral changes beyond argument form and visibility adjustments.

Sequence Diagram

sequenceDiagram
    participant Client
    participant MerkleWriter
    participant MerkleWriteSession
    participant NodeWriteSession
    participant Backend

    Client->>MerkleWriter: begin()
    MerkleWriter-->>Client: Box<dyn MerkleWriteSession>
    Client->>MerkleWriteSession: create_node(node, parent_id)
    MerkleWriteSession-->>Client: Box<dyn NodeWriteSession>
    Client->>NodeWriteSession: add_child(child)
    Client->>NodeWriteSession: finish() (consumes Box)
    NodeWriteSession->>Backend: persist node data
    Client->>MerkleWriteSession: finish() (consumes Box)
    MerkleWriteSession->>Backend: commit accumulated writes
    Backend-->>Client: Result (Ok / Err)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Oxen-AI/Oxen#470: Modifies MerkleNodeDB surface and related call sites (related API refactors).
  • Oxen-AI/Oxen#406: Changes TMerkleTreeNode object-safety and serialization that intersect with the new writer API using &dyn TMerkleTreeNode.
  • Oxen-AI/Oxen#457: Introduces MerkleReader/MerkleWriter abstractions and a combined MerkleStore trait, closely related conceptually and at the API level.

Suggested reviewers

  • CleanCut
  • jcelliott
  • rpschoenburg

Poem

🐰 I hopped through hashes, leaves, and root so neat,
I boxed sessions up where writers and readers meet,
Children joined nodes, finished with a thunk,
Paths now point to disk with a tidy trunk,
A rabbit cheers — the Merkle tree's complete.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add merkle tree interfaces for reading & writing' accurately summarizes the main change: introducing new MerkleReader and MerkleWriter interface traits.
Description check ✅ Passed The description is well-detailed and directly relates to the changeset, explaining the three new traits (MerkleReader, MerkleWriter, MerkleStore), their purposes, and design rationale.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mg/merkle_dyn_interfaces

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/lib/src/model/merkle_tree/merkle_reader.rs`:
- Around line 22-29: The get_children API currently returns
Result<Vec<(MerkleHash, MerkleTreeNode)>, OxenError> which conflates "missing
node" with "no children"; change the trait signature for get_children to return
Result<Option<Vec<(MerkleHash, MerkleTreeNode)>>, OxenError> (or alternatively
return a dedicated NotFound OxenError) so callers can distinguish absence from
an empty child list, then update all implementations of
MerkleReader::get_children and any callers to handle the Option (treat None as
missing hash and Some(vec![]) as a valid leaf/empty directory), and update the
doc comment above get_children to describe the new semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43ddd224-ef75-4f9d-8fc0-0f6368a3f7f1

📥 Commits

Reviewing files that changed from the base of the PR and between 8e2a152 and acfd963.

📒 Files selected for processing (3)
  • crates/lib/src/model/merkle_tree.rs
  • crates/lib/src/model/merkle_tree/merkle_reader.rs
  • crates/lib/src/model/merkle_tree/merkle_writer.rs

Comment thread crates/lib/src/model/merkle_tree/merkle_reader.rs Outdated
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch from acfd963 to c0a0819 Compare May 1, 2026 17:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/lib/src/model/merkle_tree/merkle_writer.rs`:
- Around line 11-19: The begin method's returned session lifetime is not tied to
the self borrow; change the signature of fn begin to borrow self with the same
lifetime parameter (i.e. make it fn begin<'a>(&'a self) -> Result<Box<dyn
MerkleWriteSession + 'a>, OxenError>) so the Box<dyn MerkleWriteSession + 'a>
cannot outlive the &self borrow, matching the documented contract and the
pattern used by MerkleWriteSession::create_node.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16bbfaf2-593b-4131-a6de-17994a29271a

📥 Commits

Reviewing files that changed from the base of the PR and between acfd963 and c0a0819.

📒 Files selected for processing (3)
  • crates/lib/src/model/merkle_tree.rs
  • crates/lib/src/model/merkle_tree/merkle_reader.rs
  • crates/lib/src/model/merkle_tree/merkle_writer.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/lib/src/model/merkle_tree.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/lib/src/model/merkle_tree/merkle_reader.rs

Comment thread crates/lib/src/model/merkle_tree/merkle_writer.rs Outdated
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch 3 times, most recently from 9a95bc3 to 7cf2bb8 Compare May 4, 2026 21:51
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs (1)

141-141: ⚡ Quick win

Unify Merkle write DB target path for rewritten nodes

Line 141 and Line 194 write using &old_repo.path, while Line 257 writes using &new_repo.path. This currently works because both repos share the same path, but it bakes in an implicit assumption. Use one write target consistently (new_repo.path) to avoid split-tree writes if these repos ever diverge.

Proposed diff
-    let mut root_commit_db =
-        MerkleNodeDB::open_read_write(&old_repo.path, &commit_node, root_node.parent_id)?;
+    let mut root_commit_db =
+        MerkleNodeDB::open_read_write(&new_repo.path, &commit_node, root_node.parent_id)?;
...
-                let mut dir_db =
-                    MerkleNodeDB::open_read_write(&old_repo.path, &dir, node.parent_id)?;
+                let mut dir_db =
+                    MerkleNodeDB::open_read_write(&new_repo.path, &dir, node.parent_id)?;

Also applies to: 193-195, 257-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs`
at line 141, The Merkle write DB target is inconsistent: calls to
MerkleNodeDB::open_read_write are using &old_repo.path in some places (e.g., the
invocation around commit_node/root_node.parent_id and the block at lines
~193-195) while another call uses &new_repo.path (~257-261); change the write
target for all rewritten-node writes to use &new_repo.path so all
MerkleNodeDB::open_read_write(...) invocations that are writing rewritten nodes
consistently pass &new_repo.path (update the two calls that currently pass
&old_repo.path to pass &new_repo.path instead).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs`:
- Line 141: The Merkle write DB target is inconsistent: calls to
MerkleNodeDB::open_read_write are using &old_repo.path in some places (e.g., the
invocation around commit_node/root_node.parent_id and the block at lines
~193-195) while another call uses &new_repo.path (~257-261); change the write
target for all rewritten-node writes to use &new_repo.path so all
MerkleNodeDB::open_read_write(...) invocations that are writing rewritten nodes
consistently pass &new_repo.path (update the two calls that currently pass
&old_repo.path to pass &new_repo.path instead).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 90fce15b-cab7-47c5-bff0-d9d14383696a

📥 Commits

Reviewing files that changed from the base of the PR and between a1b9ba7 and 7cf2bb8.

📒 Files selected for processing (12)
  • crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs
  • crates/lib/src/core/db/merkle_node/merkle_node_db.rs
  • crates/lib/src/core/v_latest/commits.rs
  • crates/lib/src/core/v_latest/entries.rs
  • crates/lib/src/core/v_latest/index/commit_merkle_tree.rs
  • crates/lib/src/core/v_old/v0_19_0/index/commit_merkle_tree.rs
  • crates/lib/src/model/merkle_tree.rs
  • crates/lib/src/model/merkle_tree/merkle_reader.rs
  • crates/lib/src/model/merkle_tree/merkle_writer.rs
  • crates/lib/src/model/merkle_tree/node/merkle_tree_node.rs
  • crates/lib/src/repositories/commits/commit_writer.rs
  • crates/lib/src/repositories/tree.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/lib/src/model/merkle_tree/merkle_reader.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/lib/src/model/merkle_tree.rs
  • crates/lib/src/model/merkle_tree/merkle_writer.rs

@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch from 7cf2bb8 to 5faad04 Compare May 4, 2026 21:56
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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)
crates/lib/src/core/db/merkle_node/merkle_node_db.rs (1)

472-482: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset children_file cursor before reading in map.

map reads with read_to_end from the current cursor position. If map is called more than once on the same handle, subsequent calls can read 0 bytes and fail while parsing offsets.

Suggested fix
         let Some(children_file) = self.children_file.as_mut() else {
             return Err(MerkleDbError::WriteBeforeOpen);
         };
+        children_file.seek(SeekFrom::Start(0))?;

         // Parse the node parent id
         let data_type = MerkleTreeNodeType::from_u8(lookup.data_type)?;
         let parent_id = MerkleTreeNode::deserialize_id(&lookup.data, data_type)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lib/src/core/db/merkle_node/merkle_node_db.rs` around lines 472 - 482,
The children_file cursor must be reset before reading because read_to_end reads
from the current position; update the code in the method that calls
children_file.read_to_end (the map logic surrounding children_file,
MerkleTreeNodeType::from_u8 and MerkleTreeNode::deserialize_id) to seek the
children_file to the start (using Seek::seek with SeekFrom::Start(0)) before
calling read_to_end, propagate any seek errors (like other io errors) and ensure
the file handle implements Seek by importing std::io::Seek/SeekFrom if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/lib/src/core/db/merkle_node/merkle_node_db.rs`:
- Around line 305-309: The open function currently calls
util::fs::create_dir_all(&path) unconditionally, which mutates the filesystem
even when opened with read_only=true; change the logic in fn open
(MerkleNodeDb::open) to only create directories when read_only is false (i.e.,
if !path.exists() && !read_only then call util::fs::create_dir_all(&path) and
map_err(MerkleDbError::dir_create)), and for the read_only && !path.exists()
case return a suitable MerkleDbError (or propagate an existing error) instead of
creating directories; ensure references to create_dir_all and
MerkleDbError::dir_create are updated accordingly.

---

Outside diff comments:
In `@crates/lib/src/core/db/merkle_node/merkle_node_db.rs`:
- Around line 472-482: The children_file cursor must be reset before reading
because read_to_end reads from the current position; update the code in the
method that calls children_file.read_to_end (the map logic surrounding
children_file, MerkleTreeNodeType::from_u8 and MerkleTreeNode::deserialize_id)
to seek the children_file to the start (using Seek::seek with
SeekFrom::Start(0)) before calling read_to_end, propagate any seek errors (like
other io errors) and ensure the file handle implements Seek by importing
std::io::Seek/SeekFrom if needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b3dffc1a-a860-4264-893c-7a6103f79a57

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf2bb8 and 5faad04.

📒 Files selected for processing (12)
  • crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs
  • crates/lib/src/core/db/merkle_node/merkle_node_db.rs
  • crates/lib/src/core/v_latest/commits.rs
  • crates/lib/src/core/v_latest/entries.rs
  • crates/lib/src/core/v_latest/index/commit_merkle_tree.rs
  • crates/lib/src/core/v_old/v0_19_0/index/commit_merkle_tree.rs
  • crates/lib/src/model/merkle_tree.rs
  • crates/lib/src/model/merkle_tree/merkle_reader.rs
  • crates/lib/src/model/merkle_tree/merkle_writer.rs
  • crates/lib/src/model/merkle_tree/node/merkle_tree_node.rs
  • crates/lib/src/repositories/commits/commit_writer.rs
  • crates/lib/src/repositories/tree.rs
✅ Files skipped from review due to trivial changes (3)
  • crates/lib/src/model/merkle_tree.rs
  • crates/lib/src/repositories/tree.rs
  • crates/lib/src/core/v_latest/index/commit_merkle_tree.rs
🚧 Files skipped from review as they are similar to previous changes (8)
  • crates/lib/src/core/v_latest/entries.rs
  • crates/lib/src/command/migrate/m20250111083535_add_child_counts_to_nodes.rs
  • crates/lib/src/repositories/commits/commit_writer.rs
  • crates/lib/src/core/v_old/v0_19_0/index/commit_merkle_tree.rs
  • crates/lib/src/model/merkle_tree/merkle_reader.rs
  • crates/lib/src/model/merkle_tree/merkle_writer.rs
  • crates/lib/src/model/merkle_tree/node/merkle_tree_node.rs
  • crates/lib/src/core/v_latest/commits.rs

Comment thread crates/lib/src/core/db/merkle_node/merkle_node_db.rs
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch 3 times, most recently from 6827c1e to b0d9aa5 Compare May 4, 2026 22:25
@malcolmgreaves malcolmgreaves changed the base branch from main to mg/refactor_use_path_merklenodedb May 5, 2026 00:48
@malcolmgreaves
Copy link
Copy Markdown
Collaborator Author

NOTE: Stacked PR! Must merge #523 before merging.

@malcolmgreaves malcolmgreaves force-pushed the mg/refactor_use_path_merklenodedb branch from b009a7f to 6736d09 Compare May 5, 2026 00:53
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch from b0d9aa5 to 16ce636 Compare May 5, 2026 00:53
@malcolmgreaves malcolmgreaves force-pushed the mg/refactor_use_path_merklenodedb branch from 6736d09 to c53b23d Compare May 5, 2026 01:17
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch from 16ce636 to 5d23577 Compare May 5, 2026 01:18
Base automatically changed from mg/refactor_use_path_merklenodedb to main May 5, 2026 17:08
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch from 5d23577 to 173f569 Compare May 5, 2026 17:09
Copy link
Copy Markdown
Contributor

@CleanCut CleanCut left a comment

Choose a reason for hiding this comment

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

👍🏻 This implementation can work.

🎁 There are some opportunities for simplification that you should consider:

  • Just like with the VersionStore trait, the trait design here can be simplified from 5 traits to just one: MerkleStore
    • A single trait design can still allow for implementations to be eager, buffer writes in-memory, and provide transaction semantics if necessary.
    • The trait implementation can internally handle transaction semantics within its methods, rather than producing nested types representing various sessions that the user must create/use/finalize manually.
    • Inner transactions/locking can be handled with private fields on the implementation struct, and happen within create_node, add_child, (etc.) implementations.
    • The current design allows you to drop the structs without calling finalize(). It would be easier to avoid problems if whatever finalize() is supposed to do is folded into the internal transaction implementation.
  • MerkleNodeRecord is redundant (see comment below)

Comment thread crates/lib/src/model/merkle_tree/merkle_reader.rs Outdated
Comment thread crates/lib/src/model/merkle_tree/merkle_reader.rs
Comment thread crates/lib/src/model/merkle_tree/merkle_reader.rs
Comment thread crates/lib/src/model/merkle_tree/merkle_writer.rs
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch 3 times, most recently from 8ff64ae to 40bfdbc Compare May 7, 2026 23:19
New interface traits: `MerkleReader`, `MerkleWriter`, and unifying `MerkleStore`.

**`MerkleStore`**
A `MerkleStore` is just a `MerkleReader` + `MerkleWriter` where their `Error` type
is the same. Includes a blanket impl. for any type that has a reader & writer impl.
with equal `Error` types.

**`MerkleReader`**
Can retrieve Merkle tree nodes from storage given a hash. Supports existence
checks, retrieving the node, and retrieving a node's children, if applicable.

**`MerkleWriter`**
Produces a `MerkleWriteSession` that allows for writing arbitrary Merkle tree nodes
to the underlying physical store. Within the write session, writing a node creates a
new `NodeWriteSession` instance. This instance can write a node as well as a node's
children. Both session types have `finish`, which ensures their writes go to the
underlying physical store.

These tiered write sessions match how the Oxen code writes Merkle tree nodes.
Additionally, this design allows for implementations to be eager, buffer writes in-
memory, or provide transaction semantics.
@malcolmgreaves malcolmgreaves force-pushed the mg/merkle_dyn_interfaces branch from 40bfdbc to ef3729b Compare May 7, 2026 23:20
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.

2 participants