Skip to content

Cayman/232 review - #244

Closed
wemeetagain wants to merge 13 commits into
te/container_node_struct_2from
cayman/232-review
Closed

Cayman/232 review#244
wemeetagain wants to merge 13 commits into
te/container_node_struct_2from
cayman/232-review

Conversation

@wemeetagain

@wemeetagain wemeetagain commented Mar 16, 2026

Copy link
Copy Markdown
Member

Opening this PR for discussion and visibility into the whole review. Will PR into #139 first with the relevant pieces.

  • The gains from flat Validators is undeniable and necessary, but I don't like the way it changes the pool and the guarantees we have there. I prefer an approach that does the same at a different layer.
  • Nice gains from container tree view tuple, ability to cache extra fields (like length) in other tree views. But imo there's still a nicer way to have code reuse of the tree view state between non-container tree views. Also we can use std.StaticBitSet for container tree view changed tracking.

Below is a more verbose and prescriptive explanation of above:

  • StructContainerTreeView was leaking a view-level optimization into the node pool. The pool gained a 5th node type (branch_struct), type-erased vtables, and pointer-stuffing in left/right fields — all so one tree view could store flat structs. This
    cut the ref count budget in half and broke the invariant that left/right are always pool indices.

    This branch pulls the struct data back up to the view layer where it belongs. StructContainerTreeView now owns its value directly — reads from the merkle tree at init, writes back at commit. The pool stays pure: zero/leaf/branch.

    The pool is an arena of value-typed nodes addressed by 32-bit indices. Every node is the same size, the same shape — a hash, a left index, a right index, a state word. No pointers, no indirection, no type erasure. You can walk, hash, diff,
    serialize, and ref-count the entire tree with just integer arithmetic on a flat array. Cache-friendly, trivially serializable, no lifetime tracking beyond ref counts.

    branch_struct broke that. A "node" could now secretly be a pointer to a heap-allocated, type-erased object with its own vtable. The pool had to ask "is this a real node or a pointer?" before every operation — unref, getRoot, noChild. The flat
    array wasn't flat anymore; some slots were trampolines to somewhere else on the heap.

    Keeping the pool pure means every node is interchangeable. No special cases in traversal, no hidden allocations, no pointer-chasing. The complexity of "some containers are stored as flat structs" lives entirely in the view layer, which is where
    type-aware logic belongs. The pool just does trees.

  • Along the way, the scattered fields duplicated across every chunk-based view (allocator, pool, root, children_nodes, changed) are consolidated into a concrete TreeViewState struct, replacing duck-typed anytype dispatch. And
    ContainerTreeView.changed becomes a StaticBitSet — zero allocation for a comptime-known field count.

    The old BaseTreeView was the right idea — share the common state — but it was a heap-allocated indirection (*TreeViewData). The refactor that removed it (PR refactor(ssz): drop BaseTreeView, use tuple for ContainerTreeView #139) was correct to kill that pointer, but it overcorrected: it scattered the five fields
    into every view type and recovered sharing through ChildNodes — a set of free functions that take anytype and duck-type their way to self.allocator, self.pool, self.root, self.children_nodes, self.changed.

    Duck typing works, but it's invisible. Nothing in the type system says "this struct is a valid ChildNodes target." A typo in a field name compiles fine until someone calls the function. The compiler error points at the ChildNodes utility, not at
    the struct that's missing a field. And you can't look at BasicPackedChunks and see at a glance what interface it satisfies — you have to read every method body and mentally verify the field names match.

    TreeViewState is an embedded value type — not a pointer, not a trait, just a struct stored inline in each view. When you see state: TreeViewState in BasicPackedChunks, you know exactly what state it carries and what operations are available. The
    methods are on a concrete type, so the compiler gives you real errors at the call site. And there's one implementation to read, not a set of generic functions that reconstruct the interface from field names.

    It's also the right boundary. ContainerTreeView doesn't embed TreeViewState because it genuinely has different storage — comptime tuples and fixed arrays instead of hashmaps. The struct makes the split explicit: chunk-based views share
    TreeViewState, container views don't. That distinction was invisible when it was just "some types happen to have the right field names for ChildNodes to work."

twoeths and others added 13 commits March 3, 2026 18:26
- Remove branch_struct_lazy/computed node types from Node.Pool
- Restore 2-bit type field / 29-bit ref count
- Remove BranchStructRef, createBranchStruct, getStructPtr
- StructContainerType.tree delegates to FixedContainerType.tree
- StructContainerTreeView stores owned T value instead of borrowing
  from pool; init reads from merkle tree, commit writes back

Pool stays pure: only zero/leaf/branch nodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add TreeViewState struct with concrete typed methods for the common
state (allocator, pool, root, children_nodes, changed) shared by all
chunk-based tree views. Replaces duck-typed ChildNodes anytype dispatch.

- BasicPackedChunks, CompositeChunks, BitArray embed TreeViewState
- Update all callers (list/array/bit views) to access via .state.*
- Delete child_nodes.zig utility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the internal architecture of tree views and the node pool. The primary motivation is to enforce a clear separation of concerns: the node pool is now strictly an arena for generic tree nodes, while view-specific optimizations and data management are handled at the view layer. This change enhances the purity and predictability of the node pool, improves type safety and code reuse across various tree view implementations through a new centralized state struct, and optimizes change tracking for container views.

Highlights

  • Node Pool Simplification: The Node.Pool has been refactored to remove the branch_struct node type and all associated logic, simplifying the pool's internal structure and operations. This ensures the pool maintains a pure arena of value-typed nodes without type-erased pointers.
  • Centralized Tree View State: A new TreeViewState struct was introduced to consolidate common state (allocator, pool, root, children_nodes, changed) across chunk-based tree views. This improves code reuse, type safety, and replaces previous duck-typed anytype dispatch.
  • StructContainerTreeView Refactor: The StructContainerTreeView now directly owns and manages its value at the view layer, rather than relying on the Node.Pool to store flat structs. This aligns with the goal of keeping the node pool pure and view-level optimizations within the view layer.
  • Optimized Change Tracking: The ContainerTreeView.changed tracking mechanism has been migrated from std.AutoArrayHashMapUnmanaged to std.StaticBitSet for fields with compile-time known counts, leading to zero allocation for change tracking in these cases.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/persistent_merkle_tree/Node.zig
    • Updated State enum documentation to reflect changes in bit allocation for node_type and ref_count.
    • Removed branch_struct_lazy and branch_struct_computed node types from State enum.
    • Removed isBranchStruct, isBranchStructLazy, isBranchStructComputed, and setBranchStructComputed functions.
    • Removed BranchStructRef struct definition.
    • Removed createBranchStruct, getStructPtr, and getBranchStructRefUnsafe functions.
    • Removed logic related to isBranchStruct from unref and noChild functions.
    • Removed logic related to isBranchStructLazy from getRoot function.
  • src/ssz/tree_view/array_basic.zig
    • Updated references from self.chunks.root and self.chunks.pool to self.chunks.state.root and self.chunks.state.pool respectively.
    • Updated test expectations to reflect the new state field.
  • src/ssz/tree_view/array_composite.zig
    • Updated references from self.chunks.root and self.chunks.pool to self.chunks.state.root and self.chunks.state.pool respectively.
  • src/ssz/tree_view/bit_array.zig
    • Replaced allocator, pool, root, children_nodes, changed fields with a single state: TreeViewState field.
    • Updated init, clone, deinit, commit, clearCache functions to delegate to self.state.
    • Updated calls to getChildNode, setChildNode, clearChildrenNodesCache to use self.state.
    • Updated references to self.pool to self.state.pool.
  • src/ssz/tree_view/bit_list.zig
    • Updated references from self.data.root and self.data.pool to self.data.state.root and self.data.state.pool respectively.
    • Updated test expectations to reflect the new state field.
  • src/ssz/tree_view/bit_vector.zig
    • Updated references from self.data.root and self.data.pool to self.data.state.root and self.data.state.pool respectively.
    • Updated test expectations to reflect the new state field.
  • src/ssz/tree_view/chunks.zig
    • Replaced ChildNodes import with TreeViewState.
    • Replaced allocator, pool, root, children_nodes, changed fields with a single state: TreeViewState field in BasicPackedChunks.
    • Updated init, clone, deinit, commit, clearCache, getChildNode, setChildNode, clearChildrenNodesCache, getLength, setLength functions to delegate to self.state.
    • Updated references to self.pool and self.allocator to self.state.pool and self.state.allocator respectively.
    • Refactored CompositeChunks to use TreeViewState for common state, updating children_data handling during clone and deinit.
  • src/ssz/tree_view/container.zig
    • Changed changed field from std.AutoArrayHashMapUnmanaged(usize, void) to std.StaticBitSet(ST.chunk_count).
    • Updated init, clone, deinit, commit, get, and set functions to use std.StaticBitSet methods (initEmpty, isSet, set).
    • Modified StructContainerTreeView to store original_value directly as T instead of *const T.
    • Updated StructContainerTreeView.init to deserialize the value directly into original_value.
    • Updated StructContainerTreeView.commit to create a new root from the new_value directly, and update original_value.
    • Updated StructContainerTreeView.toValue and serializeIntoBytes to work with the direct original_value.
  • src/ssz/tree_view/list_basic.zig
    • Updated references from self.chunks.root and self.chunks.pool to self.chunks.state.root and self.chunks.state.pool respectively.
    • Updated test expectations to reflect the new state field.
  • src/ssz/tree_view/list_composite.zig
    • Updated references from self.chunks.root and self.chunks.pool to self.chunks.state.root and self.chunks.state.pool respectively.
  • src/ssz/tree_view/utils/child_nodes.zig
    • Removed file, as its functionality has been absorbed into TreeViewState.
  • src/ssz/tree_view/utils/tree_view_state.zig
    • Added new file TreeViewState.zig to encapsulate common state and logic for chunk-based tree views.
  • src/ssz/type/container.zig
    • Removed WrappedT struct and its associated methods (getRoot, init, deinit).
    • Simplified tree constant to directly use FixedCT.tree.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request is a significant and well-executed refactoring. It simplifies the Node.Pool by removing the branch_struct type, which is a great improvement for design purity and maintainability. The introduction of TreeViewState to consolidate common state and logic from various tree views is another excellent change that enhances code structure and type safety. My review includes a couple of suggestions to improve code clarity and further align with the repository's style guide, particularly regarding safety assertions.

Comment on lines 450 to 459
const ptr = try allocator.create(Self);
ptr.* = .{
.allocator = allocator,
.pool = pool,
.root = root,
.original_value = original_value,
.changed = null,
};
errdefer allocator.destroy(ptr);

try ST.tree.toValue(root, pool, &ptr.original_value);

ptr.allocator = allocator;
ptr.pool = pool;
ptr.root = root;
ptr.changed = null;
return ptr;

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.

medium

This init function can be made more readable by separating the value deserialization from the struct initialization. This allows using a single struct literal for initialization, which is generally clearer than field-by-field assignment.

            var original_value: T = undefined;
            try ST.tree.toValue(root, pool, &original_value);

            const ptr = try allocator.create(Self);
            ptr.* = .{
                .allocator = allocator,
                .pool = pool,
                .root = root,
                .original_value = original_value,
                .changed = null,
            };
            return ptr;

Comment on lines +11 to +20
pub const TreeViewState = struct {
allocator: Allocator,
pool: *Node.Pool,
root: Node.Id,

/// cached nodes for faster access of already-visited children
children_nodes: std.AutoHashMapUnmanaged(Gindex, Node.Id),

/// whether the corresponding child node/data has changed since the last update of the root
changed: std.AutoArrayHashMapUnmanaged(Gindex, void),

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.

medium

This new TreeViewState struct is a great abstraction. To further align with the repository's style guide on safety, consider adding assertions to validate function arguments, pre-conditions, and post-conditions. For example, deinit could assert the root is valid, setChildNode could assert the node ID is valid, and commitNodes could assert that the changed map is empty upon completion.

References
  1. The style guide mandates asserting all function arguments, return values, pre/postconditions, and invariants to improve safety. It suggests an average of at least two assertions per function. (link)

@wemeetagain

Copy link
Copy Markdown
Member Author

@wemeetagain
wemeetagain deleted the cayman/232-review branch August 17, 2026 19:40
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