Skip to content

Commit c43d972

Browse files
twoethsclaude
andcommitted
fix: resolve tree view API conflicts from cherry-pick
After cherry-picking "feat: tree view state (#168)", SSZ tree view conflicts were resolved by keeping "ours", which lost new API functions the state_transition code depends on. This commit adds back the missing APIs and fixes all callers. Key changes: - Add missing tree view APIs: hashTreeRoot, fromValue, toValue, getReadonly, getValue, setValue, getFieldRoot, iteratorReadonly across container, array_basic, array_composite, list_basic, list_composite, bit_vector - Fix CompositeChunks.get() to always mark changed (fixes attester_slashing failures where getReadonly cached a child without tracking, then get() returned the cached entry without marking it changed) - Fix CompositeChunks.getAllValuesInto() to initialize variable-type values before toValue (fixes segfault from uninitialized BitList fields) - Fix pointer type mismatches (*TreeView vs **TreeView) across callers - Fix beacon_state field name bugs and return type mismatches - Add @setEvalBranchQuota(20000) for comptime iteration over BeaconState Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 323ecc3 commit c43d972

31 files changed

Lines changed: 1036 additions & 537 deletions

src/ssz/root.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub const ArrayBasicTreeView = tree_view.ArrayBasicTreeView;
4646
pub const ArrayCompositeTreeView = tree_view.ArrayCompositeTreeView;
4747
pub const ListBasicTreeView = tree_view.ListBasicTreeView;
4848
pub const ListCompositeTreeView = tree_view.ListCompositeTreeView;
49+
pub const CloneOpts = @import("tree_view/utils/clone_opts.zig").CloneOpts;
4950

