Skip to content

Commit 138b94d

Browse files
feat: enhance clone api (#119)
**Motivation** - when upgrading BeaconState, we have to manually clone() each field of `ExecutionPayloadHeader` from bellatrix -> capella and capella->deneb **Description** - enhance clone() api to accept a pointer to super-set type - this is comptime check so should be safe - list of types are applied: Container, List, Vector --------- Co-authored-by: Tuyen Nguyen <[email protected]> Co-authored-by: bing <[email protected]>
1 parent 2baef14 commit 138b94d

File tree

5 files changed

+224
-104
lines changed

5 files changed

+224
-104
lines changed

src/ssz/type/container.zig

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,21 @@ pub fn FixedContainerType(comptime ST: type) type {
7979
}
8080

8181
/// Creates a new `FixedContainerType` and clones all underlying fields in the container.
82-
///
82+
/// `out` is a pointer to any types that contains all fields of `Type`.
8383
/// Caller owns the memory.
84-
pub fn clone(value: *const Type, out: *Type) !void {
85-
out.* = value.*;
84+
pub fn clone(value: *const Type, out: anytype) !void {
85+
const OutType = @TypeOf(out.*);
86+
comptime {
87+
const OutInfo = @typeInfo(@TypeOf(out));
88+
std.debug.assert(OutInfo == .pointer);
89+
}
90+
if (OutType == Type) {
91+
out.* = value.*;
92+
} else {
93+
inline for (fields) |field| {
94+
@field(out, field.name) = @field(value, field.name);
95+
}
96+
}
8697
}
8798

8899
pub fn hashTreeRoot(value: *const Type, out: *[32]u8) !void {
@@ -358,13 +369,18 @@ pub fn VariableContainerType(comptime ST: type) type {
358369
}
359370

360371
/// Creates a new `VariableContainerType` and clones all underlying fields in the container.
361-
///
372+
/// `out` is a pointer to any types that contains all fields of `Type`.
362373
/// Caller owns the memory.
363374
pub fn clone(
364375
allocator: std.mem.Allocator,
365376
value: *const Type,
366-
out: *Type,
377+
out: anytype,
367378
) !void {
379+
comptime {
380+
const OutInfo = @typeInfo(@TypeOf(out));
381+
std.debug.assert(OutInfo == .pointer);
382+
}
383+
368384
inline for (fields) |field| {
369385
if (comptime isFixedType(field.type)) {
370386
try field.type.clone(&@field(value, field.name), &@field(out, field.name));
@@ -720,24 +736,44 @@ test "ContainerType - sanity" {
720736
try Foo.deserializeFromBytes(allocator, f_buf, &f);
721737
}
722738

723-
test "clone" {
724-
const allocator = std.testing.allocator;
739+
test "clone FixedContainerType" {
725740
const Checkpoint = FixedContainerType(struct {
726-
slot: UintType(8),
741+
epoch: UintType(8),
727742
root: ByteVectorType(32),
728743
});
744+
const CheckpointHex = FixedContainerType(struct {
745+
epoch: UintType(8),
746+
root: ByteVectorType(32),
747+
root_hex: ByteVectorType(64),
748+
});
729749

730750
var c: Checkpoint.Type = Checkpoint.default_value;
731751

732752
var cloned: Checkpoint.Type = undefined;
733753
try Checkpoint.clone(&c, &cloned);
734754
try std.testing.expect(&cloned != &c);
755+
try std.testing.expect(Checkpoint.equals(&cloned, &c));
756+
757+
// clone into a larger container
758+
var cloned2: CheckpointHex.Type = undefined;
759+
cloned2.root_hex = ByteVectorType(64).default_value;
760+
try Checkpoint.clone(&c, &cloned2);
761+
try std.testing.expect(cloned2.epoch == c.epoch);
762+
try std.testing.expectEqualSlices(u8, &cloned2.root, &c.root);
763+
try std.testing.expectEqualSlices(u8, &cloned2.root_hex, &ByteVectorType(64).default_value);
764+
}
765+
766+
test "clone VariableContainerType" {
767+
const allocator = std.testing.allocator;
768+
const FieldA = FixedListType(UintType(8), 32);
769+
const FieldB = FixedListType(UintType(8), 32);
735770
const Foo = VariableContainerType(struct {
736-
a: FixedListType(UintType(8), 32),
737-
b: FixedListType(UintType(8), 32),
738-
c: FixedListType(UintType(8), 32),
771+
a: FieldA,
772+
b: FieldB,
739773
});
740774
var f = Foo.default_value;
775+
try f.a.append(allocator, 42);
776+
try f.b.append(allocator, 42);
741777
defer Foo.deinit(allocator, &f);
742778
var cloned_f: Foo.Type = undefined;
743779
try Foo.clone(allocator, &f, &cloned_f);
@@ -746,7 +782,22 @@ test "clone" {
746782

747783
try expectEqualRootsAlloc(Foo, allocator, f, cloned_f);
748784
try expectEqualSerializedAlloc(Foo, allocator, f, cloned_f);
749-
// TODO(bing): test equals when ready
785+
try std.testing.expect(Foo.equals(&cloned_f, &f));
786+
787+
// clone into a larger container
788+
const FieldC = FixedListType(UintType(8), 32);
789+
const Foo2 = VariableContainerType(struct {
790+
a: FieldA,
791+
b: FieldB,
792+
// 1 additional field
793+
c: FieldC,
794+
});
795+
var cloned_f2: Foo2.Type = undefined;
796+
cloned_f2.c = FieldC.default_value;
797+
try Foo.clone(allocator, &f, &cloned_f2);
798+
defer Foo2.deinit(allocator, &cloned_f2);
799+
try std.testing.expectEqualSlices(u8, f.a.items, cloned_f2.a.items);
800+
try std.testing.expectEqualSlices(u8, f.b.items, cloned_f2.b.items);
750801
}
751802

752803
// Refer to https://github.com/ChainSafe/ssz/blob/f5ed0b457333749b5c3f49fa5eafa096a725f033/packages/ssz/test/unit/byType/container/valid.test.ts#L9-L64

src/ssz/type/list.zig

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ pub fn FixedListType(comptime ST: type, comptime _limit: comptime_int) type {
8080
/// Clones the underlying `ArrayList`.
8181
///
8282
/// Caller owns the memory.
83-
pub fn clone(allocator: std.mem.Allocator, value: *const Type, out: *Type) !void {
83+
pub fn clone(allocator: std.mem.Allocator, value: *const Type, out: anytype) !void {
84+
comptime {
85+
const OutInfo = @typeInfo(@TypeOf(out));
86+
std.debug.assert(OutInfo == .pointer);
87+
}
88+
8489
try out.resize(allocator, value.items.len);
8590

8691
for (value.items, 0..) |v, i| {
@@ -425,9 +430,13 @@ pub fn VariableListType(comptime ST: type, comptime _limit: comptime_int) type {
425430
}
426431

427432
/// Clones the underlying `ArrayList`.
428-
///
429433
/// Caller owns the memory.
430-
pub fn clone(allocator: std.mem.Allocator, value: *const Type, out: *Type) !void {
434+
pub fn clone(allocator: std.mem.Allocator, value: *const Type, out: anytype) !void {
435+
comptime {
436+
const OutInfo = @typeInfo(@TypeOf(out));
437+
std.debug.assert(OutInfo == .pointer);
438+
}
439+
431440
try out.resize(allocator, value.items.len);
432441
for (0..value.items.len) |i|
433442
try Element.clone(allocator, &value.items[i], &out.items[i]);
@@ -722,6 +731,7 @@ const UintType = @import("uint.zig").UintType;
722731
const BoolType = @import("bool.zig").BoolType;
723732
const ByteVectorType = @import("byte_vector.zig").ByteVectorType;
724733
const FixedContainerType = @import("container.zig").FixedContainerType;
734+
const VariableContainerType = @import("container.zig").VariableContainerType;
725735

726736
test "ListType - sanity" {
727737
const allocator = std.testing.allocator;
@@ -753,33 +763,72 @@ test "ListType - sanity" {
753763
try BytesBytes.deserializeFromBytes(allocator, bb_buf, &bb);
754764
}
755765

756-
test "clone" {
766+
test "clone FixedListType" {
757767
const allocator = std.testing.allocator;
758-
const BytesFixed = FixedListType(UintType(8), 32);
759-
const BytesVariable = VariableListType(BytesFixed, 32);
760-
761-
var b: BytesFixed.Type = BytesFixed.default_value;
762-
defer b.deinit(allocator);
763-
try b.append(allocator, 5);
764-
var cloned: BytesFixed.Type = BytesFixed.default_value;
765-
try BytesFixed.clone(allocator, &b, &cloned);
768+
const Checkpoint = FixedContainerType(struct {
769+
epoch: UintType(8),
770+
root: ByteVectorType(32),
771+
});
772+
const CheckpointList = FixedListType(Checkpoint, 8);
773+
var list: CheckpointList.Type = CheckpointList.default_value;
774+
defer CheckpointList.deinit(allocator, &list);
775+
const cp: Checkpoint.Type = .{
776+
.epoch = 41,
777+
.root = [_]u8{1} ** 32,
778+
};
779+
try list.append(allocator, cp);
780+
var cloned: CheckpointList.Type = CheckpointList.default_value;
781+
try CheckpointList.clone(allocator, &list, &cloned);
766782
defer cloned.deinit(allocator);
767-
try std.testing.expect(&b != &cloned);
768-
try std.testing.expect(std.mem.eql(u8, b.items[0..], cloned.items[0..]));
769-
try expectEqualRootsAlloc(BytesFixed, allocator, b, cloned);
770-
try expectEqualSerializedAlloc(BytesFixed, allocator, b, cloned);
771-
772-
var bv: BytesVariable.Type = BytesVariable.default_value;
773-
defer bv.deinit(allocator);
774-
const bb: BytesFixed.Type = BytesFixed.default_value;
775-
try bv.append(allocator, bb);
776-
var cloned_v: BytesVariable.Type = BytesVariable.default_value;
777-
try BytesVariable.clone(allocator, &bv, &cloned_v);
778-
defer cloned_v.deinit(allocator);
779-
try std.testing.expect(&bv != &cloned_v);
780-
try expectEqualRootsAlloc(BytesVariable, allocator, bv, cloned_v);
781-
try expectEqualSerializedAlloc(BytesVariable, allocator, bv, cloned_v);
782-
// TODO(bing): Equals test
783+
try std.testing.expect(&list != &cloned);
784+
try std.testing.expect(CheckpointList.equals(&list, &cloned));
785+
786+
// clone to a list of a different type
787+
const CheckpointHex = FixedContainerType(struct {
788+
epoch: UintType(8),
789+
root: ByteVectorType(32),
790+
root_hex: ByteVectorType(64),
791+
});
792+
const CheckpointHexList = FixedListType(CheckpointHex, 8);
793+
var list_hex: CheckpointHexList.Type = CheckpointHexList.default_value;
794+
defer list_hex.deinit(allocator);
795+
try CheckpointList.clone(allocator, &list, &list_hex);
796+
try std.testing.expect(list_hex.items.len == 1);
797+
try std.testing.expect(list_hex.items[0].epoch == cp.epoch);
798+
try std.testing.expectEqualSlices(u8, &list_hex.items[0].root, &cp.root);
799+
}
800+
801+
test "clone VariableListType" {
802+
const allocator = std.testing.allocator;
803+
const FieldA = FixedListType(UintType(8), 32);
804+
const Foo = VariableContainerType(struct {
805+
a: FieldA,
806+
});
807+
const ListFoo = VariableListType(Foo, 8);
808+
var list = ListFoo.default_value;
809+
defer ListFoo.deinit(allocator, &list);
810+
var fielda = FieldA.default_value;
811+
try fielda.append(allocator, 100);
812+
try list.append(allocator, .{ .a = fielda });
813+
814+
var cloned: ListFoo.Type = ListFoo.default_value;
815+
defer ListFoo.deinit(allocator, &cloned);
816+
try ListFoo.clone(allocator, &list, &cloned);
817+
try std.testing.expect(&list != &cloned);
818+
try std.testing.expect(cloned.items.len == 1);
819+
try std.testing.expect(ListFoo.equals(&list, &cloned));
820+
821+
// clone to a list of a different type
822+
const Bar = VariableContainerType(struct {
823+
a: FieldA,
824+
b: UintType(8),
825+
});
826+
const ListBar = VariableListType(Bar, 8);
827+
var list_bar: ListBar.Type = ListBar.default_value;
828+
defer ListBar.deinit(allocator, &list_bar);
829+
try ListFoo.clone(allocator, &list, &list_bar);
830+
try std.testing.expect(list_bar.items.len == 1);
831+
try std.testing.expect(FieldA.equals(&list_bar.items[0].a, &fielda));
783832
}
784833

785834
// Tests ported from TypeScript ssz packages/ssz/test/unit/byType/listBasic/valid.test.ts

src/ssz/type/vector.zig

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,21 @@ pub fn FixedVectorType(comptime ST: type, comptime _length: comptime_int) type {
5959
try merkleize(@ptrCast(&chunks), chunk_depth, out);
6060
}
6161

62-
pub fn clone(value: *const Type, out: *Type) !void {
63-
out.* = value.*;
62+
pub fn clone(value: *const Type, out: anytype) !void {
63+
comptime {
64+
const OutInfo = @typeInfo(@TypeOf(out.*));
65+
std.debug.assert(OutInfo == .array);
66+
std.debug.assert(OutInfo.array.len == length);
67+
}
68+
69+
const OutType = @TypeOf(out.*);
70+
if (OutType == Type) {
71+
out.* = value.*;
72+
} else {
73+
inline for (value, 0..) |*element, i| {
74+
try Element.clone(element, &out[i]);
75+
}
76+
}
6477
}
6578

6679
pub fn serializeIntoBytes(value: *const Type, out: []u8) usize {
@@ -286,8 +299,16 @@ pub fn VariableVectorType(comptime ST: type, comptime _length: comptime_int) typ
286299
try merkleize(@ptrCast(&chunks), chunk_depth, out);
287300
}
288301

289-
pub fn clone(allocator: std.mem.Allocator, value: *const Type, out: *Type) !void {
290-
for (0..length) |i| try Element.clone(allocator, &value[i], &out[i]);
302+
pub fn clone(allocator: std.mem.Allocator, value: *const Type, out: anytype) !void {
303+
comptime {
304+
const OutInfo = @typeInfo(@TypeOf(out.*));
305+
std.debug.assert(OutInfo == .array);
306+
std.debug.assert(OutInfo.array.len == length);
307+
}
308+
309+
for (value, 0..) |*element, i| {
310+
try Element.clone(allocator, element, &out[i]);
311+
}
291312
}
292313

293314
pub fn serializedSize(value: *const Type) usize {
@@ -457,6 +478,7 @@ const BitListType = @import("bit_list.zig").BitListType;
457478
const ByteVectorType = @import("byte_vector.zig").ByteVectorType;
458479
const FixedContainerType = @import("container.zig").FixedContainerType;
459480
const FixedListType = @import("list.zig").FixedListType;
481+
const VariableContainerType = @import("container.zig").VariableContainerType;
460482

461483
test "vector - sanity" {
462484
// create a fixed vector type and instance and round-trip serialize
@@ -468,31 +490,62 @@ test "vector - sanity" {
468490
try Bytes32.deserializeFromBytes(&b0_buf, &b0);
469491
}
470492

471-
test "clone" {
493+
test "clone FixedVectorType" {
494+
const Checkpoint = FixedContainerType(struct {
495+
epoch: UintType(8),
496+
root: ByteVectorType(32),
497+
});
498+
const CheckpointVector = FixedVectorType(Checkpoint, 4);
499+
var vector: CheckpointVector.Type = CheckpointVector.default_value;
500+
vector[0].epoch = 42;
501+
502+
var cloned: CheckpointVector.Type = undefined;
503+
try CheckpointVector.clone(&vector, &cloned);
504+
try std.testing.expect(&vector != &cloned);
505+
try std.testing.expect(CheckpointVector.equals(&vector, &cloned));
506+
507+
// clone into another type
508+
const CheckpointHex = FixedContainerType(struct {
509+
epoch: UintType(8),
510+
root: ByteVectorType(32),
511+
root_hex: ByteVectorType(64),
512+
});
513+
const CheckpointHexVector = FixedVectorType(CheckpointHex, 4);
514+
var cloned2: CheckpointHexVector.Type = undefined;
515+
try CheckpointVector.clone(&vector, &cloned2);
516+
try std.testing.expect(cloned2[0].epoch == 42);
517+
}
518+
519+
test "clone VariableVectorType" {
472520
const allocator = std.testing.allocator;
473-
const BoolVectorFixed = FixedVectorType(BoolType(), 8);
474-
var bvf: BoolVectorFixed.Type = BoolVectorFixed.default_value;
475-
476-
var cloned: BoolVectorFixed.Type = undefined;
477-
try BoolVectorFixed.clone(&bvf, &cloned);
478-
try expectEqualRoots(BoolVectorFixed, bvf, cloned);
479-
try expectEqualSerialized(BoolVectorFixed, bvf, cloned);
480-
481-
try std.testing.expect(&bvf != &cloned);
482-
try std.testing.expect(std.mem.eql(bool, bvf[0..], cloned[0..]));
483-
484-
const limit = 16;
485-
const BitList = BitListType(limit);
486-
const bl = BitList.default_value;
487-
const BoolVectorVariable = VariableVectorType(BitList, 8);
488-
var bvv: BoolVectorVariable.Type = BoolVectorVariable.default_value;
489-
bvv[0] = bl;
490-
491-
var cloned_v: BoolVectorVariable.Type = undefined;
492-
try BoolVectorVariable.clone(allocator, &bvv, &cloned_v);
493-
try std.testing.expect(&bvv != &cloned_v);
494-
try expectEqualRootsAlloc(BoolVectorVariable, allocator, bvv, cloned_v);
495-
try expectEqualSerializedAlloc(BoolVectorVariable, allocator, bvv, cloned_v);
521+
const FieldA = FixedListType(UintType(8), 32);
522+
const Foo = VariableContainerType(struct {
523+
a: FieldA,
524+
});
525+
const FooVector = VariableVectorType(Foo, 4);
526+
var foo_vector: FooVector.Type = FooVector.default_value;
527+
defer FooVector.deinit(allocator, &foo_vector);
528+
try foo_vector[0].a.append(allocator, 100);
529+
530+
var cloned: FooVector.Type = undefined;
531+
defer FooVector.deinit(allocator, &cloned);
532+
try FooVector.clone(allocator, &foo_vector, &cloned);
533+
try std.testing.expect(&foo_vector != &cloned);
534+
try std.testing.expect(FooVector.equals(&foo_vector, &cloned));
535+
try std.testing.expect(cloned[0].a.items.len == 1);
536+
try std.testing.expect(cloned[0].a.items[0] == 100);
537+
538+
// clone into another type
539+
const Bar = VariableContainerType(struct {
540+
a: FieldA,
541+
b: UintType(8),
542+
});
543+
const BarVector = VariableVectorType(Bar, 4);
544+
var cloned2: BarVector.Type = undefined;
545+
defer BarVector.deinit(allocator, &cloned2);
546+
try FooVector.clone(allocator, &foo_vector, &cloned2);
547+
try std.testing.expect(cloned2[0].a.items.len == 1);
548+
try std.testing.expect(cloned2[0].a.items[0] == 100);
496549
}
497550

498551
// Refer to https://github.com/ChainSafe/ssz/blob/f5ed0b457333749b5c3f49fa5eafa096a725f033/packages/ssz/test/unit/byType/vector/valid.test.ts#L15-L85

0 commit comments

Comments
 (0)