From e1117a7de22c8df679b5c55b761380ff395b12ae Mon Sep 17 00:00:00 2001 From: Jun Song <87601811+syjn99@users.noreply.github.com> Date: Wed, 10 Sep 2025 05:35:06 +0900 Subject: [PATCH] SSZ-QL: Handle `Vector` type & Add SSZ tag parser for multi-dimensional parsing (#15668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add vectorInfo * Add 2D bytes field for test * Add tag_parser for parsing SSZ tags * Integrate tag parser with analyzer * Add ByteList test case * Changelog * Better printing feature with Stringer implementation * Return error for non-determined case without printing other values * Update tag_parser.go: handle Vector and List mutually exclusive (inspired by OffchainLabs/fastssz) * Make linter happy --------- Co-authored-by: Radosław Kapka --- changelog/syjn99_ssz-ql-tag-parser.md | 4 + encoding/ssz/query/BUILD.bazel | 3 + encoding/ssz/query/analyzer.go | 71 +++++----- encoding/ssz/query/analyzer_test.go | 2 +- encoding/ssz/query/query_test.go | 51 ++++++- encoding/ssz/query/ssz_info.go | 44 +++++- encoding/ssz/query/tag_parser.go | 130 ++++++++++++++++++ encoding/ssz/query/tag_parser_test.go | 187 ++++++++++++++++++++++++++ encoding/ssz/query/vector.go | 27 ++++ proto/ssz_query/ssz_query.pb.go | 111 +++++++++------ proto/ssz_query/ssz_query.proto | 15 ++- proto/ssz_query/ssz_query.ssz.go | 151 +++++++++++++++++---- 12 files changed, 677 insertions(+), 119 deletions(-) create mode 100644 changelog/syjn99_ssz-ql-tag-parser.md create mode 100644 encoding/ssz/query/tag_parser.go create mode 100644 encoding/ssz/query/tag_parser_test.go create mode 100644 encoding/ssz/query/vector.go diff --git a/changelog/syjn99_ssz-ql-tag-parser.md b/changelog/syjn99_ssz-ql-tag-parser.md new file mode 100644 index 0000000000..acc2c63ae6 --- /dev/null +++ b/changelog/syjn99_ssz-ql-tag-parser.md @@ -0,0 +1,4 @@ +### Added + +- SSZ-QL: Add element information for `Vector` type. +- SSZ-QL: Support multi-dimensional tag parsing. diff --git a/encoding/ssz/query/BUILD.bazel b/encoding/ssz/query/BUILD.bazel index 0c16edf7dd..9bf99e6552 100644 --- a/encoding/ssz/query/BUILD.bazel +++ b/encoding/ssz/query/BUILD.bazel @@ -10,6 +10,8 @@ go_library( "query.go", "ssz_info.go", "ssz_type.go", + "tag_parser.go", + "vector.go", ], importpath = "github.com/OffchainLabs/prysm/v6/encoding/ssz/query", visibility = ["//visibility:public"], @@ -21,6 +23,7 @@ go_test( "analyzer_test.go", "path_test.go", "query_test.go", + "tag_parser_test.go", ], deps = [ ":go_default_library", diff --git a/encoding/ssz/query/analyzer.go b/encoding/ssz/query/analyzer.go index fb7bd472b4..79b6a8be1e 100644 --- a/encoding/ssz/query/analyzer.go +++ b/encoding/ssz/query/analyzer.go @@ -4,20 +4,10 @@ import ( "errors" "fmt" "reflect" - "strconv" "strings" ) -const ( - offsetBytes = 4 - - // sszMaxTag specifies the maximum capacity of a variable-sized collection, like an SSZ List. - sszMaxTag = "ssz-max" - - // sszSizeTag specifies the length of a fixed-sized collection, like an SSZ Vector. - // A wildcard ('?') indicates that the dimension is variable-sized (a List). - sszSizeTag = "ssz-size" -) +const offsetBytes = 4 // AnalyzeObject analyzes given object and returns its SSZ information. func AnalyzeObject(obj any) (*sszInfo, error) { @@ -176,44 +166,44 @@ func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszIn return nil, fmt.Errorf("can only analyze slice types, got %v", typ.Kind()) } - if tag == nil { - return nil, errors.New("tag is required for slice types") + // Parse the first dimension from the tag and get remaining tag for element + sszDimension, remainingTag, err := ParseSSZTag(tag) + if err != nil { + return nil, fmt.Errorf("could not parse SSZ tag: %w", err) + } + if sszDimension == nil { + return nil, errors.New("ssz tag is required for slice types") } - elementInfo, err := analyzeType(typ.Elem(), nil) + // Analyze element type with remaining dimensions + elementInfo, err := analyzeType(typ.Elem(), remainingTag) if err != nil { return nil, fmt.Errorf("could not analyze element type for homogeneous collection: %w", err) } - // 1. Check if the type is List/Bitlist by checking `ssz-max` tag. - sszMax := tag.Get(sszMaxTag) - if sszMax != "" { - dims := strings.Split(sszMax, ",") - if len(dims) > 1 { - return nil, fmt.Errorf("multi-dimensional lists are not supported, got %d dimensions", len(dims)) - } - - limit, err := strconv.ParseUint(dims[0], 10, 64) + // 1. Handle List/Bitlist type + if sszDimension.IsList() { + limit, err := sszDimension.GetListLimit() if err != nil { - return nil, fmt.Errorf("invalid ssz-max tag (%s): %w", sszMax, err) + return nil, fmt.Errorf("could not get list limit: %w", err) } return analyzeListType(typ, elementInfo, limit) } - // 2. Handle Vector/Bitvector type. - sszSize := tag.Get(sszSizeTag) - dims := strings.Split(sszSize, ",") - if len(dims) > 1 { - return nil, fmt.Errorf("multi-dimensional vectors are not supported, got %d dimensions", len(dims)) + // 2. Handle Vector/Bitvector type + if sszDimension.IsVector() { + length, err := sszDimension.GetVectorLength() + if err != nil { + return nil, fmt.Errorf("could not get vector length: %w", err) + } + + return analyzeVectorType(typ, elementInfo, length) } - length, err := strconv.ParseUint(dims[0], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid ssz-size tag (%s): %w", sszSize, err) - } - - return analyzeVectorType(typ, elementInfo, length) + // Parsing ssz tag doesn't provide enough information to determine the collection type, + // return an error. + return nil, errors.New("could not determine collection type from tags") } // analyzeListType analyzes SSZ List type and returns its SSZ info. @@ -242,12 +232,23 @@ func analyzeVectorType(typ reflect.Type, elementInfo *sszInfo, length uint64) (* return nil, errors.New("element info is required for Vector") } + // Validate the given length. + // https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md#illegal-types + if length == 0 { + return nil, fmt.Errorf("vector length must be greater than 0, got %d", length) + } + return &sszInfo{ sszType: Vector, typ: typ, fixedSize: length * elementInfo.Size(), isVariable: false, + + vectorInfo: &vectorInfo{ + length: length, + element: elementInfo, + }, }, nil } diff --git a/encoding/ssz/query/analyzer_test.go b/encoding/ssz/query/analyzer_test.go index 9580481232..089cf915ae 100644 --- a/encoding/ssz/query/analyzer_test.go +++ b/encoding/ssz/query/analyzer_test.go @@ -13,5 +13,5 @@ func TestAnalyzeSSZInfo(t *testing.T) { require.NoError(t, err) require.NotNil(t, info, "Expected non-nil SSZ info") - require.Equal(t, uint64(333), info.FixedSize(), "Expected fixed size to be 333") + require.Equal(t, uint64(493), info.FixedSize(), "Expected fixed size to be 333") } diff --git a/encoding/ssz/query/query_test.go b/encoding/ssz/query/query_test.go index 41019a8ea0..fc57e585c5 100644 --- a/encoding/ssz/query/query_test.go +++ b/encoding/ssz/query/query_test.go @@ -73,11 +73,18 @@ func TestCalculateOffsetAndLength(t *testing.T) { expectedOffset: 85, expectedLength: 192, // 24 * 8 bytes }, + // 2D bytes field + { + name: "two_dimension_bytes_field", + path: ".two_dimension_bytes_field", + expectedOffset: 277, + expectedLength: 160, // 5 * 32 bytes + }, // Trailing field { name: "trailing_field", path: ".trailing_field", - expectedOffset: 277, + expectedOffset: 437, expectedLength: 56, }, } @@ -112,20 +119,26 @@ func TestCalculateOffsetAndLength(t *testing.T) { { name: "field_list_uint64", path: ".field_list_uint64", - expectedOffset: 100, // First part of variable-sized type. + expectedOffset: 104, // 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. + expectedOffset: 144, // Second part of variable-sized type. expectedLength: 120, // 3 elements * FixedNestedContainer (40 bytes each) }, + { + name: "field_list_bytes32", + path: ".field_list_bytes32", + expectedOffset: 264, + expectedLength: 96, // 3 elements * 32 bytes each + }, // Nested paths { name: "nested", path: ".nested", - expectedOffset: 260, + expectedOffset: 360, // Calculated with: // - Value1: 8 bytes // - field_list_uint64 offset: 4 bytes @@ -135,20 +148,20 @@ func TestCalculateOffsetAndLength(t *testing.T) { { name: "nested.value1", path: ".nested.value1", - expectedOffset: 260, + expectedOffset: 360, expectedLength: 8, }, { name: "nested.field_list_uint64", path: ".nested.field_list_uint64", - expectedOffset: 272, + expectedOffset: 372, expectedLength: 40, }, // Fixed trailing field { name: "trailing_field", path: ".trailing_field", - expectedOffset: 44, // After leading_field + 2 offset pointers + expectedOffset: 48, // After leading_field + 4 offset pointers expectedLength: 56, }, } @@ -220,6 +233,15 @@ func createFixedTestContainer() *sszquerypb.FixedTestContainer { // Vector field VectorField: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}, + // 2D bytes field + TwoDimensionBytesField: [][]byte{ + make([]byte, 32), + make([]byte, 32), + make([]byte, 32), + make([]byte, 32), + make([]byte, 32), + }, + // Trailing field TrailingField: trailingField, } @@ -269,6 +291,11 @@ func getFixedTestContainerSpec() testutil.TestSpec { Path: ".vector_field", Expected: testContainer.VectorField, }, + // 2D bytes field + { + Path: ".two_dimension_bytes_field", + Expected: testContainer.TwoDimensionBytesField, + }, // Trailing field { Path: ".trailing_field", @@ -308,6 +335,11 @@ func createVariableTestContainer() *sszquerypb.VariableTestContainer { // Variable-size lists FieldListUint64: []uint64{100, 200, 300, 400, 500}, FieldListContainer: nestedContainers, + FieldListBytes32: [][]byte{ + make([]byte, 32), + make([]byte, 32), + make([]byte, 32), + }, // Variable nested container Nested: &sszquerypb.VariableNestedContainer{ @@ -343,6 +375,11 @@ func getVariableTestContainerSpec() testutil.TestSpec { Path: ".field_list_container", Expected: testContainer.FieldListContainer, }, + // Variable-size list of bytes32 + { + Path: ".field_list_bytes32", + Expected: testContainer.FieldListBytes32, + }, // Variable nested container with every path { Path: ".nested", diff --git a/encoding/ssz/query/ssz_info.go b/encoding/ssz/query/ssz_info.go index 668777584d..71f57d8965 100644 --- a/encoding/ssz/query/ssz_info.go +++ b/encoding/ssz/query/ssz_info.go @@ -24,6 +24,9 @@ type sszInfo struct { // For List types. listInfo *listInfo + + // For Vector types. + vectorInfo *vectorInfo } func (info *sszInfo) FixedSize() uint64 { @@ -95,6 +98,41 @@ func (info *sszInfo) ListInfo() (*listInfo, error) { return info.listInfo, nil } +func (info *sszInfo) VectorInfo() (*vectorInfo, error) { + if info == nil { + return nil, errors.New("sszInfo is nil") + } + + if info.sszType != Vector { + return nil, fmt.Errorf("sszInfo is not a Vector type, got %s", info.sszType) + } + + return info.vectorInfo, nil +} + +// String implements the Stringer interface for sszInfo. +// This follows the notation used in the consensus specs. +func (info *sszInfo) String() string { + if info == nil { + return "" + } + + switch info.sszType { + case List: + return fmt.Sprintf("List[%s, %d]", info.listInfo.element, info.listInfo.limit) + case Vector: + if info.vectorInfo.element.String() == "uint8" { + // Handle byte vectors as BytesN + // See Aliases section in SSZ spec: + // https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md#aliases + return fmt.Sprintf("Bytes%d", info.vectorInfo.length) + } + return fmt.Sprintf("Vector[%s, %d]", info.vectorInfo.element, info.vectorInfo.length) + default: + return info.typ.Name() + } +} + // Print returns a string representation of the sszInfo, which is useful for debugging. func (info *sszInfo) Print() string { if info == nil { @@ -115,7 +153,7 @@ 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())) + builder.WriteString(fmt.Sprintf("%s (%s / fixed size: %d, total size: %d)\n", info, sizeDesc, info.FixedSize(), info.Size())) for i, key := range info.containerInfo.order { connector := "├─" @@ -135,9 +173,9 @@ func printRecursive(info *sszInfo, builder *strings.Builder, prefix string) { } 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())) + builder.WriteString(fmt.Sprintf("%s (%s / length: %d, size: %d)\n", info, sizeDesc, info.listInfo.length, info.Size())) default: - builder.WriteString(fmt.Sprintf("%s (%s / size: %d)\n", info.sszType, sizeDesc, info.Size())) + builder.WriteString(fmt.Sprintf("%s (%s / size: %d)\n", info, sizeDesc, info.Size())) } } diff --git a/encoding/ssz/query/tag_parser.go b/encoding/ssz/query/tag_parser.go new file mode 100644 index 0000000000..3ec848d1b5 --- /dev/null +++ b/encoding/ssz/query/tag_parser.go @@ -0,0 +1,130 @@ +package query + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +const ( + // sszMaxTag specifies the maximum capacity of a variable-sized collection, like an SSZ List. + sszMaxTag = "ssz-max" + + // sszSizeTag specifies the length of a fixed-sized collection, like an SSZ Vector. + // A wildcard ('?') indicates that the dimension is variable-sized (a List). + sszSizeTag = "ssz-size" +) + +// SSZDimension holds parsed SSZ tag information for current dimension. +// Mutually exclusive fields indicate whether the dimension is a vector or a list. +type SSZDimension struct { + vectorLength *uint64 + listLimit *uint64 +} + +// ParseSSZTag parses SSZ-specific tags (like `ssz-max` and `ssz-size`) +// and returns the first dimension and the remaining SSZ tags. +// This function validates the tags and returns an error if they are malformed. +func ParseSSZTag(tag *reflect.StructTag) (*SSZDimension, *reflect.StructTag, error) { + if tag == nil { + return nil, nil, errors.New("nil struct tag") + } + + var newTagParts []string + var sizeStr, maxStr string + + // Parse ssz-size tag + if sszSize := tag.Get(sszSizeTag); sszSize != "" { + dims := strings.Split(sszSize, ",") + if len(dims) > 0 { + sizeStr = dims[0] + + if len(dims) > 1 { + remainingSize := strings.Join(dims[1:], ",") + newTagParts = append(newTagParts, fmt.Sprintf(`%s:"%s"`, sszSizeTag, remainingSize)) + } + } + } + + // Parse ssz-max tag + if sszMax := tag.Get(sszMaxTag); sszMax != "" { + dims := strings.Split(sszMax, ",") + if len(dims) > 0 { + maxStr = dims[0] + + if len(dims) > 1 { + remainingMax := strings.Join(dims[1:], ",") + newTagParts = append(newTagParts, fmt.Sprintf(`%s:"%s"`, sszMaxTag, remainingMax)) + } + } + } + + // Create new tag with remaining dimensions only. + // We don't have to preserve other tags like json, protobuf. + var newTag *reflect.StructTag + if len(newTagParts) > 0 { + newTagStr := strings.Join(newTagParts, " ") + t := reflect.StructTag(newTagStr) + newTag = &t + } + + // Parse the first dimension based on ssz-size and ssz-max rules. + // 1. If ssz-size is not specified (wildcard or empty), it must be a list. + if sizeStr == "?" || sizeStr == "" { + if maxStr == "?" { + return nil, nil, errors.New("ssz-size and ssz-max cannot both be '?'") + } + if maxStr == "" { + return nil, nil, errors.New("list requires ssz-max value") + } + + limit, err := strconv.ParseUint(maxStr, 10, 64) + if err != nil { + return nil, nil, fmt.Errorf("invalid ssz-max value: %w", err) + } + if limit == 0 { + return nil, nil, errors.New("ssz-max must be greater than 0") + } + + return &SSZDimension{listLimit: &limit}, newTag, nil + } + + // 2. If ssz-size is specified, it must be a vector. + length, err := strconv.ParseUint(sizeStr, 10, 64) + if err != nil { + return nil, nil, fmt.Errorf("invalid ssz-size value: %w", err) + } + if length == 0 { + return nil, nil, errors.New("ssz-size must be greater than 0") + } + + return &SSZDimension{vectorLength: &length}, newTag, nil +} + +// IsVector returns true if this dimension represents a vector. +func (d *SSZDimension) IsVector() bool { + return d.vectorLength != nil +} + +// IsList returns true if this dimension represents a list. +func (d *SSZDimension) IsList() bool { + return d.listLimit != nil +} + +// GetVectorLength returns the length for a vector in current dimension +func (d *SSZDimension) GetVectorLength() (uint64, error) { + if !d.IsVector() { + return 0, errors.New("not a vector dimension") + } + return *d.vectorLength, nil +} + +// GetListLimit returns the limit for a list in current dimension +func (d *SSZDimension) GetListLimit() (uint64, error) { + if !d.IsList() { + return 0, errors.New("not a list dimension") + } + return *d.listLimit, nil +} diff --git a/encoding/ssz/query/tag_parser_test.go b/encoding/ssz/query/tag_parser_test.go new file mode 100644 index 0000000000..43bfbe8b79 --- /dev/null +++ b/encoding/ssz/query/tag_parser_test.go @@ -0,0 +1,187 @@ +package query_test + +import ( + "reflect" + "testing" + + "github.com/OffchainLabs/prysm/v6/encoding/ssz/query" + "github.com/OffchainLabs/prysm/v6/testing/require" +) + +func TestParseSSZTag(t *testing.T) { + tests := []struct { + wantErr bool + wantIsList bool + wantIsVector bool + wantListLimit uint64 + wantVectorLength uint64 + wantRemainingTag string + tag string + name string + }{ + // Vector tests + { + name: "single dimension vector", + tag: `ssz-size:"32"`, + wantIsVector: true, + wantVectorLength: 32, + }, + { + name: "multi-dimensional vector", + tag: `ssz-size:"5,32"`, + wantIsVector: true, + wantVectorLength: 5, + wantRemainingTag: `ssz-size:"32"`, + }, + { + name: "three-dimensional vector", + tag: `ssz-size:"5,10,32"`, + wantIsVector: true, + wantVectorLength: 5, + wantRemainingTag: `ssz-size:"10,32"`, + }, + { + name: "large vector", + tag: `ssz-size:"1048576"`, + wantIsVector: true, + wantVectorLength: 1048576, + }, + + // List tests + { + name: "single dimension list", + tag: `ssz-max:"100"`, + wantIsList: true, + wantListLimit: 100, + }, + { + name: "multi-dimensional list", + tag: `ssz-max:"100,200"`, + wantIsList: true, + wantListLimit: 100, + wantRemainingTag: `ssz-max:"200"`, + }, + { + name: "large list", + tag: `ssz-max:"1048576"`, + wantIsList: true, + wantListLimit: 1048576, + }, + { + name: "wildcard size becomes list", + tag: `ssz-size:"?" ssz-max:"100"`, + wantIsList: true, + wantListLimit: 100, + }, + { + name: "wildcard with remaining dimensions", + tag: `ssz-size:"?,32" ssz-max:"100"`, + wantIsList: true, + wantListLimit: 100, + wantRemainingTag: `ssz-size:"32"`, + }, + { + name: "empty size becomes list", + tag: `ssz-size:"" ssz-max:"100"`, + wantIsList: true, + wantListLimit: 100, + }, + { + name: "list of vectors", + tag: `ssz-size:"?,32" ssz-max:"100"`, + wantIsList: true, + wantListLimit: 100, + wantRemainingTag: `ssz-size:"32"`, + }, + + // Error cases + { + name: "empty tag", + tag: "", + wantErr: true, + }, + { + name: "zero vector length", + tag: `ssz-size:"0"`, + wantErr: true, + }, + { + name: "zero list limit", + tag: `ssz-max:"0"`, + wantErr: true, + }, + { + name: "invalid vector length", + tag: `ssz-size:"abc"`, + wantErr: true, + }, + { + name: "invalid list limit", + tag: `ssz-max:"xyz"`, + wantErr: true, + }, + { + name: "both wildcard", + tag: `ssz-size:"?" ssz-max:"?"`, + wantErr: true, + }, + { + name: "list without max", + tag: `ssz-size:"?"`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var tag *reflect.StructTag + if tt.tag != "" { + structTag := reflect.StructTag(tt.tag) + tag = &structTag + } + + dim, remainingTag, err := query.ParseSSZTag(tag) + if tt.wantErr { + require.NotNil(t, err) + return + } + + require.NoError(t, err) + require.NotNil(t, dim) + + // Check dimension type + require.Equal(t, tt.wantIsVector, dim.IsVector()) + require.Equal(t, tt.wantIsList, dim.IsList()) + + // Verify vector length if it's a vector + if tt.wantIsVector { + length, err := dim.GetVectorLength() + require.NoError(t, err) + require.Equal(t, tt.wantVectorLength, length) + + // Trying to get list limit should error + _, err = dim.GetListLimit() + require.NotNil(t, err) + } + + // Verify list limit if it's a list + if tt.wantIsList { + limit, err := dim.GetListLimit() + require.NoError(t, err) + require.Equal(t, tt.wantListLimit, limit) + + // Trying to get vector length should error + _, err = dim.GetVectorLength() + require.NotNil(t, err) + } + + // Check remaining tag + if tt.wantRemainingTag == "" { + require.Equal(t, remainingTag == nil, true) + } else { + require.NotNil(t, remainingTag) + require.Equal(t, tt.wantRemainingTag, string(*remainingTag)) + } + }) + } +} diff --git a/encoding/ssz/query/vector.go b/encoding/ssz/query/vector.go new file mode 100644 index 0000000000..c0c0f70d38 --- /dev/null +++ b/encoding/ssz/query/vector.go @@ -0,0 +1,27 @@ +package query + +import "errors" + +// vectorInfo holds information about a SSZ Vector type. +type vectorInfo struct { + // element is the SSZ info of the vector's element type. + element *sszInfo + // length is the fixed length of the vector. + length uint64 +} + +func (v *vectorInfo) Length() uint64 { + if v == nil { + return 0 + } + + return v.length +} + +func (v *vectorInfo) Element() (*sszInfo, error) { + if v == nil { + return nil, errors.New("vectorInfo is nil") + } + + return v.element, nil +} diff --git a/proto/ssz_query/ssz_query.pb.go b/proto/ssz_query/ssz_query.pb.go index 6ce0180777..169444d04b 100755 --- a/proto/ssz_query/ssz_query.pb.go +++ b/proto/ssz_query/ssz_query.pb.go @@ -82,13 +82,14 @@ type FixedTestContainer struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - FieldUint32 uint32 `protobuf:"varint,3,opt,name=field_uint32,json=fieldUint32,proto3" json:"field_uint32,omitempty"` - FieldUint64 uint64 `protobuf:"varint,4,opt,name=field_uint64,json=fieldUint64,proto3" json:"field_uint64,omitempty"` - FieldBool bool `protobuf:"varint,5,opt,name=field_bool,json=fieldBool,proto3" json:"field_bool,omitempty"` - FieldBytes32 []byte `protobuf:"bytes,8,opt,name=field_bytes32,json=fieldBytes32,proto3" json:"field_bytes32,omitempty" ssz-size:"32"` - Nested *FixedNestedContainer `protobuf:"bytes,9,opt,name=nested,proto3" json:"nested,omitempty"` - VectorField []uint64 `protobuf:"varint,10,rep,packed,name=vector_field,json=vectorField,proto3" json:"vector_field,omitempty" ssz-size:"24"` - TrailingField []byte `protobuf:"bytes,11,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"` + FieldUint32 uint32 `protobuf:"varint,3,opt,name=field_uint32,json=fieldUint32,proto3" json:"field_uint32,omitempty"` + FieldUint64 uint64 `protobuf:"varint,4,opt,name=field_uint64,json=fieldUint64,proto3" json:"field_uint64,omitempty"` + FieldBool bool `protobuf:"varint,5,opt,name=field_bool,json=fieldBool,proto3" json:"field_bool,omitempty"` + FieldBytes32 []byte `protobuf:"bytes,8,opt,name=field_bytes32,json=fieldBytes32,proto3" json:"field_bytes32,omitempty" ssz-size:"32"` + Nested *FixedNestedContainer `protobuf:"bytes,9,opt,name=nested,proto3" json:"nested,omitempty"` + VectorField []uint64 `protobuf:"varint,10,rep,packed,name=vector_field,json=vectorField,proto3" json:"vector_field,omitempty" ssz-size:"24"` + TwoDimensionBytesField [][]byte `protobuf:"bytes,11,rep,name=two_dimension_bytes_field,json=twoDimensionBytesField,proto3" json:"two_dimension_bytes_field,omitempty" ssz-size:"5,32"` + TrailingField []byte `protobuf:"bytes,12,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"` } func (x *FixedTestContainer) Reset() { @@ -165,6 +166,13 @@ func (x *FixedTestContainer) GetVectorField() []uint64 { return nil } +func (x *FixedTestContainer) GetTwoDimensionBytesField() [][]byte { + if x != nil { + return x.TwoDimensionBytesField + } + return nil +} + func (x *FixedTestContainer) GetTrailingField() []byte { if x != nil { return x.TrailingField @@ -235,8 +243,9 @@ type VariableTestContainer struct { 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"` + 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"` + TrailingField []byte `protobuf:"bytes,6,opt,name=trailing_field,json=trailingField,proto3" json:"trailing_field,omitempty" ssz-size:"56"` } func (x *VariableTestContainer) Reset() { @@ -292,6 +301,13 @@ func (x *VariableTestContainer) GetFieldListContainer() []*FixedNestedContainer return nil } +func (x *VariableTestContainer) GetFieldListBytes32() [][]byte { + if x != nil { + return x.FieldListBytes32 + } + return nil +} + func (x *VariableTestContainer) GetNested() *VariableNestedContainer { if x != nil { return x.Nested @@ -318,7 +334,7 @@ var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{ 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, 0x1e, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, - 0x32, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x22, 0xb9, 0x02, 0x0a, 0x12, 0x46, 0x69, + 0x32, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x32, 0x22, 0xfe, 0x02, 0x0a, 0x12, 0x46, 0x69, 0x78, 0x65, 0x64, 0x54, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x55, 0x69, 0x6e, @@ -335,40 +351,49 @@ var file_proto_ssz_query_ssz_query_proto_rawDesc = []byte{ 0x6e, 0x65, 0x72, 0x52, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x0c, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x04, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x32, 0x34, 0x52, 0x0b, 0x76, 0x65, 0x63, 0x74, 0x6f, - 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, + 0x72, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x43, 0x0a, 0x19, 0x74, 0x77, 0x6f, 0x5f, 0x64, 0x69, + 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x08, 0x8a, 0xb5, 0x18, 0x04, 0x35, + 0x2c, 0x33, 0x32, 0x52, 0x16, 0x74, 0x77, 0x6f, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2d, 0x0a, 0x0e, 0x74, + 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x0c, 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, 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, 0x80, 0x03, 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, 0x3d, 0x0a, 0x12, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x33, + 0x32, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0c, 0x42, 0x0f, 0x8a, 0xb5, 0x18, 0x04, 0x3f, 0x2c, 0x33, + 0x32, 0x92, 0xb5, 0x18, 0x03, 0x31, 0x30, 0x30, 0x52, 0x10, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x4c, + 0x69, 0x73, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x33, 0x32, 0x12, 0x3a, 0x0a, 0x06, 0x6e, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 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, 0x06, 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, 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, + 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 ( diff --git a/proto/ssz_query/ssz_query.proto b/proto/ssz_query/ssz_query.proto index fee50f3caa..fec55bc482 100644 --- a/proto/ssz_query/ssz_query.proto +++ b/proto/ssz_query/ssz_query.proto @@ -20,7 +20,7 @@ message FixedNestedContainer { // FixedTestContainer - comprehensive fixed-size container for SSZ query testing // Tests: All basic fixed-size SSZ types, nested containers, vectors, offset/length calculations -// Total size: 333 bytes (4+8+1+32+40+192+56) +// Total size: 493 bytes (4+8+1+32+40+192+160+56) message FixedTestContainer { // Basic integer types - test different integer sizes and their SSZ serialization uint32 field_uint32 = 3; // Test: uint32 basic type, offset: 0 @@ -37,9 +37,12 @@ message FixedTestContainer { // Vector type - test fixed-size array of basic elements repeated uint64 vector_field = 10 [ (ethereum.eth.ext.ssz_size) = "24" ]; // Test: Vector[24] of uint64 (24*8=192 bytes), offset: 85 + + // 2D bytes type - test 2-dimensional byte arrays, common in blockchain state roots (e.g., beacon_state.block_roots) + repeated bytes two_dimension_bytes_field = 11 [ (ethereum.eth.ext.ssz_size) = "5,32" ]; // Test: Vector[Bytes32, 5] (32*5=160 bytes), offset: 277 // 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 + bytes trailing_field = 12 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: trailing field after vector, offset: 437 } // ===== VARIABLE-SIZE TEST CONTAINERS ===== @@ -66,10 +69,14 @@ message VariableTestContainer { // 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-size list of bytes32 - test list with fixed-size byte arrays + // e.g., beacon_state.historical_roots + repeated bytes field_list_bytes32 = 4 [ (ethereum.eth.ext.ssz_size) = "?,32", (ethereum.eth.ext.ssz_max) = "100"]; // Test: List[Bytes32, 100] + // Variable nested container - test nested container access within variable container - VariableNestedContainer nested = 4; + VariableNestedContainer nested = 5; // 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 + bytes trailing_field = 6 [ (ethereum.eth.ext.ssz_size) = "56" ]; // Test: fixed 56-byte field at end, offset: 32 + 4 + 4 + 4 + 4 = 48 } diff --git a/proto/ssz_query/ssz_query.ssz.go b/proto/ssz_query/ssz_query.ssz.go index 6e1f7e61f4..2cd9dbd77c 100644 --- a/proto/ssz_query/ssz_query.ssz.go +++ b/proto/ssz_query/ssz_query.ssz.go @@ -118,7 +118,20 @@ func (f *FixedTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) { dst = ssz.MarshalUint64(dst, f.VectorField[ii]) } - // Field (6) 'TrailingField' + // Field (6) 'TwoDimensionBytesField' + if size := len(f.TwoDimensionBytesField); size != 5 { + err = ssz.ErrVectorLengthFn("--.TwoDimensionBytesField", size, 5) + return + } + for ii := 0; ii < 5; ii++ { + if size := len(f.TwoDimensionBytesField[ii]); size != 32 { + err = ssz.ErrBytesLengthFn("--.TwoDimensionBytesField[ii]", size, 32) + return + } + dst = append(dst, f.TwoDimensionBytesField[ii]...) + } + + // Field (7) 'TrailingField' if size := len(f.TrailingField); size != 56 { err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) return @@ -132,7 +145,7 @@ func (f *FixedTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) { func (f *FixedTestContainer) UnmarshalSSZ(buf []byte) error { var err error size := uint64(len(buf)) - if size != 333 { + if size != 493 { return ssz.ErrSize } @@ -168,18 +181,27 @@ func (f *FixedTestContainer) UnmarshalSSZ(buf []byte) error { f.VectorField[ii] = ssz.UnmarshallUint64(buf[85:277][ii*8 : (ii+1)*8]) } - // Field (6) 'TrailingField' - if cap(f.TrailingField) == 0 { - f.TrailingField = make([]byte, 0, len(buf[277:333])) + // Field (6) 'TwoDimensionBytesField' + f.TwoDimensionBytesField = make([][]byte, 5) + for ii := 0; ii < 5; ii++ { + if cap(f.TwoDimensionBytesField[ii]) == 0 { + f.TwoDimensionBytesField[ii] = make([]byte, 0, len(buf[277:437][ii*32:(ii+1)*32])) + } + f.TwoDimensionBytesField[ii] = append(f.TwoDimensionBytesField[ii], buf[277:437][ii*32:(ii+1)*32]...) } - f.TrailingField = append(f.TrailingField, buf[277:333]...) + + // Field (7) 'TrailingField' + if cap(f.TrailingField) == 0 { + f.TrailingField = make([]byte, 0, len(buf[437:493])) + } + f.TrailingField = append(f.TrailingField, buf[437:493]...) return err } // SizeSSZ returns the ssz encoded size in bytes for the FixedTestContainer object func (f *FixedTestContainer) SizeSSZ() (size int) { - size = 333 + size = 493 return } @@ -226,7 +248,24 @@ func (f *FixedTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) { hh.Merkleize(subIndx) } - // Field (6) 'TrailingField' + // Field (6) 'TwoDimensionBytesField' + { + if size := len(f.TwoDimensionBytesField); size != 5 { + err = ssz.ErrVectorLengthFn("--.TwoDimensionBytesField", size, 5) + return + } + subIndx := hh.Index() + for _, i := range f.TwoDimensionBytesField { + if len(i) != 32 { + err = ssz.ErrBytesLength + return + } + hh.Append(i) + } + hh.Merkleize(subIndx) + } + + // Field (7) 'TrailingField' if size := len(f.TrailingField); size != 56 { err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) return @@ -354,7 +393,7 @@ func (v *VariableTestContainer) MarshalSSZ() ([]byte, error) { // 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) + offset := int(104) // Field (0) 'LeadingField' if size := len(v.LeadingField); size != 32 { @@ -371,14 +410,18 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) dst = ssz.WriteOffset(dst, offset) offset += len(v.FieldListContainer) * 40 - // Offset (3) 'Nested' + // Offset (3) 'FieldListBytes32' + dst = ssz.WriteOffset(dst, offset) + offset += len(v.FieldListBytes32) * 32 + + // Offset (4) 'Nested' dst = ssz.WriteOffset(dst, offset) if v.Nested == nil { v.Nested = new(VariableNestedContainer) } offset += v.Nested.SizeSSZ() - // Field (4) 'TrailingField' + // Field (5) 'TrailingField' if size := len(v.TrailingField); size != 56 { err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) return @@ -405,7 +448,20 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) } } - // Field (3) 'Nested' + // Field (3) 'FieldListBytes32' + if size := len(v.FieldListBytes32); size > 100 { + err = ssz.ErrListTooBigFn("--.FieldListBytes32", size, 100) + return + } + for ii := 0; ii < len(v.FieldListBytes32); ii++ { + if size := len(v.FieldListBytes32[ii]); size != 32 { + err = ssz.ErrBytesLengthFn("--.FieldListBytes32[ii]", size, 32) + return + } + dst = append(dst, v.FieldListBytes32[ii]...) + } + + // Field (4) 'Nested' if dst, err = v.Nested.MarshalSSZTo(dst); err != nil { return } @@ -417,12 +473,12 @@ func (v *VariableTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { var err error size := uint64(len(buf)) - if size < 100 { + if size < 104 { return ssz.ErrSize } tail := buf - var o1, o2, o3 uint64 + var o1, o2, o3, o4 uint64 // Field (0) 'LeadingField' if cap(v.LeadingField) == 0 { @@ -435,7 +491,7 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { return ssz.ErrOffset } - if o1 != 100 { + if o1 != 104 { return ssz.ErrInvalidVariableOffset } @@ -444,16 +500,21 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { return ssz.ErrOffset } - // Offset (3) 'Nested' + // Offset (3) 'FieldListBytes32' 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])) + // Offset (4) 'Nested' + if o4 = ssz.ReadOffset(buf[44:48]); o4 > size || o3 > o4 { + return ssz.ErrOffset } - v.TrailingField = append(v.TrailingField, buf[44:100]...) + + // Field (5) 'TrailingField' + if cap(v.TrailingField) == 0 { + v.TrailingField = make([]byte, 0, len(buf[48:104])) + } + v.TrailingField = append(v.TrailingField, buf[48:104]...) // Field (1) 'FieldListUint64' { @@ -486,9 +547,25 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { } } - // Field (3) 'Nested' + // Field (3) 'FieldListBytes32' { - buf = tail[o3:] + buf = tail[o3:o4] + num, err := ssz.DivideInt2(len(buf), 32, 100) + if err != nil { + return err + } + v.FieldListBytes32 = make([][]byte, num) + for ii := 0; ii < num; ii++ { + if cap(v.FieldListBytes32[ii]) == 0 { + v.FieldListBytes32[ii] = make([]byte, 0, len(buf[ii*32:(ii+1)*32])) + } + v.FieldListBytes32[ii] = append(v.FieldListBytes32[ii], buf[ii*32:(ii+1)*32]...) + } + } + + // Field (4) 'Nested' + { + buf = tail[o4:] if v.Nested == nil { v.Nested = new(VariableNestedContainer) } @@ -501,7 +578,7 @@ func (v *VariableTestContainer) UnmarshalSSZ(buf []byte) error { // SizeSSZ returns the ssz encoded size in bytes for the VariableTestContainer object func (v *VariableTestContainer) SizeSSZ() (size int) { - size = 100 + size = 104 // Field (1) 'FieldListUint64' size += len(v.FieldListUint64) * 8 @@ -509,7 +586,10 @@ func (v *VariableTestContainer) SizeSSZ() (size int) { // Field (2) 'FieldListContainer' size += len(v.FieldListContainer) * 40 - // Field (3) 'Nested' + // Field (3) 'FieldListBytes32' + size += len(v.FieldListBytes32) * 32 + + // Field (4) 'Nested' if v.Nested == nil { v.Nested = new(VariableNestedContainer) } @@ -566,12 +646,31 @@ func (v *VariableTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) { hh.MerkleizeWithMixin(subIndx, num, 128) } - // Field (3) 'Nested' + // Field (3) 'FieldListBytes32' + { + if size := len(v.FieldListBytes32); size > 100 { + err = ssz.ErrListTooBigFn("--.FieldListBytes32", size, 100) + return + } + subIndx := hh.Index() + for _, i := range v.FieldListBytes32 { + if len(i) != 32 { + err = ssz.ErrBytesLength + return + } + hh.Append(i) + } + + numItems := uint64(len(v.FieldListBytes32)) + hh.MerkleizeWithMixin(subIndx, numItems, 100) + } + + // Field (4) 'Nested' if err = v.Nested.HashTreeRootWith(hh); err != nil { return } - // Field (4) 'TrailingField' + // Field (5) 'TrailingField' if size := len(v.TrailingField); size != 56 { err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56) return