SSZ-QL: Support nested List type (#15725)

* Add nested 2d list cases

* Add elementSize member for listInfo to track each element's byte size

* Fix misleading variable in RunStructTest

* Changelog

* Regen pb file

* Update encoding/ssz/query/list.go

Co-authored-by: Radosław Kapka <radoslaw.kapka@gmail.com>

* Rename elementSize into plural

* Update changelog/syjn99_ssz-ql-nested-list.md

---------

Co-authored-by: Radosław Kapka <radoslaw.kapka@gmail.com>
This commit is contained in:
Jun Song
2025-09-26 23:23:22 +09:00
committed by GitHub
parent d68196822b
commit 29fe707143
9 changed files with 355 additions and 57 deletions

View File

@@ -0,0 +1,3 @@
### Fixed
- SSZ-QL: Support nested `List` type (e.g., `ExecutionPayload.Transactions`)

View File

@@ -60,9 +60,21 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
if val.Kind() != reflect.Slice { if val.Kind() != reflect.Slice {
return fmt.Errorf("expected slice for List type, got %v", val.Kind()) return fmt.Errorf("expected slice for List type, got %v", val.Kind())
} }
length := val.Len()
length := uint64(val.Len()) if listInfo.element.isVariable {
if err := listInfo.SetLength(length); err != nil { listInfo.elementSizes = make([]uint64, 0, length)
// Populate nested variable-sized type element lengths recursively.
for i := range length {
if err := PopulateVariableLengthInfo(listInfo.element, val.Index(i).Interface()); err != nil {
return fmt.Errorf("could not populate nested list element at index %d: %w", i, err)
}
listInfo.elementSizes = append(listInfo.elementSizes, listInfo.element.Size())
}
}
if err := listInfo.SetLength(uint64(length)); err != nil {
return fmt.Errorf("could not set list length: %w", err) return fmt.Errorf("could not set list length: %w", err)
} }
@@ -111,15 +123,20 @@ func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error {
continue continue
} }
// Set the actual offset for variable-sized fields.
fieldInfo.offset = currentOffset
// Recursively populate variable-sized fields. // Recursively populate variable-sized fields.
fieldValue := derefValue.FieldByName(fieldInfo.goFieldName) fieldValue := derefValue.FieldByName(fieldInfo.goFieldName)
if err := PopulateVariableLengthInfo(childSszInfo, fieldValue.Interface()); err != nil { if err := PopulateVariableLengthInfo(childSszInfo, fieldValue.Interface()); err != nil {
return fmt.Errorf("could not populate from value for field %s: %w", fieldName, err) return fmt.Errorf("could not populate from value for field %s: %w", fieldName, err)
} }
// Each variable-sized element needs an offset entry.
if childSszInfo.sszType == List {
currentOffset += childSszInfo.listInfo.OffsetBytes()
}
// Set the actual offset for variable-sized fields.
fieldInfo.offset = currentOffset
currentOffset += childSszInfo.Size() currentOffset += childSszInfo.Size()
} }

View File

