From b1dc5e485dd02abb3ce55f2790892e56b9b07d64 Mon Sep 17 00:00:00 2001 From: Jun Song <87601811+syjn99@users.noreply.github.com> Date: Tue, 9 Sep 2025 01:50:24 +0900 Subject: [PATCH] SSZ-QL: Handle `List` type & Populate the actual value dynamically (#15637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add VariableTestContainer in ssz_query.proto * Add listInfo * Use errors.New for making an error with a static string literal * Add listInfo field when analyzing the List type * Persist the field order in the container * Add actualOffset and goFieldName at fieldInfo * Add PopulateFromValue function & update test runner * Handle slice of ssz object for marshalling * Add CalculateOffsetAndLength test * Add comments for better doc * Changelog :) * Apply reviews from Radek * Remove actualOffset and update offset field instead * Add Nested container of variable-sized for testing nested path * Fix offset adding logics: for variable-sized field, always add 4 instead of its fixed size * Fix multiple import issue --------- Co-authored-by: Radosław Kapka --- changelog/syjn99_ssz-ql-list.md | 3 + encoding/ssz/query/BUILD.bazel | 1 + encoding/ssz/query/analyzer.go | 130 ++++++++-- encoding/ssz/query/container.go | 11 +- encoding/ssz/query/list.go | 53 ++++ encoding/ssz/query/query.go | 27 +- encoding/ssz/query/query_test.go | 322 ++++++++++++++++++------ encoding/ssz/query/ssz_info.go | 93 ++++--- encoding/ssz/query/testutil/runner.go | 3 + encoding/ssz/query/testutil/util.go | 28 ++- proto/ssz_query/BUILD.bazel | 1 + proto/ssz_query/ssz_query.pb.go | 216 ++++++++++++++-- proto/ssz_query/ssz_query.proto | 32 +++ proto/ssz_query/ssz_query.ssz.go | 345 ++++++++++++++++++++++++++ 14 files changed, 1111 insertions(+), 154 deletions(-) create mode 100644 changelog/syjn99_ssz-ql-list.md create mode 100644 encoding/ssz/query/list.go diff --git a/changelog/syjn99_ssz-ql-list.md b/changelog/syjn99_ssz-ql-list.md new file mode 100644 index 0000000000..baf304da2e --- /dev/null +++ b/changelog/syjn99_ssz-ql-list.md @@ -0,0 +1,3 @@ +### Added + +- Support `List` type for SSZ-QL. diff --git a/encoding/ssz/query/BUILD.bazel b/encoding/ssz/query/BUILD.bazel index 82ce958d19..0c16edf7dd 100644 --- a/encoding/ssz/query/BUILD.bazel +++ b/encoding/ssz/query/BUILD.bazel @@ -5,6 +5,7 @@ go_library( srcs = [ "analyzer.go", "container.go", + "list.go", "path.go", "query.go", "ssz_info.go", diff --git a/encoding/ssz/query/analyzer.go b/encoding/ssz/query/analyzer.go index 82865e8b57..fb7bd472b4 100644 --- a/encoding/ssz/query/analyzer.go +++ b/encoding/ssz/query/analyzer.go @@ -1,6 +1,7 @@ package query import ( + "errors" "fmt" "reflect" "strconv" @@ -30,6 +31,89 @@ func AnalyzeObject(obj any) (*sszInfo, error) { return info, nil } +// PopulateVariableLengthInfo populates runtime information for SSZ fields of variable-sized types. +// This function updates the sszInfo structure with actual lengths and offsets that can only +// be determined at runtime for variable-sized items like Lists and variable-sized Container fields. +func PopulateVariableLengthInfo(sszInfo *sszInfo, value any) error { + if sszInfo == nil { + return errors.New("sszInfo is nil") + } + + if value == nil { + return errors.New("value is nil") + } + + // Short circuit: If the type is fixed-sized, we don't need to fill in the info. + if !sszInfo.isVariable { + return nil + } + + switch sszInfo.sszType { + // In List case, we have to set the actual length of the list. + case List: + listInfo, err := sszInfo.ListInfo() + if err != nil { + return fmt.Errorf("could not get list info: %w", err) + } + + if listInfo == nil { + return errors.New("listInfo is nil") + } + + val := reflect.ValueOf(value) + if val.Kind() != reflect.Slice { + return fmt.Errorf("expected slice for List type, got %v", val.Kind()) + } + + length := uint64(val.Len()) + if err := listInfo.SetLength(length); err != nil { + return fmt.Errorf("could not set list length: %w", err) + } + + return nil + // In Container case, we need to recursively populate variable-sized fields. + case Container: + containerInfo, err := sszInfo.ContainerInfo() + if err != nil { + return fmt.Errorf("could not get container info: %w", err) + } + + // Dereference first in case value is a pointer. + derefValue := dereferencePointer(value) + + // Start with the fixed size of this Container. + currentOffset := sszInfo.FixedSize() + + for _, fieldName := range containerInfo.order { + fieldInfo := containerInfo.fields[fieldName] + childSszInfo := fieldInfo.sszInfo + if childSszInfo == nil { + return fmt.Errorf("sszInfo is nil for field %s", fieldName) + } + + // Skip fixed-size fields. + if !childSszInfo.isVariable { + continue + } + + // Set the actual offset for variable-sized fields. + fieldInfo.offset = currentOffset + + // Recursively populate variable-sized fields. + fieldValue := derefValue.FieldByName(fieldInfo.goFieldName) + if err := PopulateVariableLengthInfo(childSszInfo, fieldValue.Interface()); err != nil { + return fmt.Errorf("could not populate from value for field %s: %w", fieldName, err) + } + + currentOffset += childSszInfo.Size() + } + + return nil + default: + return fmt.Errorf("unsupported SSZ type (%s) for variable size info", sszInfo.sszType) + } +} + // analyzeType is an entry point that inspects a reflect.Type and computes its SSZ layout information. func analyzeType(typ reflect.Type, tag *reflect.StructTag) (*sszInfo, error) { switch typ.Kind() { @@ -93,7 +177,7 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn } if tag == nil { - return nil, fmt.Errorf("tag is required for slice types") + return nil, errors.New("tag is required for slice types") } elementInfo, err := analyzeType(typ.Elem(), nil) @@ -135,7 +219,7 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn // analyzeListType analyzes SSZ List type and returns its SSZ info. func analyzeListType(typ reflect.Type, elementInfo *sszInfo, limit uint64) (*sszInfo, error) { if elementInfo == nil { - return nil, fmt.Errorf("element info is required for List") + return nil, errors.New("element info is required for List") } return &sszInfo{ @@ -144,13 +228,18 @@ func analyzeListType(typ reflect.Type, elementInfo *sszInfo, limit uint64) (*ssz fixedSize: offsetBytes, isVariable: true, + + listInfo: &listInfo{ + limit: limit, + element: elementInfo, + }, }, nil } // analyzeVectorType analyzes SSZ Vector type and returns its SSZ info. func analyzeVectorType(typ reflect.Type, elementInfo *sszInfo, length uint64) (*sszInfo, error) { if elementInfo == nil { - return nil, fmt.Errorf("element info is required for Vector") + return nil, errors.New("element info is required for Vector") } return &sszInfo{ @@ -168,11 +257,12 @@ func analyzeContainerType(typ reflect.Type) (*sszInfo, error) { return nil, fmt.Errorf("can only analyze struct types, got %v", typ.Kind()) } + fields := make(map[string]*fieldInfo) + order := make([]string, 0, typ.NumField()) + sszInfo := &sszInfo{ sszType: Container, typ: typ, - - containerInfo: make(map[string]*fieldInfo), } var currentOffset uint64 @@ -204,23 +294,31 @@ func analyzeContainerType(typ reflect.Type) (*sszInfo, error) { return nil, fmt.Errorf("could not analyze type for field %s: %w", fieldName, err) } - // If one of the fields is variable-sized, - // the entire struct is considered variable-sized. - if info.isVariable { - sszInfo.isVariable = true - } - // Store nested struct info. - sszInfo.containerInfo[fieldName] = &fieldInfo{ - sszInfo: info, - offset: currentOffset, + fields[fieldName] = &fieldInfo{ + sszInfo: info, + offset: currentOffset, + goFieldName: field.Name, } + // Persist order + order = append(order, fieldName) - // Update the current offset based on the field's fixed size. - currentOffset += info.fixedSize + // Update the current offset depending on whether the field is variable-sized. + if info.isVariable { + // If one of the fields is variable-sized, + // the entire struct is considered variable-sized. + sszInfo.isVariable = true + currentOffset += offsetBytes + } else { + currentOffset += info.fixedSize + } } sszInfo.fixedSize = currentOffset + sszInfo.containerInfo = &containerInfo{ + fields: fields, + order: order, + } return sszInfo, nil } diff --git a/encoding/ssz/query/container.go b/encoding/ssz/query/container.go index 1f16d36b2c..374bd7c4fb 100644 --- a/encoding/ssz/query/container.go +++ b/encoding/ssz/query/container.go @@ -1,11 +1,18 @@ package query -// containerInfo maps a field's JSON name to its sszInfo for nested Containers. -type containerInfo = map[string]*fieldInfo +// containerInfo has +// 1. fields: a field map that maps a field's JSON name to its sszInfo for nested Containers +// 2. order: a list of field names in the order they should be serialized +type containerInfo struct { + fields map[string]*fieldInfo + order []string +} type fieldInfo struct { // sszInfo contains the SSZ information of the field. sszInfo *sszInfo // offset is the offset of the field within the parent struct. offset uint64 + // goFieldName is the name of the field in Go struct. + goFieldName string } diff --git a/encoding/ssz/query/list.go b/encoding/ssz/query/list.go new file mode 100644 index 0000000000..ef2278d9c5 --- /dev/null +++ b/encoding/ssz/query/list.go @@ -0,0 +1,53 @@ +package query + +import ( + "errors" + "fmt" +) + +// listInfo holds information about a SSZ List type. +// +// length is initialized with zero, +// and can be set using SetLength while populating the actual SSZ List. +type listInfo struct { + // limit is the maximum number of elements in the list. + limit uint64 + // element is the SSZ info of the list's element type. + element *sszInfo + // length is the actual number of elements at runtime (0 if not set). + length uint64 +} + +func (l *listInfo) Limit() uint64 { + if l == nil { + return 0 + } + return l.limit +} + +func (l *listInfo) Element() (*sszInfo, error) { + if l == nil { + return nil, errors.New("listInfo is nil") + } + return l.element, nil +} + +func (l *listInfo) Length() uint64 { + if l == nil { + return 0 + } + return l.length +} + +func (l *listInfo) SetLength(length uint64) error { + if l == nil { + return errors.New("listInfo is nil") + } + + if length > l.limit { + return fmt.Errorf("length %d exceeds limit %d", length, l.limit) + } + + l.length = length + return nil +} diff --git a/encoding/ssz/query/query.go b/encoding/ssz/query/query.go index 12028fdc4c..c19ec94b20 100644 --- a/encoding/ssz/query/query.go +++ b/encoding/ssz/query/query.go @@ -1,37 +1,38 @@ package query -import "fmt" +import ( + "errors" + "fmt" +) +// CalculateOffsetAndLength calculates the offset and length of a given path within the SSZ object. +// By walking the given path, it accumulates the offsets based on sszInfo. func CalculateOffsetAndLength(sszInfo *sszInfo, path []PathElement) (*sszInfo, uint64, uint64, error) { if sszInfo == nil { - return nil, 0, 0, fmt.Errorf("sszInfo is nil") + return nil, 0, 0, errors.New("sszInfo is nil") } if len(path) == 0 { - return nil, 0, 0, fmt.Errorf("path is empty") + return nil, 0, 0, errors.New("path is empty") } walk := sszInfo - currentOffset := uint64(0) + offset := uint64(0) for _, elem := range path { - fieldInfos, err := walk.ContainerInfo() + containerInfo, err := walk.ContainerInfo() if err != nil { return nil, 0, 0, fmt.Errorf("could not get field infos: %w", err) } - fieldInfo, exists := fieldInfos[elem.Name] + fieldInfo, exists := containerInfo.fields[elem.Name] if !exists { - return nil, 0, 0, fmt.Errorf("field %s not found in fieldInfos", elem.Name) + return nil, 0, 0, fmt.Errorf("field %s not found in containerInfo", elem.Name) } - currentOffset += fieldInfo.offset + offset += fieldInfo.offset walk = fieldInfo.sszInfo } - if walk.isVariable { - return nil, 0, 0, fmt.Errorf("cannot calculate length for variable-sized type") - } - - return walk, currentOffset, walk.Size(), nil + return walk, offset, walk.Size(), nil } diff --git a/encoding/ssz/query/query_test.go b/encoding/ssz/query/query_test.go index e954ee75ed..41019a8ea0 100644 --- a/encoding/ssz/query/query_test.go +++ b/encoding/ssz/query/query_test.go @@ -6,100 +6,179 @@ import ( "github.com/OffchainLabs/prysm/v6/encoding/ssz/query" "github.com/OffchainLabs/prysm/v6/encoding/ssz/query/testutil" - "github.com/OffchainLabs/prysm/v6/proto/ssz_query" sszquerypb "github.com/OffchainLabs/prysm/v6/proto/ssz_query" "github.com/OffchainLabs/prysm/v6/testing/require" ) func TestCalculateOffsetAndLength(t *testing.T) { - tests := []struct { + type testCase struct { name string path string expectedOffset uint64 expectedLength uint64 - }{ - // Basic integer types - { - name: "field_uint32", - path: ".field_uint32", - expectedOffset: 0, - expectedLength: 4, - }, - { - name: "field_uint64", - path: ".field_uint64", - expectedOffset: 4, - expectedLength: 8, - }, - // Boolean type - { - name: "field_bool", - path: ".field_bool", - expectedOffset: 12, - expectedLength: 1, - }, - // Fixed-size bytes - { - name: "field_bytes32", - path: ".field_bytes32", - expectedOffset: 13, - expectedLength: 32, - }, - // Nested container - { - name: "nested container", - path: ".nested", - expectedOffset: 45, - expectedLength: 40, - }, - { - name: "nested value1", - path: ".nested.value1", - expectedOffset: 45, - expectedLength: 8, - }, - { - name: "nested value2", - path: ".nested.value2", - expectedOffset: 53, - expectedLength: 32, - }, - // Vector field - { - name: "vector field", - path: ".vector_field", - expectedOffset: 85, - expectedLength: 192, // 24 * 8 bytes - }, - // Trailing field - { - name: "trailing_field", - path: ".trailing_field", - expectedOffset: 277, - expectedLength: 56, - }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - path, err := query.ParsePath(tt.path) - require.NoError(t, err) + t.Run("FixedTestContainer", func(t *testing.T) { + tests := []testCase{ + // Basic integer types + { + name: "field_uint32", + path: ".field_uint32", + expectedOffset: 0, + expectedLength: 4, + }, + { + name: "field_uint64", + path: ".field_uint64", + expectedOffset: 4, + expectedLength: 8, + }, + // Boolean type + { + name: "field_bool", + path: ".field_bool", + expectedOffset: 12, + expectedLength: 1, + }, + // Fixed-size bytes + { + name: "field_bytes32", + path: ".field_bytes32", + expectedOffset: 13, + expectedLength: 32, + }, + // Nested container + { + name: "nested container", + path: ".nested", + expectedOffset: 45, + expectedLength: 40, + }, + { + name: "nested value1", + path: ".nested.value1", + expectedOffset: 45, + expectedLength: 8, + }, + { + name: "nested value2", + path: ".nested.value2", + expectedOffset: 53, + expectedLength: 32, + }, + // Vector field + { + name: "vector field", + path: ".vector_field", + expectedOffset: 85, + expectedLength: 192, // 24 * 8 bytes + }, + // Trailing field + { + name: "trailing_field", + path: ".trailing_field", + expectedOffset: 277, + expectedLength: 56, + }, + } - info, err := query.AnalyzeObject(&sszquerypb.FixedTestContainer{}) - require.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path, err := query.ParsePath(tt.path) + require.NoError(t, err) - _, offset, length, err := query.CalculateOffsetAndLength(info, path) - require.NoError(t, err) + info, err := query.AnalyzeObject(&sszquerypb.FixedTestContainer{}) + require.NoError(t, err) - require.Equal(t, tt.expectedOffset, offset, "Expected offset to be %d", tt.expectedOffset) - require.Equal(t, tt.expectedLength, length, "Expected length to be %d", tt.expectedLength) - }) - } + _, offset, length, err := query.CalculateOffsetAndLength(info, path) + require.NoError(t, err) + + require.Equal(t, tt.expectedOffset, offset, "Expected offset to be %d", tt.expectedOffset) + require.Equal(t, tt.expectedLength, length, "Expected length to be %d", tt.expectedLength) + }) + } + }) + + t.Run("VariableTestContainer", func(t *testing.T) { + tests := []testCase{ + // Fixed leading field + { + name: "leading_field", + path: ".leading_field", + expectedOffset: 0, + expectedLength: 32, + }, + // Variable-size list fields + { + name: "field_list_uint64", + path: ".field_list_uint64", + expectedOffset: 100, // First part of variable-sized type. + expectedLength: 40, // 5 elements * uint64 (8 bytes each) + }, + { + name: "field_list_container", + path: ".field_list_container", + expectedOffset: 140, // Second part of variable-sized type. + expectedLength: 120, // 3 elements * FixedNestedContainer (40 bytes each) + }, + // Nested paths + { + name: "nested", + path: ".nested", + expectedOffset: 260, + // Calculated with: + // - Value1: 8 bytes + // - field_list_uint64 offset: 4 bytes + // - field_list_uint64 length: 40 bytes + expectedLength: 52, + }, + { + name: "nested.value1", + path: ".nested.value1", + expectedOffset: 260, + expectedLength: 8, + }, + { + name: "nested.field_list_uint64", + path: ".nested.field_list_uint64", + expectedOffset: 272, + expectedLength: 40, + }, + // Fixed trailing field + { + name: "trailing_field", + path: ".trailing_field", + expectedOffset: 44, // After leading_field + 2 offset pointers + expectedLength: 56, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path, err := query.ParsePath(tt.path) + require.NoError(t, err) + + info, err := query.AnalyzeObject(&sszquerypb.VariableTestContainer{}) + require.NoError(t, err) + + testContainer := createVariableTestContainer() + err = query.PopulateVariableLengthInfo(info, testContainer) + require.NoError(t, err) + + _, offset, length, err := query.CalculateOffsetAndLength(info, path) + require.NoError(t, err) + + require.Equal(t, tt.expectedOffset, offset, "Expected offset to be %d", tt.expectedOffset) + require.Equal(t, tt.expectedLength, length, "Expected length to be %d", tt.expectedLength) + }) + } + }) } func TestRoundTripSszInfo(t *testing.T) { specs := []testutil.TestSpec{ getFixedTestContainerSpec(), + getVariableTestContainerSpec(), } for _, spec := range specs { @@ -107,7 +186,7 @@ func TestRoundTripSszInfo(t *testing.T) { } } -func createFixedTestContainer() any { +func createFixedTestContainer() *sszquerypb.FixedTestContainer { fieldBytes32 := make([]byte, 32) for i := range fieldBytes32 { fieldBytes32[i] = byte(i + 24) @@ -123,7 +202,7 @@ func createFixedTestContainer() any { trailingField[i] = byte(i + 88) } - return &ssz_query.FixedTestContainer{ + return &sszquerypb.FixedTestContainer{ // Basic types FieldUint32: math.MaxUint32, FieldUint64: math.MaxUint64, @@ -147,7 +226,7 @@ func createFixedTestContainer() any { } func getFixedTestContainerSpec() testutil.TestSpec { - testContainer := createFixedTestContainer().(*sszquerypb.FixedTestContainer) + testContainer := createFixedTestContainer() return testutil.TestSpec{ Name: "FixedTestContainer", @@ -198,3 +277,90 @@ func getFixedTestContainerSpec() testutil.TestSpec { }, } } + +func createVariableTestContainer() *sszquerypb.VariableTestContainer { + leadingField := make([]byte, 32) + for i := range leadingField { + leadingField[i] = byte(i + 100) + } + + trailingField := make([]byte, 56) + for i := range trailingField { + trailingField[i] = byte(i + 150) + } + + nestedContainers := make([]*sszquerypb.FixedNestedContainer, 3) + for i := range nestedContainers { + value2 := make([]byte, 32) + for j := range value2 { + value2[j] = byte(j + i*32) + } + nestedContainers[i] = &sszquerypb.FixedNestedContainer{ + Value1: uint64(1000 + i), + Value2: value2, + } + } + + return &sszquerypb.VariableTestContainer{ + // Fixed leading field + LeadingField: leadingField, + + // Variable-size lists + FieldListUint64: []uint64{100, 200, 300, 400, 500}, + FieldListContainer: nestedContainers, + + // Variable nested container + Nested: &sszquerypb.VariableNestedContainer{ + Value1: 42, + FieldListUint64: []uint64{1, 2, 3, 4, 5}, + }, + + // Fixed trailing field + TrailingField: trailingField, + } +} + +func getVariableTestContainerSpec() testutil.TestSpec { + testContainer := createVariableTestContainer() + + return testutil.TestSpec{ + Name: "VariableTestContainer", + Type: sszquerypb.VariableTestContainer{}, + Instance: testContainer, + PathTests: []testutil.PathTest{ + // Fixed leading field + { + Path: ".leading_field", + Expected: testContainer.LeadingField, + }, + // Variable-size list of uint64 + { + Path: ".field_list_uint64", + Expected: testContainer.FieldListUint64, + }, + // Variable-size list of (fixed-size) containers + { + Path: ".field_list_container", + Expected: testContainer.FieldListContainer, + }, + // Variable nested container with every path + { + Path: ".nested", + Expected: testContainer.Nested, + }, + { + Path: ".nested.value1", + Expected: testContainer.Nested.Value1, + }, + { + Path: ".nested.field_list_uint64", + Expected: testContainer.Nested.FieldListUint64, + }, + // Fixed trailing field + { + Path: ".trailing_field", + Expected: testContainer.TrailingField, + }, + }, + } +} diff --git a/encoding/ssz/query/ssz_info.go b/encoding/ssz/query/ssz_info.go index 54827b8830..668777584d 100644 --- a/encoding/ssz/query/ssz_info.go +++ b/encoding/ssz/query/ssz_info.go @@ -1,9 +1,9 @@ package query import ( + "errors" "fmt" "reflect" - "sort" "strings" ) @@ -20,7 +20,10 @@ type sszInfo struct { fixedSize uint64 // For Container types. - containerInfo containerInfo + containerInfo *containerInfo + + // For List types. + listInfo *listInfo } func (info *sszInfo) FixedSize() uint64 { @@ -40,13 +43,33 @@ func (info *sszInfo) Size() uint64 { return info.fixedSize } - // NOTE: Handle variable-sized types. - return 0 + switch info.sszType { + case List: + length := info.listInfo.length + elementSize := info.listInfo.element.Size() + + return length * elementSize + + case Container: + size := info.fixedSize + for _, fieldInfo := range info.containerInfo.fields { + if !fieldInfo.sszInfo.isVariable { + continue + } + + size += fieldInfo.sszInfo.Size() + } + return size + + default: + // NOTE: Handle other variable-sized types. + return 0 + } } -func (info *sszInfo) ContainerInfo() (containerInfo, error) { +func (info *sszInfo) ContainerInfo() (*containerInfo, error) { if info == nil { - return nil, fmt.Errorf("sszInfo is nil") + return nil, errors.New("sszInfo is nil") } if info.sszType != Container { @@ -54,12 +77,24 @@ func (info *sszInfo) ContainerInfo() (containerInfo, error) { } if info.containerInfo == nil { - return nil, fmt.Errorf("sszInfo.containerInfo is nil") + return nil, errors.New("sszInfo.containerInfo is nil") } return info.containerInfo, nil } +func (info *sszInfo) ListInfo() (*listInfo, error) { + if info == nil { + return nil, errors.New("sszInfo is nil") + } + + if info.sszType != List { + return nil, fmt.Errorf("sszInfo is not a List type, got %s", info.sszType) + } + + return info.listInfo, nil +} + // Print returns a string representation of the sszInfo, which is useful for debugging. func (info *sszInfo) Print() string { if info == nil { @@ -81,30 +116,28 @@ func printRecursive(info *sszInfo, builder *strings.Builder, prefix string) { switch info.sszType { case Container: builder.WriteString(fmt.Sprintf("%s: %s (%s / fixed size: %d, total size: %d)\n", info.sszType, info.typ.Name(), sizeDesc, info.FixedSize(), info.Size())) + + for i, key := range info.containerInfo.order { + connector := "├─" + nextPrefix := prefix + "│ " + if i == len(info.containerInfo.order)-1 { + connector = "└─" + nextPrefix = prefix + " " + } + + builder.WriteString(fmt.Sprintf("%s%s %s (offset: %d) ", prefix, connector, key, info.containerInfo.fields[key].offset)) + + if nestedInfo := info.containerInfo.fields[key].sszInfo; nestedInfo != nil { + printRecursive(nestedInfo, builder, nextPrefix) + } else { + builder.WriteString("\n") + } + } + + case List: + builder.WriteString(fmt.Sprintf("%s[%s] (%s / limit: %d, length: %d, size: %d)\n", info.sszType, info.listInfo.element.typ.Name(), sizeDesc, info.listInfo.limit, info.listInfo.length, info.Size())) + default: builder.WriteString(fmt.Sprintf("%s (%s / size: %d)\n", info.sszType, sizeDesc, info.Size())) } - - keys := make([]string, 0, len(info.containerInfo)) - for k := range info.containerInfo { - keys = append(keys, k) - } - sort.Strings(keys) - - for i, key := range keys { - connector := "├─" - nextPrefix := prefix + "│ " - if i == len(keys)-1 { - connector = "└─" - nextPrefix = prefix + " " - } - - builder.WriteString(fmt.Sprintf("%s%s %s (offset: %d) ", prefix, connector, key, info.containerInfo[key].offset)) - - if nestedInfo := info.containerInfo[key].sszInfo; nestedInfo != nil { - printRecursive(nestedInfo, builder, nextPrefix) - } else { - builder.WriteString("\n") - } - } } diff --git a/encoding/ssz/query/testutil/runner.go b/encoding/ssz/query/testutil/runner.go index bc3db90547..92dbc94c13 100644 --- a/encoding/ssz/query/testutil/runner.go +++ b/encoding/ssz/query/testutil/runner.go @@ -14,6 +14,9 @@ func RunStructTest(t *testing.T, spec TestSpec) { require.NoError(t, err) testInstance := spec.Instance + err = query.PopulateVariableLengthInfo(info, testInstance) + require.NoError(t, err) + marshaller, ok := testInstance.(ssz.Marshaler) require.Equal(t, true, ok, "Test instance must implement ssz.Marshaler, got %T", testInstance) diff --git a/encoding/ssz/query/testutil/util.go b/encoding/ssz/query/testutil/util.go index 039e6e8ebd..3f99357230 100644 --- a/encoding/ssz/query/testutil/util.go +++ b/encoding/ssz/query/testutil/util.go @@ -14,8 +14,12 @@ func marshalAny(value any) ([]byte, error) { return marshaler.MarshalSSZ() } - // Handle custom type aliases by checking if they're based on primitive types valueType := reflect.TypeOf(value) + if valueType.Kind() == reflect.Slice && valueType.Elem().Kind() != reflect.Uint8 { + return marshalSlice(value) + } + + // Handle custom type aliases by checking if they're based on primitive types if valueType.PkgPath() != "" { switch valueType.Kind() { case reflect.Uint64: @@ -47,3 +51,25 @@ func marshalAny(value any) ([]byte, error) { return nil, fmt.Errorf("unsupported type for SSZ marshalling: %T", value) } } + +func marshalSlice(value any) ([]byte, error) { + valueType := reflect.TypeOf(value) + + if valueType.Kind() != reflect.Slice { + return nil, fmt.Errorf("expected slice, got %T", value) + } + + sliceValue := reflect.ValueOf(value) + var result []byte + + // Marshal each element recursively + for i := 0; i < sliceValue.Len(); i++ { + elem := sliceValue.Index(i).Interface() + data, err := marshalAny(elem) + if err != nil { + return nil, fmt.Errorf("failed to marshal slice element at index %d: %w", i, err) + } + result = append(result, data...) + } + return result, nil +} diff --git a/proto/ssz_query/BUILD.bazel b/proto/ssz_query/BUILD.bazel index 1df73ce8ff..933689529d 100644 --- a/proto/ssz_query/BUILD.bazel +++ b/proto/ssz_query/BUILD.bazel @@ -39,6 +39,7 @@ ssz_gen_marshal( objs = [ "FixedTestContainer", "FixedNestedContainer", + "VariableTestContainer", ], ) diff --git a/proto/ssz_query/ssz_query.pb.go b/proto/ssz_query/ssz_query.pb.go index dac481abe3..6ce0180777 100755 --- a/proto/ssz_query/ssz_query.pb.go +++ b/proto/ssz_query/ssz_query.pb.go @@ -172,6 +172,140 @@ func (x *FixedTestContainer) GetTrailingField() []byte { return nil } +type VariableNestedContainer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + 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"` +} + +func (x *VariableNestedContainer) Reset() { + *x = VariableNestedContainer{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VariableNestedContainer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VariableNestedContainer) ProtoMessage() {} + +func (x *VariableNestedContainer) ProtoReflect() protoreflect.Message { + mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VariableNestedContainer.ProtoReflect.Descriptor instead. +func (*VariableNestedContainer) Descriptor() ([]byte, []int) { + return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{2} +} + +func (x *VariableNestedContainer) GetValue1() uint64 { + if x != nil { + return x.Value1 + } + return 0 +} + +func (x *VariableNestedContainer) GetFieldListUint64() []uint64 { + if x != nil { + return x.FieldListUint64 + } + return nil +} + +type VariableTestContainer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LeadingField []byte `protobuf:"bytes,1,opt,name=leading_field,json=leadingField,proto3" json:"leading_field,omitempty" ssz-size:"32"` + FieldListUint64 []uint64 `protobuf:"varint,2,rep,packed,name=field_list_uint64,json=fieldListUint64,proto3" json:"field_list_uint64,omitempty" ssz-max:"2048"` + FieldListContainer []*FixedNestedContainer `protobuf:"bytes,3,rep,name=field_list_container,json=fieldListContainer,proto3" json:"field_list_container,omitempty" ssz-max:"128"` + Nested *VariableNestedContainer `protobuf:"bytes,4,opt,name=nested,proto3" json:"nested,omitempty"` + TrailingField []byte `protobuf:"bytes,5,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"` +} + +func (x *VariableTestContainer) Reset() { + *x = VariableTestContainer{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VariableTestContainer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VariableTestContainer) ProtoMessage() {} + +func (x *VariableTestContainer) ProtoReflect() protoreflect.Message { + mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VariableTestContainer.ProtoReflect.Descriptor instead. +func (*VariableTestContainer) Descriptor() ([]byte, []int) { + return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{3} +} + +func (x *VariableTestContainer) GetLeadingField() []byte { + if x != nil { + return x.LeadingField + } + return nil +} + +func (x *VariableTestContainer) GetFieldListUint64() []uint64 { + if x != nil { + return x.FieldListUint64 + } + return nil +} + +func (x *VariableTestContainer) GetFieldListContainer() []*FixedNestedContainer { + if x != nil { + return x.FieldListContainer + } + return nil +} + +func (x *VariableTestContainer) GetNested() *VariableNestedContainer { + if x != nil { + return x.Nested + } + return nil +} + +func (x *VariableTestContainer) GetTrailingField() []byte { + if x != nil { + return x.TrailingField + } + return nil +} + var File_proto_ssz_query_ssz_query_proto protoreflect.FileDescriptor var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{ @@ -204,11 +338,37 @@ var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{ 0x72, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x0b, 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, 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, + 0x46, 0x69, 0x65, 0x6c, 0x64, 0x22, 0x66, 0x0a, 0x17, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x31, 0x12, 0x33, 0x0a, 0x11, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x04, 0x42, 0x07, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x30, 0x30, 0x52, 0x0f, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x22, 0xc1, 0x02, + 0x0a, 0x15, 0x56, 0x61, 0x72, 0x69, 0x61, 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, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, + 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x46, + 0x69, 0x65, 0x6c, 0x64, 0x12, 0x34, 0x0a, 0x11, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, + 0x73, 0x74, 0x5f, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x42, + 0x08, 0x92, 0xb5, 0x18, 0x04, 0x32, 0x30, 0x34, 0x38, 0x52, 0x0f, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x4c, 0x69, 0x73, 0x74, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x5a, 0x0a, 0x14, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, + 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x78, 0x65, 0x64, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x42, 0x07, 0x92, 0xb5, 0x18, 0x03, 0x31, + 0x32, 0x38, 0x52, 0x12, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, + 0x72, 0x79, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x06, 0x6e, 0x65, 0x73, 0x74, + 0x65, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x18, 0x05, 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, 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 ( @@ -223,18 +383,22 @@ func file_proto_ssz_query_ssz_query_proto_rawDescGZIP() []byte { return file_proto_ssz_query_ssz_query_proto_rawDescData } -var file_proto_ssz_query_ssz_query_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_ssz_query_ssz_query_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proto_ssz_query_ssz_query_proto_goTypes = []interface{}{ - (*FixedNestedContainer)(nil), // 0: ssz_query.FixedNestedContainer - (*FixedTestContainer)(nil), // 1: ssz_query.FixedTestContainer + (*FixedNestedContainer)(nil), // 0: ssz_query.FixedNestedContainer + (*FixedTestContainer)(nil), // 1: ssz_query.FixedTestContainer + (*VariableNestedContainer)(nil), // 2: ssz_query.VariableNestedContainer + (*VariableTestContainer)(nil), // 3: ssz_query.VariableTestContainer } var file_proto_ssz_query_ssz_query_proto_depIdxs = []int32{ 0, // 0: ssz_query.FixedTestContainer.nested:type_name -> ssz_query.FixedNestedContainer - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 0, // 1: ssz_query.VariableTestContainer.field_list_container:type_name -> ssz_query.FixedNestedContainer + 2, // 2: ssz_query.VariableTestContainer.nested:type_name -> ssz_query.VariableNestedContainer + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_proto_ssz_query_ssz_query_proto_init() } @@ -267,6 +431,30 @@ func file_proto_ssz_query_ssz_query_proto_init() { return nil } } + file_proto_ssz_query_ssz_query_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VariableNestedContainer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_ssz_query_ssz_query_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VariableTestContainer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -274,7 +462,7 @@ func file_proto_ssz_query_ssz_query_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_ssz_query_ssz_query_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/ssz_query/ssz_query.proto b/proto/ssz_query/ssz_query.proto index e6d3ae2369..fee50f3caa 100644 --- a/proto/ssz_query/ssz_query.proto +++ b/proto/ssz_query/ssz_query.proto @@ -41,3 +41,35 @@ message FixedTestContainer { // Additional bytes field - test field ordering and offset calculation bytes trailing_field = 11 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: trailing field after vector, offset: 277 } + +// ===== VARIABLE-SIZE TEST CONTAINERS ===== + +// VariableNestedContainer - nested container for testing nested field access +// Tests: nested container navigation, field offset calculations within nested structures +message VariableNestedContainer { + uint64 value1 = 1; + repeated uint64 field_list_uint64 = 2 [ (ethereum.eth.ext.ssz_max) = "100" ]; +} + +// VariableTestContainer - comprehensive variable-size container for SSZ query testing +// Tests: Variable-size lists, offsets in variable containers, mixed fixed/variable fields +message VariableTestContainer { + // Fixed-size leading field - test fixed field before variable fields + // Acts as a baseline to verify offset calculations start correctly + bytes leading_field = 1 [ (ethereum.eth.ext.ssz_size) = "32" ]; // Test: fixed 32-byte field at start, offset: 0 + + // Variable-size list of basic type - test list with primitive elements + // SSZ uses 4-byte offset pointer, actual data stored after all fixed fields + repeated uint64 field_list_uint64 = 2 [ (ethereum.eth.ext.ssz_max) = "2048" ]; // Test: List[uint64, 2048] (max 2048 elements) + + // Variable-size list of containers - test list with composite elements + // Each container is fixed-size (40 bytes), but list itself is variable + repeated FixedNestedContainer field_list_container = 3 [ (ethereum.eth.ext.ssz_max) = "128" ]; // Test: List[FixedNestedContainer, 128] + + // Variable nested container - test nested container access within variable container + VariableNestedContainer nested = 4; + + // Fixed-size trailing field - test fixed field after variable fields + // Verifies correct offset calculation after variable-size fields + bytes trailing_field = 5 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: fixed 56-byte field at end, offset: 32 + 4 + 4 + 4 = 44 +} diff --git a/proto/ssz_query/ssz_query.ssz.go b/proto/ssz_query/ssz_query.ssz.go index c5ec4a7595..6e1f7e61f4 100644 --- a/proto/ssz_query/ssz_query.ssz.go +++ b/proto/ssz_query/ssz_query.ssz.go @@ -236,3 +236,348 @@ func (f *FixedTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) { hh.Merkleize(indx) return } + +// MarshalSSZ ssz marshals the VariableNestedContainer object +func (v *VariableNestedContainer) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(v) +} + +// MarshalSSZTo ssz marshals the VariableNestedContainer object to a target array +func (v *VariableNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(12) + + // Field (0) 'Value1' + dst = ssz.MarshalUint64(dst, v.Value1) + + // Offset (1) 'FieldListUint64' + dst = ssz.WriteOffset(dst, offset) + offset += len(v.FieldListUint64) * 8 + + // Field (1) 'FieldListUint64' + if size := len(v.FieldListUint64); size > 100 { + err = ssz.ErrListTooBigFn("--.FieldListUint64", size, 100) + return + } + for ii := 0; ii < len(v.FieldListUint64); ii++ { + dst = ssz.MarshalUint64(dst, v.FieldListUint64[ii]) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the VariableNestedContainer object +func (v *VariableNestedContainer) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 12 { + return ssz.ErrSize + } + + tail := buf + var o1 uint64 + + // Field (0) 'Value1' + v.Value1 = ssz.UnmarshallUint64(buf[0:8]) + + // Offset (1) 'FieldListUint64' + if o1 = ssz.ReadOffset(buf[8:12]); o1 > size { + return ssz.ErrOffset + } + + if o1 != 12 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'FieldListUint64' + { + buf = tail[o1:] + num, err := ssz.DivideInt2(len(buf), 8, 100) + if err != nil { + return err + } + v.FieldListUint64 = ssz.ExtendUint64(v.FieldListUint64, num) + for ii := 0; ii < num; ii++ { + v.FieldListUint64[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the VariableNestedContainer object +func (v *VariableNestedContainer) SizeSSZ() (size int) { + size = 12 + + // Field (1) 'FieldListUint64' + size += len(v.FieldListUint64) * 8 + + return +} + +// HashTreeRoot ssz hashes the VariableNestedContainer object +func (v *VariableNestedContainer) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(v) +} + +// HashTreeRootWith ssz hashes the VariableNestedContainer object with a hasher +func (v *VariableNestedContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'Value1' + hh.PutUint64(v.Value1) + + // Field (1) 'FieldListUint64' + { + if size := len(v.FieldListUint64); size > 100 { + err = ssz.ErrListTooBigFn("--.FieldListUint64", size, 100) + return + } + subIndx := hh.Index() + for _, i := range v.FieldListUint64 { + hh.AppendUint64(i) + } + hh.FillUpTo32() + + numItems := uint64(len(v.FieldListUint64)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(100, numItems, 8)) + } + + hh.Merkleize(indx) + return +} + +// MarshalSSZ ssz marshals the VariableTestContainer object +func (v *VariableTestContainer) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(v) +} + +// MarshalSSZTo ssz marshals the VariableTestContainer object to a target array +func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(100) + + // Field (0) 'LeadingField' + if size := len(v.LeadingField); size != 32 { + err = ssz.ErrBytesLengthFn("--.LeadingField", size, 32) + return + } + dst = append(dst, v.LeadingField...) + + // Offset (1) 'FieldListUint64' + dst = ssz.WriteOffset(dst, offset) + offset += len(v.FieldListUint64) * 8 + + // Offset (2) 'FieldListContainer' + dst = ssz.WriteOffset(dst, offset) + offset += len(v.FieldListContainer) * 40 + + // Offset (3) 'Nested' + dst = ssz.WriteOffset(dst, offset) + if v.Nested == nil { + v.Nested = new(VariableNestedContainer) + } + offset += v.Nested.SizeSSZ() + + // Field (4) 'TrailingField' + if size := len(v.TrailingField); size != 56 { + err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) + return + } + dst = append(dst, v.TrailingField...) + + // Field (1) 'FieldListUint64' + if size := len(v.FieldListUint64); size > 2048 { + err = ssz.ErrListTooBigFn("--.FieldListUint64", size, 2048) + return + } + for ii := 0; ii < len(v.FieldListUint64); ii++ { + dst = ssz.MarshalUint64(dst, v.FieldListUint64[ii]) + } + + // Field (2) 'FieldListContainer' + if size := len(v.FieldListContainer); size > 128 { + err = ssz.ErrListTooBigFn("--.FieldListContainer", size, 128) + return + } + for ii := 0; ii < len(v.FieldListContainer); ii++ { + if dst, err = v.FieldListContainer[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (3) 'Nested' + if dst, err = v.Nested.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the VariableTestContainer object +func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 100 { + return ssz.ErrSize + } + + tail := buf + var o1, o2, o3 uint64 + + // Field (0) 'LeadingField' + if cap(v.LeadingField) == 0 { + v.LeadingField = make([]byte, 0, len(buf[0:32])) + } + v.LeadingField = append(v.LeadingField, buf[0:32]...) + + // Offset (1) 'FieldListUint64' + if o1 = ssz.ReadOffset(buf[32:36]); o1 > size { + return ssz.ErrOffset + } + + if o1 != 100 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (2) 'FieldListContainer' + if o2 = ssz.ReadOffset(buf[36:40]); o2 > size || o1 > o2 { + return ssz.ErrOffset + } + + // Offset (3) 'Nested' + if o3 = ssz.ReadOffset(buf[40:44]); o3 > size || o2 > o3 { + return ssz.ErrOffset + } + + // Field (4) 'TrailingField' + if cap(v.TrailingField) == 0 { + v.TrailingField = make([]byte, 0, len(buf[44:100])) + } + v.TrailingField = append(v.TrailingField, buf[44:100]...) + + // Field (1) 'FieldListUint64' + { + buf = tail[o1:o2] + num, err := ssz.DivideInt2(len(buf), 8, 2048) + if err != nil { + return err + } + v.FieldListUint64 = ssz.ExtendUint64(v.FieldListUint64, num) + for ii := 0; ii < num; ii++ { + v.FieldListUint64[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) + } + } + + // Field (2) 'FieldListContainer' + { + buf = tail[o2:o3] + num, err := ssz.DivideInt2(len(buf), 40, 128) + if err != nil { + return err + } + v.FieldListContainer = make([]*FixedNestedContainer, num) + for ii := 0; ii < num; ii++ { + if v.FieldListContainer[ii] == nil { + v.FieldListContainer[ii] = new(FixedNestedContainer) + } + if err = v.FieldListContainer[ii].UnmarshalSSZ(buf[ii*40 : (ii+1)*40]); err != nil { + return err + } + } + } + + // Field (3) 'Nested' + { + buf = tail[o3:] + if v.Nested == nil { + v.Nested = new(VariableNestedContainer) + } + if err = v.Nested.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the VariableTestContainer object +func (v *VariableTestContainer) SizeSSZ() (size int) { + size = 100 + + // Field (1) 'FieldListUint64' + size += len(v.FieldListUint64) * 8 + + // Field (2) 'FieldListContainer' + size += len(v.FieldListContainer) * 40 + + // Field (3) 'Nested' + if v.Nested == nil { + v.Nested = new(VariableNestedContainer) + } + size += v.Nested.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the VariableTestContainer object +func (v *VariableTestContainer) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(v) +} + +// HashTreeRootWith ssz hashes the VariableTestContainer object with a hasher +func (v *VariableTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // Field (0) 'LeadingField' + if size := len(v.LeadingField); size != 32 { + err = ssz.ErrBytesLengthFn("--.LeadingField", size, 32) + return + } + hh.PutBytes(v.LeadingField) + + // Field (1) 'FieldListUint64' + { + if size := len(v.FieldListUint64); size > 2048 { + err = ssz.ErrListTooBigFn("--.FieldListUint64", size, 2048) + return + } + subIndx := hh.Index() + for _, i := range v.FieldListUint64 { + hh.AppendUint64(i) + } + hh.FillUpTo32() + + numItems := uint64(len(v.FieldListUint64)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(2048, numItems, 8)) + } + + // Field (2) 'FieldListContainer' + { + subIndx := hh.Index() + num := uint64(len(v.FieldListContainer)) + if num > 128 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range v.FieldListContainer { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 128) + } + + // Field (3) 'Nested' + if err = v.Nested.HashTreeRootWith(hh); err != nil { + return + } + + // Field (4) 'TrailingField' + if size := len(v.TrailingField); size != 56 { + err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) + return + } + hh.PutBytes(v.TrailingField) + + hh.Merkleize(indx) + return +}