-
Notifications
You must be signed in to change notification settings - Fork 11
feat: model phase0 Validator as struct #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a5608d4
e6f36ae
59dcc5e
9a3b230
7c4b03d
f0738c7
ef21669
6f0c12b
49f2caf
640b420
91b030a
ef59c0b
8f64421
2ca264b
deab0b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,9 @@ const Depth = @import("hashing").Depth; | |
| const Gindex = @import("gindex.zig").Gindex; | ||
|
|
||
| hash: [32]u8, | ||
| // `left` and `right` are child node IDs for leaf/zero/regular branch nodes. | ||
| // For branch-struct nodes, `left` stores the high pointer bits and `right` | ||
| // stores the low pointer bits of the packed BranchStructRef pointer. | ||
| left: Id, | ||
| right: Id, | ||
| state: State, | ||
|
|
@@ -34,8 +37,8 @@ pub const Error = error{ | |
| /// | ||
| /// `[1, next_free]` | ||
| /// | ||
| /// If the high bit is not set, the next two bits determine the `node_type` | ||
| /// The following 29 bits are used for the `ref_count`. | ||
| /// If the high bit is not set, the next 3 bits determine the `node_type` | ||
| /// The following 28 bits are used for the `ref_count`. | ||
| /// | ||
| /// `[0, node_type, ref_count]` | ||
| pub const State = enum(u32) { | ||
|
|
@@ -45,14 +48,16 @@ pub const State = enum(u32) { | |
|
|
||
| pub const max_next_free = 0x7FFFFFFF; | ||
|
|
||
| // four types of nodes | ||
| const node_type = 0x60000000; | ||
| // five types of nodes, use 3 bits | ||
| const node_type = 0x70000000; | ||
| pub const zero: State = @enumFromInt(0x00000000); | ||
| pub const leaf: State = @enumFromInt(0x20000000); | ||
| pub const branch_lazy: State = @enumFromInt(0x40000000); | ||
| pub const branch_computed: State = @enumFromInt(0x60000000); | ||
| pub const leaf: State = @enumFromInt(0x10000000); | ||
| pub const branch_lazy: State = @enumFromInt(0x20000000); | ||
| pub const branch_computed: State = @enumFromInt(0x30000000); | ||
| pub const branch_struct_lazy: State = @enumFromInt(0x40000000); | ||
| pub const branch_struct_computed: State = @enumFromInt(0x50000000); | ||
|
|
||
| pub const max_ref_count = 0x1FFFFFFF; | ||
| pub const max_ref_count = 0x0FFFFFFF; | ||
|
|
||
| pub inline fn isFree(node: State) bool { | ||
| return @intFromEnum(node) & @intFromEnum(free) != 0; | ||
|
|
@@ -75,7 +80,8 @@ pub const State = enum(u32) { | |
| } | ||
|
|
||
| pub inline fn isBranch(node: State) bool { | ||
| return @intFromEnum(node) & @intFromEnum(branch_lazy) != 0; | ||
| const nt = @intFromEnum(node) & node_type; | ||
| return nt == @intFromEnum(branch_lazy) or nt == @intFromEnum(branch_computed); | ||
| } | ||
|
|
||
| pub inline fn isBranchLazy(node: State) bool { | ||
|
|
@@ -86,10 +92,27 @@ pub const State = enum(u32) { | |
| return @intFromEnum(node) & node_type == @intFromEnum(branch_computed); | ||
| } | ||
|
|
||
| pub inline fn isBranchStruct(node: State) bool { | ||
| const nt = @intFromEnum(node) & node_type; | ||
| return nt == @intFromEnum(branch_struct_lazy) or nt == @intFromEnum(branch_struct_computed); | ||
| } | ||
|
|
||
| pub inline fn isBranchStructLazy(node: State) bool { | ||
| return @intFromEnum(node) & node_type == @intFromEnum(branch_struct_lazy); | ||
| } | ||
|
|
||
| pub inline fn isBranchStructComputed(node: State) bool { | ||
| return @intFromEnum(node) & node_type == @intFromEnum(branch_struct_computed); | ||
| } | ||
|
|
||
| pub inline fn setBranchComputed(node: *State) void { | ||
| node.* = @enumFromInt(@intFromEnum(node.*) | @intFromEnum(branch_computed)); | ||
| } | ||
|
|
||
| pub inline fn setBranchStructComputed(node: *State) void { | ||
| node.* = @enumFromInt(@intFromEnum(node.*) | @intFromEnum(branch_struct_computed)); | ||
| } | ||
|
|
||
| pub inline fn initRefCount(node: State) State { | ||
| return node; | ||
| } | ||
|
|
@@ -123,6 +146,15 @@ pub const Pool = struct { | |
| nodes: std.MultiArrayList(Node).Slice, | ||
| next_free_node: Id, | ||
|
|
||
| pub const BranchStructRef = struct { | ||
| ptr: *anyopaque, | ||
| get_root: *const fn (ptr: *const anyopaque, out: *[32]u8) void, | ||
| // Proof generation may need a traversable tree even though branch-struct | ||
| // nodes are normally opaque to left/right navigation. | ||
| to_tree: *const fn (ptr: *const anyopaque, pool: *Pool) Error!Id, | ||
| deinit: *const fn (ptr: *anyopaque, allocator: Allocator) void, | ||
| }; | ||
|
|
||
| pub const free_bit: u32 = 0x80000000; | ||
| pub const max_ref_count: u32 = 0x7FFFFFFF; | ||
|
|
||
|
|
@@ -246,6 +278,53 @@ pub const Pool = struct { | |
| return node_id; | ||
| } | ||
|
|
||
| /// The pool allocates and owns a clone of `ptr`; the caller retains ownership of its data. | ||
| pub fn createBranchStruct(self: *Pool, comptime T: type, ptr: *const T) Error!Id { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The style guide (rule 51) requires asserting all function arguments. The References
|
||
| const cloned = try T.init(self.allocator, ptr); | ||
| errdefer @constCast(cloned).deinit(self.allocator); | ||
|
|
||
| const branch_struct_ref = try self.allocator.create(BranchStructRef); | ||
| errdefer self.allocator.destroy(branch_struct_ref); | ||
|
|
||
| branch_struct_ref.* = .{ | ||
| .ptr = @ptrCast(@constCast(cloned)), | ||
| .get_root = struct { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Pointer split across Encoding a heap pointer into two Alternative: a Not blocking since the current approach works correctly on 64-bit, but worth considering for maintainability.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree if we go from the current/old design point of view. With the new approach of this PR, it changes semantic of node storage: it could be either left/right nodes or pointer to some data
good call on it. The only getLeft()/getRight() calls that affect is proof, which is addressed in 91b030a |
||
| fn call(ptr_erased: *const anyopaque, out: *[32]u8) void { | ||
| const typed_ptr: *const T = @ptrCast(@alignCast(ptr_erased)); | ||
| typed_ptr.getRoot(out); | ||
| } | ||
| }.call, | ||
| .to_tree = struct { | ||
| fn call(ptr_erased: *const anyopaque, pool: *Pool) Error!Id { | ||
| const typed_ptr: *const T = @ptrCast(@alignCast(ptr_erased)); | ||
| return try typed_ptr.toTree(pool); | ||
| } | ||
| }.call, | ||
| .deinit = struct { | ||
| fn call(ptr_erased: *anyopaque, allocator: Allocator) void { | ||
| const typed_ptr: *T = @ptrCast(@alignCast(ptr_erased)); | ||
| typed_ptr.deinit(allocator); | ||
| } | ||
| }.call, | ||
| }; | ||
|
|
||
| const node_id = try self.create(); | ||
| const ptr_usize = @intFromPtr(branch_struct_ref); | ||
| // branch-struct nodes do not store child node IDs in left/right. Instead we | ||
| // pack the BranchStructRef pointer into those fields and rely on noChild() | ||
| // to keep generic traversal from treating them as normal branches. | ||
| const right_ptr_value: u32 = @intCast(ptr_usize & 0xFFFFFFFF); | ||
| self.nodes.items(.right)[@intFromEnum(node_id)] = @enumFromInt(right_ptr_value); | ||
| if (comptime @sizeOf(usize) == 8) { | ||
| const left_ptr_value: u32 = @intCast(ptr_usize >> 32); | ||
| self.nodes.items(.left)[@intFromEnum(node_id)] = @enumFromInt(left_ptr_value); | ||
| } else { | ||
| self.nodes.items(.left)[@intFromEnum(node_id)] = @enumFromInt(0); | ||
| } | ||
| self.nodes.items(.state)[@intFromEnum(node_id)] = State.branch_struct_lazy.initRefCount(); | ||
| return node_id; | ||
| } | ||
|
|
||
| /// Allocates nodes into the pool. | ||
| /// | ||
| /// All nodes are allocated with refcount=0. | ||
|
|
@@ -333,6 +412,39 @@ pub const Pool = struct { | |
| _ = try states[@intFromEnum(node_id)].incRefCount(); | ||
| } | ||
|
|
||
| pub fn getStructPtr(self: *Pool, node_id: Id, comptime T: type) Error!*const T { | ||
| const state = self.nodes.items(.state)[@intFromEnum(node_id)]; | ||
| if (!state.isBranchStruct()) { | ||
| return Error.InvalidNode; | ||
| } | ||
| const struct_ref = self.getBranchStructRefUnsafe(node_id); | ||
| const ptr: *const T = @ptrCast(@alignCast(struct_ref.ptr)); | ||
| return ptr; | ||
| } | ||
|
|
||
| pub fn getBranchStructRefUnsafe(self: *Pool, node_id: Id) *BranchStructRef { | ||
| const left_ptr_value: u32 = @intFromEnum(self.nodes.items(.left)[@intFromEnum(node_id)]); | ||
| const right_ptr_value: u32 = @intFromEnum(self.nodes.items(.right)[@intFromEnum(node_id)]); | ||
| // This reverses the packing performed in createBranchStruct(). Callers must | ||
| // ensure node_id is a branch-struct node before decoding pointer bits. | ||
| const ptr_int: usize = if (comptime @sizeOf(usize) == 8) | ||
| @as(u64, left_ptr_value) << 32 | @as(u64, right_ptr_value) | ||
| else | ||
| right_ptr_value; | ||
| return @ptrFromInt(ptr_int); | ||
| } | ||
|
|
||
| pub fn materializeBranchStruct(self: *Pool, node_id: Id) Error!Id { | ||
| const state = self.nodes.items(.state)[@intFromEnum(node_id)]; | ||
| if (!state.isBranchStruct()) { | ||
| return Error.InvalidNode; | ||
| } | ||
| // Materialization is only for specialized flows such as proof generation. | ||
| // Generic tree navigation should keep branch-struct nodes opaque. | ||
| const struct_ref = self.getBranchStructRefUnsafe(node_id); | ||
| return try struct_ref.to_tree(struct_ref.ptr, self); | ||
| } | ||
|
|
||
| pub fn unref(self: *Pool, node_id: Id) void { | ||
| const states = self.nodes.items(.state); | ||
| const lefts = self.nodes.items(.left); | ||
|
|
@@ -381,6 +493,13 @@ pub const Pool = struct { | |
| } else { | ||
| current = null; | ||
| } | ||
|
|
||
| if (states[@intFromEnum(id)].isBranchStruct()) { | ||
| const struct_ref = self.getBranchStructRefUnsafe(id); | ||
| struct_ref.deinit(struct_ref.ptr, self.allocator); | ||
| self.allocator.destroy(struct_ref); | ||
| } | ||
|
|
||
| // Return the node to the free list | ||
| states[@intFromEnum(id)] = State.initNextFree(self.next_free_node); | ||
| self.next_free_node = id; | ||
|
|
@@ -396,7 +515,8 @@ pub const Id = enum(u32) { | |
|
|
||
| /// Returns true if navigation to the child node is not possible | ||
| pub inline fn noChild(node_id: Id, state: State) bool { | ||
| return state.isLeaf() or @intFromEnum(node_id) == 0; | ||
| // branch-struct nodes keep packed pointer bits in left/right, not child IDs. | ||
| return state.isLeaf() or state.isBranchStruct() or @intFromEnum(node_id) == 0; | ||
| } | ||
|
|
||
| /// Returns the root hash of the tree, computing any lazy branches as needed. | ||
|
|
@@ -410,6 +530,12 @@ pub const Id = enum(u32) { | |
| hashOne(hash, left, right); | ||
| state.setBranchComputed(); | ||
| } | ||
|
|
||
| if (state.isBranchStructLazy()) { | ||
| const struct_ref = pool.getBranchStructRefUnsafe(node_id); | ||
| struct_ref.get_root(struct_ref.ptr, hash); | ||
| state.setBranchStructComputed(); | ||
| } | ||
| return hash; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.