@@ -16,6 +16,8 @@ type listInfo struct {
element *sszInfo element *sszInfo
// length is the actual number of elements at runtime (0 if not set). // length is the actual number of elements at runtime (0 if not set).
length uint64 length uint64
// elementSizes caches each element's byte size for variable-sized type elements
elementSizes []uint64
} }
func (l *listInfo) Limit() uint64 { func (l *listInfo) Limit() uint64 {
@@ -51,3 +53,35 @@ func (l *listInfo) SetLength(length uint64) error {
l.length = length l.length = length
return nil return nil
} }
func (l *listInfo) Size() uint64 {
if l == nil {
return 0
}
// For fixed-sized type elements, size is multiplying length by element size.
if !l.element.isVariable {
return l.length * l.element.Size()
}
// For variable-sized type elements, sum up the sizes of each element.
totalSize := uint64(0)
for _, sz := range l.elementSizes {
totalSize += sz
}
return totalSize
}
// OffsetBytes returns the total number of offset bytes used for the list elements.
// Each variable-sized element uses 4 bytes to store its offset.
func (l *listInfo) OffsetBytes() uint64 {
if l == nil {
return 0
}
if !l.element.isVariable {
return 0
}
return offsetBytes * l.length
}

View File

@@ -133,56 +133,73 @@ func TestCalculateOffsetAndLength(t *testing.T) {
{ {
name: "field_list_uint64", name: "field_list_uint64",
path: ".field_list_uint64", path: ".field_list_uint64",
expectedOffset: 108, // First part of variable-sized type. expectedOffset: 112, // First part of variable-sized type.
expectedLength: 40, // 5 elements * uint64 (8 bytes each) expectedLength: 40, // 5 elements * uint64 (8 bytes each)
}, },
{ {
name: "field_list_container", name: "field_list_container",
path: ".field_list_container", path: ".field_list_container",
expectedOffset: 148, // Second part of variable-sized type. expectedOffset: 152, // Second part of variable-sized type.
expectedLength: 120, // 3 elements * FixedNestedContainer (40 bytes each) expectedLength: 120, // 3 elements * FixedNestedContainer (40 bytes each)
}, },
{ {
name: "field_list_bytes32", name: "field_list_bytes32",
path: ".field_list_bytes32", path: ".field_list_bytes32",
expectedOffset: 268, expectedOffset: 272,
expectedLength: 96, // 3 elements * 32 bytes each expectedLength: 96, // 3 elements * 32 bytes each
}, },
// Nested paths // Nested paths
{ {
name: "nested", name: "nested",
path: ".nested", path: ".nested",
expectedOffset: 364, expectedOffset: 368,
// Calculated with: // Calculated with:
// - Value1: 8 bytes // - Value1: 8 bytes
// - field_list_uint64 offset: 4 bytes // - field_list_uint64 offset: 4 bytes
// - field_list_uint64 length: 40 bytes // - field_list_uint64 length: 40 bytes
expectedLength: 52, // - nested_list_field offset: 4 bytes
// - nested_list_field length: 99 bytes
// - 3 offset pointers for each element in nested_list_field: 12 bytes
// Total: 8 + 4 + 40 + 4 + 99 + 12 = 167 bytes
expectedLength: 167,
}, },
{ {
name: "nested.value1", name: "nested.value1",
path: ".nested.value1", path: ".nested.value1",
expectedOffset: 364, expectedOffset: 368,
expectedLength: 8, expectedLength: 8,
}, },
{ {
name: "nested.field_list_uint64", name: "nested.field_list_uint64",
path: ".nested.field_list_uint64", path: ".nested.field_list_uint64",
expectedOffset: 376, expectedOffset: 384,
expectedLength: 40, expectedLength: 40,
}, },
{
name: "nested.nested_list_field",
path: ".nested.nested_list_field",
expectedOffset: 436,
expectedLength: 99,
},
// Bitlist field // Bitlist field
{ {
name: "bitlist_field", name: "bitlist_field",
path: ".bitlist_field", path: ".bitlist_field",
expectedOffset: 416, expectedOffset: 535,
expectedLength: 33, // 32 bytes + 1 byte for length delimiter expectedLength: 33, // 32 bytes + 1 byte for length delimiter
}, },
// 2D bytes field
{
name: "nested_list_field",
path: ".nested_list_field",
expectedOffset: 580,
expectedLength: 99,
},
// Fixed trailing field // Fixed trailing field
{ {
name: "trailing_field", name: "trailing_field",
path: ".trailing_field", path: ".trailing_field",
expectedOffset: 52, // After leading_field + 5 offset pointers expectedOffset: 56, // After leading_field + 6 offset pointers
expectedLength: 56, expectedLength: 56,
}, },
} }
@@ -377,6 +394,15 @@ func createVariableTestContainer() *sszquerypb.VariableTestContainer {
bitlistField.SetBitAt(100, true) bitlistField.SetBitAt(100, true)
bitlistField.SetBitAt(255, true) bitlistField.SetBitAt(255, true)
// Total size: 3 lists with lengths 32, 33, and 34 = 99 bytes
nestedListField := make([][]byte, 3)
for i := range nestedListField {
nestedListField[i] = make([]byte, (32 + i)) // Different lengths for each sub-list
for j := range nestedListField[i] {
nestedListField[i][j] = byte(j + i*16)
}
}
return &sszquerypb.VariableTestContainer{ return &sszquerypb.VariableTestContainer{
// Fixed leading field // Fixed leading field
LeadingField: leadingField, LeadingField: leadingField,
@@ -394,11 +420,15 @@ func createVariableTestContainer() *sszquerypb.VariableTestContainer {
Nested: &sszquerypb.VariableNestedContainer{ Nested: &sszquerypb.VariableNestedContainer{
Value1: 42, Value1: 42,
FieldListUint64: []uint64{1, 2, 3, 4, 5}, FieldListUint64: []uint64{1, 2, 3, 4, 5},
NestedListField: nestedListField,
}, },
// Bitlist field // Bitlist field
BitlistField: bitlistField, BitlistField: bitlistField,
// 2D bytes field
NestedListField: nestedListField,
// Fixed trailing field // Fixed trailing field
TrailingField: trailingField, TrailingField: trailingField,
} }
@@ -445,11 +475,20 @@ func getVariableTestContainerSpec() testutil.TestSpec {
Path: ".nested.field_list_uint64", Path: ".nested.field_list_uint64",
Expected: testContainer.Nested.FieldListUint64, Expected: testContainer.Nested.FieldListUint64,
}, },
{
Path: ".nested.nested_list_field",
Expected: testContainer.Nested.NestedListField,
},
// Bitlist field // Bitlist field
{ {
Path: ".bitlist_field", Path: ".bitlist_field",
Expected: testContainer.BitlistField, Expected: testContainer.BitlistField,
}, },
// 2D bytes field
{
Path: ".nested_list_field",
Expected: testContainer.NestedListField,
},
// Fixed trailing field // Fixed trailing field
{ {
Path: ".trailing_field", Path: ".trailing_field",

View File

@@ -54,10 +54,7 @@ func (info *sszInfo) Size() uint64 {
switch info.sszType { switch info.sszType {
case List: case List:
length := info.listInfo.length return info.listInfo.Size()
elementSize := info.listInfo.element.Size()
return length * elementSize
case Bitlist: case Bitlist:
return info.bitlistInfo.Size() return info.bitlistInfo.Size()
@@ -69,6 +66,11 @@ func (info *sszInfo) Size() uint64 {
continue continue
} }
// Include offset bytes inside nested lists.
if fieldInfo.sszInfo.sszType == List {
size += fieldInfo.sszInfo.listInfo.OffsetBytes()
}
size += fieldInfo.sszInfo.Size() size += fieldInfo.sszInfo.Size()
} }
return size return size

View File

@@ -31,10 +31,10 @@ func RunStructTest(t *testing.T, spec TestSpec) {
_, offset, length, err := query.CalculateOffsetAndLength(info, path) _, offset, length, err := query.CalculateOffsetAndLength(info, path)
require.NoError(t, err) require.NoError(t, err)
expectedRawBytes := marshalledData[offset : offset+length] actualRawBytes := marshalledData[offset : offset+length]
rawBytes, err := marshalAny(pathTest.Expected) expectedRawBytes, err := marshalAny(pathTest.Expected)
require.NoError(t, err, "Marshalling expected value should not return an error") require.NoError(t, err, "Marshalling expected value should not return an error")
require.DeepEqual(t, expectedRawBytes, rawBytes, "Extracted value should match expected") require.DeepEqual(t, actualRawBytes, expectedRawBytes, "Extracted value should match expected")
}) })
} }
}) })