5051
test {
5152
testing.refAllDecls(@This());

src/ssz/tree_view/array_basic.zig

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,27 @@ pub fn ArrayBasicTreeView(comptime ST: type) type {
7272
self.chunks.clearCache();
7373
}
7474

75-
pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void {
75+
pub fn hashTreeRootInto(self: *Self, out: *[32]u8) !void {
7676
try self.commit();
7777
out.* = self.chunks.root.getRoot(self.chunks.pool).*;
7878
}
7979

80+
pub fn hashTreeRoot(self: *Self) !*const [32]u8 {
81+
try self.commit();
82+
return self.chunks.root.getRoot(self.chunks.pool);
83+
}
84+
85+
pub fn fromValue(allocator: Allocator, pool: *Node.Pool, value: *const ST.Type) !*Self {
86+
const root = try ST.tree.fromValue(pool, value);
87+
errdefer pool.unref(root);
88+
return try Self.init(allocator, pool, root);
89+
}
90+
91+
pub fn toValue(self: *Self, _: Allocator, out: *ST.Type) !void {
92+
try self.commit();
93+
try ST.tree.toValue(self.chunks.root, self.chunks.pool, out);
94+
}
95+
8096
pub fn getRoot(self: *const Self) Node.Id {
8197
return self.chunks.root;
8298
}
@@ -91,8 +107,8 @@ pub fn ArrayBasicTreeView(comptime ST: type) type {
91107
try self.chunks.set(index, value);
92108
}
93109

94-
pub fn getAll(self: *Self) ![]Element {
95-
return try self.chunks.getAll(self.allocator, length);
110+
pub fn getAll(self: *Self, allocator: ?Allocator) ![]Element {
111+
return try self.chunks.getAll(allocator orelse self.allocator, length);
96112
}
97113

98114
pub fn getAllInto(self: *Self, values: []Element) ![]Element {
@@ -149,7 +165,7 @@ test "TreeView vector element roundtrip" {
149165
try VectorType.hashTreeRoot(&expected, &expected_root);
150166

151167
var actual_root: [32]u8 = undefined;
152-
try view.hashTreeRoot(&actual_root);
168+
try view.hashTreeRootInto(&actual_root);
153169

154170
try std.testing.expectEqualSlices(u8, &expected_root, &actual_root);
155171

@@ -197,7 +213,7 @@ test "TreeView vector getAllAlloc roundtrip" {
197213
var view = try VectorType.TreeView.init(allocator, &pool, root_node);
198214
defer view.deinit();
199215

200-
const filled = try view.getAll();
216+
const filled = try view.getAll(null);
201217
defer allocator.free(filled);
202218

203219
try std.testing.expectEqualSlices(u16, values[0..], filled);
@@ -216,13 +232,13 @@ test "TreeView vector getAllAlloc repeat reflects updates" {
216232
var view = try VectorType.TreeView.init(allocator, &pool, root_node);
217233
defer view.deinit();
218234

219-
const first = try view.getAll();
235+
const first = try view.getAll(null);
220236
defer allocator.free(first);
221237
try std.testing.expectEqualSlices(u32, values[0..], first);
222238

223239
try view.set(3, 99);
224240

225-
const second = try view.getAll();
241+
const second = try view.getAll(null);
226242
defer allocator.free(second);
227243
values[3] = 99;
228244
try std.testing.expectEqualSlices(u32, values[0..], second);
@@ -396,7 +412,7 @@ test "ArrayBasicTreeView - serialize (uint64 vector)" {
396412
try std.testing.expectEqual(tc.expected_serialized.len, view_size);
397413

398414
var hash_root: [32]u8 = undefined;
399-
try view.hashTreeRoot(&hash_root);
415+
try view.hashTreeRootInto(&hash_root);
400416
try std.testing.expectEqualSlices(u8, &tc.expected_root, &hash_root);
401417
}
402418
}

src/ssz/tree_view/array_composite.zig

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,34 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type {
7474
self.chunks.clearCache();
7575
}
7676

77-
pub fn hashTreeRoot(self: *Self, out: *[32]u8) !void {
77+
pub fn hashTreeRootInto(self: *Self, out: *[32]u8) !void {
7878
try self.commit();
7979
out.* = self.chunks.root.getRoot(self.chunks.pool).*;
8080
}
8181

82+
pub fn hashTreeRoot(self: *Self) !*const [32]u8 {
83+
try self.commit();
84+
return self.chunks.root.getRoot(self.chunks.pool);
85+
}
86+
87+
pub fn fromValue(allocator: Allocator, pool: *Node.Pool, value: *const ST.Type) !*Self {
88+
const root = if (comptime isFixedType(ST))
89+
try ST.tree.fromValue(pool, value)
90+
else
91+
try ST.tree.fromValue(allocator, pool, value);
92+
errdefer pool.unref(root);
93+
return try Self.init(allocator, pool, root);
94+
}
95+
96+
pub fn toValue(self: *Self, allocator: Allocator, out: *ST.Type) !void {
97+
try self.commit();
98+
if (comptime isFixedType(ST)) {
99+
try ST.tree.toValue(self.chunks.root, self.chunks.pool, out);
100+
} else {
101+
try ST.tree.toValue(allocator, self.chunks.root, self.chunks.pool, out);
102+
}
103+
}
104+
82105
pub fn getRoot(self: *const Self) Node.Id {
83106
return self.chunks.root;
84107
}
@@ -89,10 +112,25 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type {
89112
}
90113

91114
pub fn getReadonly(self: *Self, index: usize) !Element {
92-
// TODO: Implement read-only access after other PRs land.
93-
_ = self;
94-
_ = index;
95-
return error.NotImplemented;
115+
if (index >= length) return error.IndexOutOfBounds;
116+
return self.chunks.getReadonly(index);
117+
}
118+
119+
pub fn getValue(self: *Self, allocator: Allocator, index: usize, out: *ST.Element.Type) !void {
120+
if (index >= length) return error.IndexOutOfBounds;
121+
return self.chunks.getValue(allocator, index, out);
122+
}
123+
124+
pub fn setValue(self: *Self, index: usize, value: *const ST.Element.Type) !void {
125+
if (index >= length) return error.IndexOutOfBounds;
126+
try self.chunks.setValue(index, value);
127+
}
128+
129+
pub fn getFieldRoot(self: *Self, index: usize) !*const [32]u8 {
130+
if (index >= length) return error.IndexOutOfBounds;
131+
const elem = try self.chunks.get(index);
132+
try elem.commit();
133+
return elem.getRoot().getRoot(self.chunks.pool);
96134
}
97135

98136
pub fn set(self: *Self, index: usize, value: Element) !void {
@@ -101,10 +139,11 @@ pub fn ArrayCompositeTreeView(comptime ST: type) type {
101139
}
102140

103141
pub fn getAllReadonly(self: *Self, allocator: Allocator) ![]Element {
104-
// TODO: Implement bulk read-only access after other PRs land.
105-
_ = self;
106-
_ = allocator;
107-
return error.NotImplemented;
142+
return self.chunks.getAllReadonly(allocator, length);
143+
}
144+
145+
pub fn getAllReadonlyValues(self: *Self, allocator: Allocator) ![]ST.Element.Type {
146+
return self.chunks.getAllValues(allocator, length);
108147
}
109148

110149
/// Serialize the tree view into a provided buffer.
@@ -170,7 +209,7 @@ test "TreeView vector composite element set/get/commit" {
170209
try view.commit();
171210

172211
var actual_root: [32]u8 = undefined;
173-
try view.hashTreeRoot(&actual_root);
212+
try view.hashTreeRootInto(&actual_root);
174213

175214
var expected: VectorType.Type = .{ v0, replacement };
176215
var expected_root: [32]u8 = undefined;
@@ -237,7 +276,7 @@ test "TreeView vector composite clearCache does not break subsequent commits" {
237276
replacement_view = null;
238277

239278
var actual_root: [32]u8 = undefined;
240-
try view.hashTreeRoot(&actual_root);
279+
try view.hashTreeRootInto(&actual_root);
241280

242281
var expected: VectorType.Type = .{ replacement, v1 };
243282
var expected_root: [32]u8 = undefined;
@@ -498,7 +537,7 @@ test "ArrayCompositeTreeView - serialize (Container vector)" {
498537
try std.testing.expectEqualSlices(u8, &expected, &view_serialized);
499538

500539
var hash_root: [32]u8 = undefined;
501-
try view.hashTreeRoot(&hash_root);
540+
try view.hashTreeRootInto(&hash_root);
502541
// 0xb1a797eb50654748ba239010edccea7b46b55bf740730b700684f48b0c478372
503542
const expected_root = [_]u8{ 0xb1, 0xa7, 0x97, 0xeb, 0x50, 0x65, 0x47, 0x48, 0xba, 0x23, 0x90, 0x10, 0xed, 0xcc, 0xea, 0x7b, 0x46, 0xb5, 0x5b, 0xf7, 0x40, 0x73, 0x0b, 0x70, 0x06, 0x84, 0xf4, 0x8b, 0x0c, 0x47, 0x83, 0x72 };
504543
try std.testing.expectEqualSlices(u8, &expected_root, &hash_root);

src/ssz/tree_view/bit_vector.zig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ pub fn BitVectorTreeView(comptime ST: type) type {
8181
return try self.data.set(index, value, length);
8282
}
8383

84+
pub fn fromValue(allocator: Allocator, pool: *Node.Pool, value: *const ST.Type) !*Self {
85+
const root = try ST.tree.fromValue(pool, value);
86+
errdefer pool.unref(root);
87+
return try Self.init(allocator, pool, root);
88+
}
89+
90+
pub fn toValue(self: *Self, _: Allocator, out: *ST.Type) !void {
91+
try self.commit();
92+
try ST.tree.toValue(self.data.root, self.data.pool, out);
93+
}
94+
8495
/// Caller must free the returned slice.
8596
pub fn toBoolArray(self: *Self, allocator: Allocator) ![]bool {
8697
const values = try allocator.alloc(bool, length);

src/ssz/tree_view/chunks.zig

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const Depth = hashing.Depth;
77
const Node = @import("persistent_merkle_tree").Node;
88
const Gindex = @import("persistent_merkle_tree").Gindex;
99

10+
const isFixedType = @import("../type/type_kind.zig").isFixedType;
11+
1012
const tree_view_root = @import("root.zig");
1113
const ChildNodes = @import("utils/child_nodes.zig").ChildNodes;
1214
const CloneOpts = @import("utils/clone_opts.zig").CloneOpts;
@@ -290,16 +292,16 @@ pub fn CompositeChunks(
290292

291293
pub fn get(self: *Self, index: usize) !ElementPtr {
292294
const gindex = Gindex.fromDepth(chunk_depth, index);
295+
// Always mark as changed - the child may have been previously cached
296+
// via getReadonly() without being tracked in changed.
297+
try self.changed.put(self.allocator, gindex, {});
293298
const gop = try self.children_data.getOrPut(self.allocator, gindex);
294299
if (gop.found_existing) {
295300
return gop.value_ptr.*;
296301
}
297302
const child_node = try self.getChildNode(gindex);
298303
const child_ptr = try Element.init(self.allocator, self.pool, child_node);
299304
gop.value_ptr.* = child_ptr;
300-
301-
// TODO only update changed if the subview is mutable
302-
try self.changed.put(self.allocator, gindex, {});
303305
return child_ptr;
304306
}
305307

@@ -319,6 +321,97 @@ pub fn CompositeChunks(
319321
}
320322
}
321323

324+
/// Get a child view without tracking changes (read-only access).
325+
pub fn getReadonly(self: *Self, index: usize) !ElementPtr {
326+
const gindex = Gindex.fromDepth(chunk_depth, index);
327+
if (self.children_data.get(gindex)) |child_ptr| {
328+
return child_ptr;
329+
}
330+
const child_node = try self.getChildNode(gindex);
331+
const child_ptr = try Element.init(self.allocator, self.pool, child_node);
332+
try self.children_data.put(self.allocator, gindex, child_ptr);
333+
// Do NOT add to self.changed (read-only)
334+
return child_ptr;
335+
}
336+
337+
/// Get all child views without tracking changes (read-only).
338+
pub fn getAllReadonly(self: *Self, allocator: Allocator, len: usize) ![]ElementPtr {
339+
const views = try allocator.alloc(ElementPtr, len);
340+
errdefer allocator.free(views);
341+
for (0..len) |i| {
342+
views[i] = try self.getReadonly(i);
343+
}
344+
return views;
345+
}
346+
347+
pub const Value = ST.Element.Type;
348+
349+
/// Get a child value as an SSZ value type.
350+
pub fn getValue(self: *Self, allocator: Allocator, index: usize, out: *Value) !void {
351+
var child_view = try self.getReadonly(index);
352+
if (comptime isFixedType(ST.Element)) {
353+
try child_view.toValue(undefined, out);
354+
} else {
355+
try child_view.toValue(allocator, out);
356+
}
357+
}
358+
359+
/// Set a child from an SSZ value type.
360+
pub fn setValue(self: *Self, index: usize, value: *const Value) !void {
361+
const root = try ST.Element.tree.fromValue(self.pool, value);
362+
errdefer self.pool.unref(root);
363+
const child_view = try Element.init(self.allocator, self.pool, root);
364+
errdefer child_view.deinit();
365+
try self.set(index, child_view);
366+
}
367+
368+
/// Get all element values in a single traversal.
369+
/// Caller owns the returned slice and must free it with the same allocator.
370+
pub fn getAllValues(self: *Self, allocator: Allocator, len: usize) ![]Value {
371+
const values = try allocator.alloc(Value, len);
372+
errdefer allocator.free(values);
373+
return try self.getAllValuesInto(allocator, values);
374+
}
375+
376+
/// Fills `values` with all element values.
377+
pub fn getAllValuesInto(self: *Self, allocator: Allocator, values: []Value) ![]Value {
378+
const len = values.len;
379+
if (len == 0) return values;
380+
381+
if (self.changed.count() != 0) {
382+
return error.MustCommitBeforeBulkRead;
383+
}
384+
385+
const nodes = try allocator.alloc(Node.Id, len);
386+
defer allocator.free(nodes);
387+
388+
try self.root.getNodesAtDepth(self.pool, chunk_depth, 0, nodes);
389+
390+
for (nodes, 0..) |node, i| {
391+
if (comptime @hasDecl(ST.Element, "deinit")) {
392+
errdefer {
393+
for (values[0..i]) |*value| {
394+
ST.Element.deinit(allocator, value);
395+
}
396+
}
397+
}
398+
if (comptime isFixedType(ST.Element)) {
399+
try ST.Element.tree.toValue(node, self.pool, &values[i]);
400+
} else {
401+
// Initialize value to default before toValue for variable types
402+
// (e.g. BitList fields need initialized ArrayListUnmanaged)
403+
if (comptime @hasDecl(ST.Element, "default_value")) {
404+
values[i] = ST.Element.default_value;
405+
} else {
406+
values[i] = std.mem.zeroes(Value);
407+
}
408+
try ST.Element.tree.toValue(allocator, node, self.pool, &values[i]);
409+
}
410+
}
411+
412+
return values;
413+
}
414+
322415
pub fn getChildNode(self: *Self, gindex: Gindex) !Node.Id {
323416
return ChildNodes.getChildNode(self, gindex);
324417
}

0 commit comments

Comments
 (0)