View File

@@ -195,6 +195,7 @@ type VariableNestedContainer struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Value1 uint64 `protobuf:"varint,1,opt,name=value1,proto3" json:"value1,omitempty"` Value1 uint64 `protobuf:"varint,1,opt,name=value1,proto3" json:"value1,omitempty"`
FieldListUint64 []uint64 `protobuf:"varint,2,rep,packed,name=field_list_uint64,json=fieldListUint64,proto3" json:"field_list_uint64,omitempty" ssz-max:"100"` FieldListUint64 []uint64 `protobuf:"varint,2,rep,packed,name=field_list_uint64,json=fieldListUint64,proto3" json:"field_list_uint64,omitempty" ssz-max:"100"`
NestedListField [][]byte `protobuf:"bytes,3,rep,name=nested_list_field,json=nestedListField,proto3" json:"nested_list_field,omitempty" ssz-max:"100,50" ssz-size:"?,?"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -243,6 +244,13 @@ func (x *VariableNestedContainer) GetFieldListUint64() []uint64 {
return nil return nil
} }
func (x *VariableNestedContainer) GetNestedListField() [][]byte {
if x != nil {
return x.NestedListField
}
return nil
}
type VariableTestContainer struct { type VariableTestContainer struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
LeadingField []byte `protobuf:"bytes,1,opt,name=leading_field,json=leadingField,proto3" json:"leading_field,omitempty" ssz-size:"32"` LeadingField []byte `protobuf:"bytes,1,opt,name=leading_field,json=leadingField,proto3" json:"leading_field,omitempty" ssz-size:"32"`
@@ -251,7 +259,8 @@ type VariableTestContainer struct {
FieldListBytes32 [][]byte `protobuf:"bytes,4,rep,name=field_list_bytes32,json=fieldListBytes32,proto3" json:"field_list_bytes32,omitempty" ssz-max:"100" ssz-size:"?,32"` FieldListBytes32 [][]byte `protobuf:"bytes,4,rep,name=field_list_bytes32,json=fieldListBytes32,proto3" json:"field_list_bytes32,omitempty" ssz-max:"100" ssz-size:"?,32"`
Nested *VariableNestedContainer `protobuf:"bytes,5,opt,name=nested,proto3" json:"nested,omitempty"` Nested *VariableNestedContainer `protobuf:"bytes,5,opt,name=nested,proto3" json:"nested,omitempty"`
BitlistField github_com_prysmaticlabs_go_bitfield.Bitlist `protobuf:"bytes,6,opt,name=bitlist_field,json=bitlistField,proto3" json:"bitlist_field,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitlist" ssz-max:"2048"` BitlistField github_com_prysmaticlabs_go_bitfield.Bitlist `protobuf:"bytes,6,opt,name=bitlist_field,json=bitlistField,proto3" json:"bitlist_field,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitlist" ssz-max:"2048"`
TrailingField []byte `protobuf:"bytes,7,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"` NestedListField [][]byte `protobuf:"bytes,7,rep,name=nested_list_field,json=nestedListField,proto3" json:"nested_list_field,omitempty" ssz-max:"100,50" ssz-size:"?,?"`
TrailingField []byte `protobuf:"bytes,8,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -328,6 +337,13 @@ func (x *VariableTestContainer) GetBitlistField() github_com_prysmaticlabs_go_bi
return github_com_prysmaticlabs_go_bitfield.Bitlist(nil) return github_com_prysmaticlabs_go_bitfield.Bitlist(nil)
} }
func (x *VariableTestContainer) GetNestedListField() [][]byte {
if x != nil {
return x.NestedListField
}
return nil
}
func (x *VariableTestContainer) GetTrailingField() []byte { func (x *VariableTestContainer) GetTrailingField() []byte {
if x != nil { if x != nil {
return x.TrailingField return x.TrailingField
@@ -384,14 +400,18 @@ var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{
0x74, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x35, 0x31, 0x32, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x74, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x35, 0x31, 0x32, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12,
0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x35, 0x36, 0x52, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x35, 0x36, 0x52,
0x0d, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x66, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0xa5,
0x0a, 0x17, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x01, 0x0a, 0x17, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65,
0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61,
0x75, 0x65, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x6c, 0x75, 0x65, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75,
0x31, 0x12, 0x33, 0x0a, 0x11, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x65, 0x31, 0x12, 0x33, 0x0a, 0x11, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74,
0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x42, 0x07, 0x92, 0xb5, 0x5f, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x42, 0x07, 0x92,
0x18, 0x03, 0x31, 0x30, 0x30, 0x52, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0xb5, 0x18, 0x03, 0x31, 0x30, 0x30, 0x52, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73,
0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x22, 0xdf, 0x03, 0x0a, 0x15, 0x56, 0x61, 0x72, 0x69, 0x61, 0x74, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x3d, 0x0a, 0x11, 0x6e, 0x65, 0x73, 0x74, 0x65,
0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0c, 0x42, 0x11, 0x8a, 0xb5, 0x18, 0x03, 0x3f, 0x2c, 0x3f, 0x92, 0xb5, 0x18, 0x06, 0x31,
0x30, 0x30, 0x2c, 0x35, 0x30, 0x52, 0x0f, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x73,
0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x9e, 0x04, 0x0a, 0x15, 0x56, 0x61, 0x72, 0x69, 0x61,
0x62, 0x6c, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x62, 0x6c, 0x65, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
0x12, 0x2b, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x12, 0x2b, 0x0a, 0x0d, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52,
@@ -418,14 +438,18 @@ var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{
0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f,
0x67, 0x6f, 0x2d, 0x62, 0x69, 0x74, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x42, 0x69, 0x74, 0x6c, 0x67, 0x6f, 0x2d, 0x62, 0x69, 0x74, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x42, 0x69, 0x74, 0x6c,
0x69, 0x73, 0x74, 0x92, 0xb5, 0x18, 0x04, 0x32, 0x30, 0x34, 0x38, 0x52, 0x0c, 0x62, 0x69, 0x74, 0x69, 0x73, 0x74, 0x92, 0xb5, 0x18, 0x04, 0x32, 0x30, 0x34, 0x38, 0x52, 0x0c, 0x62, 0x69, 0x74,
0x6c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x3d, 0x0a, 0x11, 0x6e, 0x65, 0x73,
0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x07,
0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x35, 0x36, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x11, 0x8a, 0xb5, 0x18, 0x03, 0x3f, 0x2c, 0x3f, 0x92, 0xb5, 0x18,
0x69, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x06, 0x31, 0x30, 0x30, 0x2c, 0x35, 0x30, 0x52, 0x0f, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x4c,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69,
0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x36, 0x2f, 0x70, 0x72, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c,
0x74, 0x6f, 0x2f, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x35, 0x36, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69,
0x6f, 0x74, 0x6f, 0x33, 0x6e, 0x67, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4f, 0x66, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x4c, 0x61,
0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x36, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2f, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
} }
var ( var (

View File

@@ -65,6 +65,10 @@ message FixedTestContainer {
message VariableNestedContainer { message VariableNestedContainer {
uint64 value1 = 1; uint64 value1 = 1;
repeated uint64 field_list_uint64 = 2 [ (ethereum.eth.ext.ssz_max) = "100" ]; repeated uint64 field_list_uint64 = 2 [ (ethereum.eth.ext.ssz_max) = "100" ];
repeated bytes nested_list_field = 3 [
(ethereum.eth.ext.ssz_size) = "?,?",
(ethereum.eth.ext.ssz_max) = "100,50"
];
} }
// VariableTestContainer - comprehensive variable-size container for SSZ query testing // VariableTestContainer - comprehensive variable-size container for SSZ query testing
@@ -96,7 +100,14 @@ message VariableTestContainer {
"github.com/prysmaticlabs/go-bitfield.Bitlist" "github.com/prysmaticlabs/go-bitfield.Bitlist"
]; ];
// 2D bytes list - test list of bytelists.
// e.g., ExecutionPayload.transactions
repeated bytes nested_list_field = 7 [
(ethereum.eth.ext.ssz_size) = "?,?",
(ethereum.eth.ext.ssz_max) = "100,50"
];
// Fixed-size trailing field - test fixed field after variable fields // Fixed-size trailing field - test fixed field after variable fields
// Verifies correct offset calculation after variable-size fields // Verifies correct offset calculation after variable-size fields
bytes trailing_field = 7 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: fixed 56-byte field at end, offset: 32 + 4 + 4 + 4 + 4 + 4 = 52 bytes trailing_field = 8 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: fixed 56-byte field at end, offset: 32 + 4 + 4 + 4 + 4 + 4 + 4 = 56
} }

View File

@@ -324,7 +324,7 @@ func (v *VariableNestedContainer) MarshalSSZ() ([]byte, error) {
// MarshalSSZTo ssz marshals the VariableNestedContainer object to a target array // MarshalSSZTo ssz marshals the VariableNestedContainer object to a target array
func (v *VariableNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) { func (v *VariableNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf dst = buf
offset := int(12) offset := int(16)
// Field (0) 'Value1' // Field (0) 'Value1'
dst = ssz.MarshalUint64(dst, v.Value1) dst = ssz.MarshalUint64(dst, v.Value1)
@@ -333,6 +333,13 @@ func (v *VariableNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err erro
dst = ssz.WriteOffset(dst, offset) dst = ssz.WriteOffset(dst, offset)
offset += len(v.FieldListUint64) * 8 offset += len(v.FieldListUint64) * 8
// Offset (2) 'NestedListField'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(v.NestedListField); ii++ {
offset += 4
offset += len(v.NestedListField[ii])
}
// Field (1) 'FieldListUint64' // Field (1) 'FieldListUint64'
if size := len(v.FieldListUint64); size > 100 { if size := len(v.FieldListUint64); size > 100 {
err = ssz.ErrListTooBigFn("--.FieldListUint64", size, 100) err = ssz.ErrListTooBigFn("--.FieldListUint64", size, 100)
@@ -342,6 +349,26 @@ func (v *VariableNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err erro
dst = ssz.MarshalUint64(dst, v.FieldListUint64[ii]) dst = ssz.MarshalUint64(dst, v.FieldListUint64[ii])
} }
// Field (2) 'NestedListField'
if size := len(v.NestedListField); size > 100 {
err = ssz.ErrListTooBigFn("--.NestedListField", size, 100)
return
}
{
offset = 4 * len(v.NestedListField)
for ii := 0; ii < len(v.NestedListField); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(v.NestedListField[ii])
}
}
for ii := 0; ii < len(v.NestedListField); ii++ {
if size := len(v.NestedListField[ii]); size > 50 {
err = ssz.ErrBytesLengthFn("--.NestedListField[ii]", size, 50)
return
}
dst = append(dst, v.NestedListField[ii]...)
}
return return
} }
@@ -349,12 +376,12 @@ func (v *VariableNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err erro
func (v *VariableNestedContainer) UnmarshalSSZ(buf []byte) error { func (v *VariableNestedContainer) UnmarshalSSZ(buf []byte) error {
var err error var err error
size := uint64(len(buf)) size := uint64(len(buf))
if size < 12 { if size < 16 {
return ssz.ErrSize return ssz.ErrSize
} }
tail := buf tail := buf
var o1 uint64 var o1, o2 uint64
// Field (0) 'Value1' // Field (0) 'Value1'
v.Value1 = ssz.UnmarshallUint64(buf[0:8]) v.Value1 = ssz.UnmarshallUint64(buf[0:8])
@@ -364,13 +391,18 @@ func (v *VariableNestedContainer) UnmarshalSSZ(buf []byte) error {
return ssz.ErrOffset return ssz.ErrOffset
} }
if o1 != 12 { if o1 != 16 {
return ssz.ErrInvalidVariableOffset return ssz.ErrInvalidVariableOffset
} }
// Offset (2) 'NestedListField'
if o2 = ssz.ReadOffset(buf[12:16]); o2 > size || o1 > o2 {
return ssz.ErrOffset
}
// Field (1) 'FieldListUint64' // Field (1) 'FieldListUint64'
{ {
buf = tail[o1:] buf = tail[o1:o2]
num, err := ssz.DivideInt2(len(buf), 8, 100) num, err := ssz.DivideInt2(len(buf), 8, 100)
if err != nil { if err != nil {
return err return err
@@ -380,16 +412,45 @@ func (v *VariableNestedContainer) UnmarshalSSZ(buf []byte) error {
v.FieldListUint64[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) v.FieldListUint64[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8])
} }
} }
// Field (2) 'NestedListField'
{
buf = tail[o2:]
num, err := ssz.DecodeDynamicLength(buf, 100)
if err != nil {
return err
}
v.NestedListField = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 50 {
return ssz.ErrBytesLength
}
if cap(v.NestedListField[indx]) == 0 {
v.NestedListField[indx] = make([]byte, 0, len(buf))
}
v.NestedListField[indx] = append(v.NestedListField[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err return err
} }
// SizeSSZ returns the ssz encoded size in bytes for the VariableNestedContainer object // SizeSSZ returns the ssz encoded size in bytes for the VariableNestedContainer object
func (v *VariableNestedContainer) SizeSSZ() (size int) { func (v *VariableNestedContainer) SizeSSZ() (size int) {
size = 12 size = 16
// Field (1) 'FieldListUint64' // Field (1) 'FieldListUint64'
size += len(v.FieldListUint64) * 8 size += len(v.FieldListUint64) * 8
// Field (2) 'NestedListField'
for ii := 0; ii < len(v.NestedListField); ii++ {
size += 4
size += len(v.NestedListField[ii])
}
return return
} }
@@ -421,6 +482,29 @@ func (v *VariableNestedContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(100, numItems, 8)) hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(100, numItems, 8))
} }
// Field (2) 'NestedListField'
{
subIndx := hh.Index()
num := uint64(len(v.NestedListField))
if num > 100 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range v.NestedListField {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 50 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (50+31)/32)
}
}
hh.MerkleizeWithMixin(subIndx, num, 100)
}
hh.Merkleize(indx) hh.Merkleize(indx)
return return
} }
@@ -433,7 +517,7 @@ func (v *VariableTestContainer) MarshalSSZ() ([]byte, error) {
// MarshalSSZTo ssz marshals the VariableTestContainer object to a target array // MarshalSSZTo ssz marshals the VariableTestContainer object to a target array
func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) { func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
dst = buf dst = buf
offset := int(108) offset := int(112)
// Field (0) 'LeadingField' // Field (0) 'LeadingField'
if size := len(v.LeadingField); size != 32 { if size := len(v.LeadingField); size != 32 {
@@ -465,7 +549,14 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error)
dst = ssz.WriteOffset(dst, offset) dst = ssz.WriteOffset(dst, offset)
offset += len(v.BitlistField) offset += len(v.BitlistField)
// Field (6) 'TrailingField' // Offset (6) 'NestedListField'
dst = ssz.WriteOffset(dst, offset)
for ii := 0; ii < len(v.NestedListField); ii++ {
offset += 4
offset += len(v.NestedListField[ii])
}
// Field (7) 'TrailingField'
if size := len(v.TrailingField); size != 56 { if size := len(v.TrailingField); size != 56 {
err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56)
return return
@@ -517,6 +608,26 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error)
} }
dst = append(dst, v.BitlistField...) dst = append(dst, v.BitlistField...)
// Field (6) 'NestedListField'
if size := len(v.NestedListField); size > 100 {
err = ssz.ErrListTooBigFn("--.NestedListField", size, 100)
return
}
{
offset = 4 * len(v.NestedListField)
for ii := 0; ii < len(v.NestedListField); ii++ {
dst = ssz.WriteOffset(dst, offset)
offset += len(v.NestedListField[ii])
}
}
for ii := 0; ii < len(v.NestedListField); ii++ {
if size := len(v.NestedListField[ii]); size > 50 {
err = ssz.ErrBytesLengthFn("--.NestedListField[ii]", size, 50)
return
}
dst = append(dst, v.NestedListField[ii]...)
}
return return
} }
@@ -524,12 +635,12 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error)
func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
var err error var err error
size := uint64(len(buf)) size := uint64(len(buf))
if size < 108 { if size < 112 {
return ssz.ErrSize return ssz.ErrSize
} }
tail := buf tail := buf
var o1, o2, o3, o4, o5 uint64 var o1, o2, o3, o4, o5, o6 uint64
// Field (0) 'LeadingField' // Field (0) 'LeadingField'
if cap(v.LeadingField) == 0 { if cap(v.LeadingField) == 0 {
@@ -542,7 +653,7 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
return ssz.ErrOffset return ssz.ErrOffset
} }
if o1 != 108 { if o1 != 112 {
return ssz.ErrInvalidVariableOffset return ssz.ErrInvalidVariableOffset
} }
@@ -566,11 +677,16 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
return ssz.ErrOffset return ssz.ErrOffset
} }
// Field (6) 'TrailingField' // Offset (6) 'NestedListField'
if cap(v.TrailingField) == 0 { if o6 = ssz.ReadOffset(buf[52:56]); o6 > size || o5 > o6 {
v.TrailingField = make([]byte, 0, len(buf[52:108])) return ssz.ErrOffset
} }
v.TrailingField = append(v.TrailingField, buf[52:108]...)
// Field (7) 'TrailingField'
if cap(v.TrailingField) == 0 {
v.TrailingField = make([]byte, 0, len(buf[56:112]))
}
v.TrailingField = append(v.TrailingField, buf[56:112]...)
// Field (1) 'FieldListUint64' // Field (1) 'FieldListUint64'
{ {
@@ -632,7 +748,7 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
// Field (5) 'BitlistField' // Field (5) 'BitlistField'
{ {
buf = tail[o5:] buf = tail[o5:o6]
if err = ssz.ValidateBitlist(buf, 2048); err != nil { if err = ssz.ValidateBitlist(buf, 2048); err != nil {
return err return err
} }
@@ -641,12 +757,35 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error {
} }
v.BitlistField = append(v.BitlistField, buf...) v.BitlistField = append(v.BitlistField, buf...)
} }
// Field (6) 'NestedListField'
{
buf = tail[o6:]
num, err := ssz.DecodeDynamicLength(buf, 100)
if err != nil {
return err
}
v.NestedListField = make([][]byte, num)
err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) {
if len(buf) > 50 {
return ssz.ErrBytesLength
}
if cap(v.NestedListField[indx]) == 0 {
v.NestedListField[indx] = make([]byte, 0, len(buf))
}
v.NestedListField[indx] = append(v.NestedListField[indx], buf...)
return nil
})
if err != nil {
return err
}
}
return err return err
} }
// SizeSSZ returns the ssz encoded size in bytes for the VariableTestContainer object // SizeSSZ returns the ssz encoded size in bytes for the VariableTestContainer object
func (v *VariableTestContainer) SizeSSZ() (size int) { func (v *VariableTestContainer) SizeSSZ() (size int) {
size = 108 size = 112
// Field (1) 'FieldListUint64' // Field (1) 'FieldListUint64'
size += len(v.FieldListUint64) * 8 size += len(v.FieldListUint64) * 8
@@ -666,6 +805,12 @@ func (v *VariableTestContainer) SizeSSZ() (size int) {
// Field (5) 'BitlistField' // Field (5) 'BitlistField'
size += len(v.BitlistField) size += len(v.BitlistField)
// Field (6) 'NestedListField'
for ii := 0; ii < len(v.NestedListField); ii++ {
size += 4
size += len(v.NestedListField[ii])
}
return return
} }
@@ -748,7 +893,30 @@ func (v *VariableTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
} }
hh.PutBitlist(v.BitlistField, 2048) hh.PutBitlist(v.BitlistField, 2048)
// Field (6) 'TrailingField' // Field (6) 'NestedListField'
{
subIndx := hh.Index()
num := uint64(len(v.NestedListField))
if num > 100 {
err = ssz.ErrIncorrectListSize
return
}
for _, elem := range v.NestedListField {
{
elemIndx := hh.Index()
byteLen := uint64(len(elem))
if byteLen > 50 {
err = ssz.ErrIncorrectListSize
return
}
hh.AppendBytes32(elem)
hh.MerkleizeWithMixin(elemIndx, byteLen, (50+31)/32)
}
}
hh.MerkleizeWithMixin(subIndx, num, 100)
}
// Field (7) 'TrailingField'
if size := len(v.TrailingField); size != 56 { if size := len(v.TrailingField); size != 56 {
err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56)
return return