mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-07 22:54:17 -05:00
Initialize SSZ-QL package with support for fixed-size types (#15588)
* Add basic PathElement * Add ssz_type.go * Add basic sszInfo * Add containerInfo * Add basic analyzer without analyzing list/vector * Add analyzer for homogeneous collection types * Add offset/length calculator * Add testutil package in encoding/ssz/query * Add first round trip test for IndexedAttestationElectra * Go mod tidy * Add Print function for debugging purpose * Add changelog * Add testonly flag for testutil package & Nit for nogo * Apply reviews from Radek * Replace fastssz with prysmaticlabs one * Add proto/ssz_query package for testing purpose * Update encoding/ssz/query tests to decouple with beacon types * Use require.* instead of assert.* * Fix import name for proto ssz_query package * Remove uint8/uint16 and some byte arrays in FixedTestContainer * Add newline for files * Fix comment about byte array in ssz_query.proto --------- Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
3
changelog/syjn99_initialize-ssz-ql.md
Normal file
3
changelog/syjn99_initialize-ssz-ql.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### Added
|
||||
|
||||
- Initialize package for SSZ Query Language.
|
||||
30
encoding/ssz/query/BUILD.bazel
Normal file
30
encoding/ssz/query/BUILD.bazel
Normal file
@@ -0,0 +1,30 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"analyzer.go",
|
||||
"container.go",
|
||||
"path.go",
|
||||
"query.go",
|
||||
"ssz_info.go",
|
||||
"ssz_type.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v6/encoding/ssz/query",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"analyzer_test.go",
|
||||
"path_test.go",
|
||||
"query_test.go",
|
||||
],
|
||||
deps = [
|
||||
":go_default_library",
|
||||
"//encoding/ssz/query/testutil:go_default_library",
|
||||
"//proto/ssz_query:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
],
|
||||
)
|
||||
242
encoding/ssz/query/analyzer.go
Normal file
242
encoding/ssz/query/analyzer.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// AnalyzeObject analyzes given object and returns its SSZ information.
|
||||
func AnalyzeObject(obj any) (*sszInfo, error) {
|
||||
value := dereferencePointer(obj)
|
||||
|
||||
info, err := analyzeType(value.Type(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not analyze type %s: %w", value.Type().Name(), err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// 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() {
|
||||
// Basic types (e.g., uintN where N is 8, 16, 32, 64)
|
||||
// NOTE: uint128 and uint256 are represented as []byte in Go,
|
||||
// so we handle them as slices. See `analyzeHomogeneousColType`.
|
||||
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Bool:
|
||||
return analyzeBasicType(typ)
|
||||
|
||||
case reflect.Slice:
|
||||
return analyzeHomogeneousColType(typ, tag)
|
||||
|
||||
case reflect.Struct:
|
||||
return analyzeContainerType(typ)
|
||||
|
||||
case reflect.Ptr:
|
||||
// Dereference pointer types.
|
||||
return analyzeType(typ.Elem(), tag)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type %v for SSZ calculation", typ.Kind())
|
||||
}
|
||||
}
|
||||
|
||||
// analyzeBasicType analyzes SSZ basic types (uintN, bool) and returns its info.
|
||||
func analyzeBasicType(typ reflect.Type) (*sszInfo, error) {
|
||||
sszInfo := &sszInfo{
|
||||
typ: typ,
|
||||
|
||||
// Every basic type is fixed-size and not variable.
|
||||
isVariable: false,
|
||||
}
|
||||
|
||||
switch typ.Kind() {
|
||||
case reflect.Uint64:
|
||||
sszInfo.sszType = UintN
|
||||
sszInfo.fixedSize = 8
|
||||
case reflect.Uint32:
|
||||
sszInfo.sszType = UintN
|
||||
sszInfo.fixedSize = 4
|
||||
case reflect.Uint16:
|
||||
sszInfo.sszType = UintN
|
||||
sszInfo.fixedSize = 2
|
||||
case reflect.Uint8:
|
||||
sszInfo.sszType = UintN
|
||||
sszInfo.fixedSize = 1
|
||||
case reflect.Bool:
|
||||
sszInfo.sszType = Boolean
|
||||
sszInfo.fixedSize = 1
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported basic type %v for SSZ calculation", typ.Kind())
|
||||
}
|
||||
|
||||
return sszInfo, nil
|
||||
}
|
||||
|
||||
// analyzeHomogeneousColType analyzes homogeneous collection types (e.g., List, Vector, Bitlist, Bitvector) and returns its SSZ info.
|
||||
func analyzeHomogeneousColType(typ reflect.Type, tag *reflect.StructTag) (*sszInfo, error) {
|
||||
if typ.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("can only analyze slice types, got %v", typ.Kind())
|
||||
}
|
||||
|
||||
if tag == nil {
|
||||
return nil, fmt.Errorf("tag is required for slice types")
|
||||
}
|
||||
|
||||
elementInfo, err := analyzeType(typ.Elem(), nil)
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ssz-max tag (%s): %w", sszMax, 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))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 &sszInfo{
|
||||
sszType: List,
|
||||
typ: typ,
|
||||
|
||||
fixedSize: offsetBytes,
|
||||
isVariable: true,
|
||||
}, 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 &sszInfo{
|
||||
sszType: Vector,
|
||||
typ: typ,
|
||||
|
||||
fixedSize: length * elementInfo.Size(),
|
||||
isVariable: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// analyzeContainerType analyzes SSZ Container type and returns its SSZ info.
|
||||
func analyzeContainerType(typ reflect.Type) (*sszInfo, error) {
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("can only analyze struct types, got %v", typ.Kind())
|
||||
}
|
||||
|
||||
sszInfo := &sszInfo{
|
||||
sszType: Container,
|
||||
typ: typ,
|
||||
|
||||
containerInfo: make(map[string]*fieldInfo),
|
||||
}
|
||||
var currentOffset uint64
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
// Protobuf-generated structs contain private fields we must skip.
|
||||
// e.g., state, sizeCache, unknownFields, etc.
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
|
||||
// The JSON tag contains the field name in the first part.
|
||||
// e.g., "attesting_indices,omitempty" -> "attesting_indices".
|
||||
jsonTag := field.Tag.Get("json")
|
||||
if jsonTag == "" {
|
||||
return nil, fmt.Errorf("field %s has no JSON tag", field.Name)
|
||||
}
|
||||
|
||||
// NOTE: `fieldName` is a string with `snake_case` format (following consensus specs).
|
||||
fieldName := strings.Split(jsonTag, ",")[0]
|
||||
if fieldName == "" {
|
||||
return nil, fmt.Errorf("field %s has an empty JSON tag", field.Name)
|
||||
}
|
||||
|
||||
// Analyze each field so that we can complete full SSZ information.
|
||||
info, err := analyzeType(field.Type, &field.Tag)
|
||||
if err != nil {
|
||||
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,
|
||||
}
|
||||
|
||||
// Update the current offset based on the field's fixed size.
|
||||
currentOffset += info.fixedSize
|
||||
}
|
||||
|
||||
sszInfo.fixedSize = currentOffset
|
||||
|
||||
return sszInfo, nil
|
||||
}
|
||||
|
||||
// dereferencePointer dereferences a pointer to get the underlying value using reflection.
|
||||
func dereferencePointer(obj any) reflect.Value {
|
||||
value := reflect.ValueOf(obj)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
if value.IsNil() {
|
||||
// If we encounter a nil pointer before the end of the path, we can still proceed
|
||||
// by analyzing the type, not the value.
|
||||
value = reflect.New(value.Type().Elem()).Elem()
|
||||
} else {
|
||||
value = value.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
17
encoding/ssz/query/analyzer_test.go
Normal file
17
encoding/ssz/query/analyzer_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
|
||||
sszquerypb "github.com/OffchainLabs/prysm/v6/proto/ssz_query"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
)
|
||||
|
||||
func TestAnalyzeSSZInfo(t *testing.T) {
|
||||
info, err := query.AnalyzeObject(&sszquerypb.FixedTestContainer{})
|
||||
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")
|
||||
}
|
||||
11
encoding/ssz/query/container.go
Normal file
11
encoding/ssz/query/container.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package query
|
||||
|
||||
// containerInfo maps a field's JSON name to its sszInfo for nested Containers.
|
||||
type containerInfo = map[string]*fieldInfo
|
||||
|
||||
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
|
||||
}
|
||||
31
encoding/ssz/query/path.go
Normal file
31
encoding/ssz/query/path.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PathElement represents a single element in a path.
|
||||
type PathElement struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func ParsePath(rawPath string) ([]PathElement, error) {
|
||||
// We use dot notation, so we split the path by '.'.
|
||||
rawElements := strings.Split(rawPath, ".")
|
||||
if len(rawElements) == 0 {
|
||||
return nil, errors.New("empty path provided")
|
||||
}
|
||||
|
||||
if rawElements[0] == "" {
|
||||
// Remove leading dot if present
|
||||
rawElements = rawElements[1:]
|
||||
}
|
||||
|
||||
var path []PathElement
|
||||
for _, elem := range rawElements {
|
||||
path = append(path, PathElement{Name: elem})
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
53
encoding/ssz/query/path_test.go
Normal file
53
encoding/ssz/query/path_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
)
|
||||
|
||||
func TestParsePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected []query.PathElement
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple nested path",
|
||||
path: "data.target.root",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "simple nested path with leading dot",
|
||||
path: ".data.target.root",
|
||||
expected: []query.PathElement{
|
||||
{Name: "data"},
|
||||
{Name: "target"},
|
||||
{Name: "root"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parsedPath, err := query.ParsePath(tt.path)
|
||||
|
||||
if tt.wantErr {
|
||||
require.NotNil(t, err, "Expected error but got none")
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(tt.expected), len(parsedPath), "Expected %d path elements, got %d", len(tt.expected), len(parsedPath))
|
||||
require.DeepEqual(t, tt.expected, parsedPath, "Parsed path does not match expected path")
|
||||
})
|
||||
}
|
||||
}
|
||||
37
encoding/ssz/query/query.go
Normal file
37
encoding/ssz/query/query.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package query
|
||||
|
||||
import "fmt"
|
||||
|
||||
func CalculateOffsetAndLength(sszInfo *sszInfo, path []PathElement) (*sszInfo, uint64, uint64, error) {
|
||||
if sszInfo == nil {
|
||||
return nil, 0, 0, fmt.Errorf("sszInfo is nil")
|
||||
}
|
||||
|
||||
if len(path) == 0 {
|
||||
return nil, 0, 0, fmt.Errorf("path is empty")
|
||||
}
|
||||
|
||||
walk := sszInfo
|
||||
currentOffset := uint64(0)
|
||||
|
||||
for _, elem := range path {
|
||||
fieldInfos, err := walk.ContainerInfo()
|
||||
if err != nil {
|
||||
return nil, 0, 0, fmt.Errorf("could not get field infos: %w", err)
|
||||
}
|
||||
|
||||
fieldInfo, exists := fieldInfos[elem.Name]
|
||||
if !exists {
|
||||
return nil, 0, 0, fmt.Errorf("field %s not found in fieldInfos", elem.Name)
|
||||
}
|
||||
|
||||
currentOffset += 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
|
||||
}
|
||||
200
encoding/ssz/query/query_test.go
Normal file
200
encoding/ssz/query/query_test.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package query_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"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 {
|
||||
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)
|
||||
|
||||
info, err := query.AnalyzeObject(&sszquerypb.FixedTestContainer{})
|
||||
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(),
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
testutil.RunStructTest(t, spec)
|
||||
}
|
||||
}
|
||||
|
||||
func createFixedTestContainer() any {
|
||||
fieldBytes32 := make([]byte, 32)
|
||||
for i := range fieldBytes32 {
|
||||
fieldBytes32[i] = byte(i + 24)
|
||||
}
|
||||
|
||||
nestedValue2 := make([]byte, 32)
|
||||
for i := range nestedValue2 {
|
||||
nestedValue2[i] = byte(i + 56)
|
||||
}
|
||||
|
||||
trailingField := make([]byte, 56)
|
||||
for i := range trailingField {
|
||||
trailingField[i] = byte(i + 88)
|
||||
}
|
||||
|
||||
return &ssz_query.FixedTestContainer{
|
||||
// Basic types
|
||||
FieldUint32: math.MaxUint32,
|
||||
FieldUint64: math.MaxUint64,
|
||||
FieldBool: true,
|
||||
|
||||
// Fixed-size bytes
|
||||
FieldBytes32: fieldBytes32,
|
||||
|
||||
// Nested container
|
||||
Nested: &sszquerypb.FixedNestedContainer{
|
||||
Value1: 123,
|
||||
Value2: nestedValue2,
|
||||
},
|
||||
|
||||
// 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},
|
||||
|
||||
// Trailing field
|
||||
TrailingField: trailingField,
|
||||
}
|
||||
}
|
||||
|
||||
func getFixedTestContainerSpec() testutil.TestSpec {
|
||||
testContainer := createFixedTestContainer().(*sszquerypb.FixedTestContainer)
|
||||
|
||||
return testutil.TestSpec{
|
||||
Name: "FixedTestContainer",
|
||||
Type: sszquerypb.FixedTestContainer{},
|
||||
Instance: testContainer,
|
||||
PathTests: []testutil.PathTest{
|
||||
// Basic types
|
||||
{
|
||||
Path: ".field_uint32",
|
||||
Expected: testContainer.FieldUint32,
|
||||
},
|
||||
{
|
||||
Path: ".field_uint64",
|
||||
Expected: testContainer.FieldUint64,
|
||||
},
|
||||
{
|
||||
Path: ".field_bool",
|
||||
Expected: testContainer.FieldBool,
|
||||
},
|
||||
// Fixed-size bytes
|
||||
{
|
||||
Path: ".field_bytes32",
|
||||
Expected: testContainer.FieldBytes32,
|
||||
},
|
||||
// Nested container
|
||||
{
|
||||
Path: ".nested",
|
||||
Expected: testContainer.Nested,
|
||||
},
|
||||
{
|
||||
Path: ".nested.value1",
|
||||
Expected: testContainer.Nested.Value1,
|
||||
},
|
||||
{
|
||||
Path: ".nested.value2",
|
||||
Expected: testContainer.Nested.Value2,
|
||||
},
|
||||
// Vector field
|
||||
{
|
||||
Path: ".vector_field",
|
||||
Expected: testContainer.VectorField,
|
||||
},
|
||||
// Trailing field
|
||||
{
|
||||
Path: ".trailing_field",
|
||||
Expected: testContainer.TrailingField,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
110
encoding/ssz/query/ssz_info.go
Normal file
110
encoding/ssz/query/ssz_info.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// sszInfo holds the all necessary data for analyzing SSZ data types.
|
||||
type sszInfo struct {
|
||||
// Type of the SSZ structure (Basic, Container, List, etc.).
|
||||
sszType SSZType
|
||||
// Type in Go. Need this for unmarshaling.
|
||||
typ reflect.Type
|
||||
|
||||
// isVariable is true if the struct contains any variable-size fields.
|
||||
isVariable bool
|
||||
// fixedSize is the total size of the struct's fixed part.
|
||||
fixedSize uint64
|
||||
|
||||
// For Container types.
|
||||
containerInfo containerInfo
|
||||
}
|
||||
|
||||
func (info *sszInfo) FixedSize() uint64 {
|
||||
if info == nil {
|
||||
return 0
|
||||
}
|
||||
return info.fixedSize
|
||||
}
|
||||
|
||||
func (info *sszInfo) Size() uint64 {
|
||||
if info == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Easy case: if the type is not variable, we can return the fixed size.
|
||||
if !info.isVariable {
|
||||
return info.fixedSize
|
||||
}
|
||||
|
||||
// NOTE: Handle variable-sized types.
|
||||
return 0
|
||||
}
|
||||
|
||||
func (info *sszInfo) ContainerInfo() (containerInfo, error) {
|
||||
if info == nil {
|
||||
return nil, fmt.Errorf("sszInfo is nil")
|
||||
}
|
||||
|
||||
if info.sszType != Container {
|
||||
return nil, fmt.Errorf("sszInfo is not a Container type, got %s", info.sszType)
|
||||
}
|
||||
|
||||
if info.containerInfo == nil {
|
||||
return nil, fmt.Errorf("sszInfo.containerInfo is nil")
|
||||
}
|
||||
|
||||
return info.containerInfo, nil
|
||||
}
|
||||
|
||||
// Print returns a string representation of the sszInfo, which is useful for debugging.
|
||||
func (info *sszInfo) Print() string {
|
||||
if info == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
var builder strings.Builder
|
||||
printRecursive(info, &builder, "")
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func printRecursive(info *sszInfo, builder *strings.Builder, prefix string) {
|
||||
var sizeDesc string
|
||||
if info.isVariable {
|
||||
sizeDesc = "Variable-size"
|
||||
} else {
|
||||
sizeDesc = "Fixed-size"
|
||||
}
|
||||
|
||||
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()))
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
53
encoding/ssz/query/ssz_type.go
Normal file
53
encoding/ssz/query/ssz_type.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package query
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SSZType represents the type supported by SSZ.
|
||||
// https://github.com/ethereum/consensus-specs/blob/master/ssz/simple-serialize.md#typing
|
||||
type SSZType int
|
||||
|
||||
// SSZ type constants.
|
||||
const (
|
||||
// Basic types
|
||||
UintN SSZType = iota
|
||||
Byte
|
||||
Boolean
|
||||
|
||||
// Composite types
|
||||
Container
|
||||
Vector
|
||||
List
|
||||
Bitvector
|
||||
Bitlist
|
||||
|
||||
// Added in EIP-7916
|
||||
ProgressiveList
|
||||
Union
|
||||
)
|
||||
|
||||
func (t SSZType) String() string {
|
||||
switch t {
|
||||
case UintN:
|
||||
return "UintN"
|
||||
case Byte:
|
||||
return "Byte"
|
||||
case Boolean:
|
||||
return "Boolean"
|
||||
case Container:
|
||||
return "Container"
|
||||
case Vector:
|
||||
return "Vector"
|
||||
case List:
|
||||
return "List"
|
||||
case Bitvector:
|
||||
return "Bitvector"
|
||||
case Bitlist:
|
||||
return "Bitlist"
|
||||
case ProgressiveList:
|
||||
return "ProgressiveList"
|
||||
case Union:
|
||||
return "Union"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown(%d)", t)
|
||||
}
|
||||
}
|
||||
18
encoding/ssz/query/testutil/BUILD.bazel
Normal file
18
encoding/ssz/query/testutil/BUILD.bazel
Normal file
@@ -0,0 +1,18 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"runner.go",
|
||||
"type.go",
|
||||
"util.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v6/encoding/ssz/query/testutil",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//encoding/ssz/query:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"@com_github_prysmaticlabs_fastssz//:go_default_library",
|
||||
],
|
||||
)
|
||||
38
encoding/ssz/query/testutil/runner.go
Normal file
38
encoding/ssz/query/testutil/runner.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
|
||||
"github.com/OffchainLabs/prysm/v6/testing/require"
|
||||
ssz "github.com/prysmaticlabs/fastssz"
|
||||
)
|
||||
|
||||
func RunStructTest(t *testing.T, spec TestSpec) {
|
||||
t.Run(spec.Name, func(t *testing.T) {
|
||||
info, err := query.AnalyzeObject(spec.Type)
|
||||
require.NoError(t, err)
|
||||
|
||||
testInstance := spec.Instance
|
||||
marshaller, ok := testInstance.(ssz.Marshaler)
|
||||
require.Equal(t, true, ok, "Test instance must implement ssz.Marshaler, got %T", testInstance)
|
||||
|
||||
marshalledData, err := marshaller.MarshalSSZ()
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, pathTest := range spec.PathTests {
|
||||
t.Run(pathTest.Path, func(t *testing.T) {
|
||||
path, err := query.ParsePath(pathTest.Path)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, offset, length, err := query.CalculateOffsetAndLength(info, path)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedRawBytes := marshalledData[offset : offset+length]
|
||||
rawBytes, err := marshalAny(pathTest.Expected)
|
||||
require.NoError(t, err, "Marshalling expected value should not return an error")
|
||||
require.DeepEqual(t, expectedRawBytes, rawBytes, "Extracted value should match expected")
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
13
encoding/ssz/query/testutil/type.go
Normal file
13
encoding/ssz/query/testutil/type.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package testutil
|
||||
|
||||
type PathTest struct {
|
||||
Path string
|
||||
Expected any
|
||||
}
|
||||
|
||||
type TestSpec struct {
|
||||
Name string
|
||||
Type any
|
||||
Instance any
|
||||
PathTests []PathTest
|
||||
}
|
||||
49
encoding/ssz/query/testutil/util.go
Normal file
49
encoding/ssz/query/testutil/util.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
ssz "github.com/prysmaticlabs/fastssz"
|
||||
)
|
||||
|
||||
// marshalAny marshals any value into SSZ format.
|
||||
func marshalAny(value any) ([]byte, error) {
|
||||
// First check if it implements ssz.Marshaler (this catches custom types like primitives.Epoch)
|
||||
if marshaler, ok := value.(ssz.Marshaler); ok {
|
||||
return marshaler.MarshalSSZ()
|
||||
}
|
||||
|
||||
// Handle custom type aliases by checking if they're based on primitive types
|
||||
valueType := reflect.TypeOf(value)
|
||||
if valueType.PkgPath() != "" {
|
||||
switch valueType.Kind() {
|
||||
case reflect.Uint64:
|
||||
return ssz.MarshalUint64(make([]byte, 0), reflect.ValueOf(value).Uint()), nil
|
||||
case reflect.Uint32:
|
||||
return ssz.MarshalUint32(make([]byte, 0), uint32(reflect.ValueOf(value).Uint())), nil
|
||||
case reflect.Bool:
|
||||
return ssz.MarshalBool(make([]byte, 0), reflect.ValueOf(value).Bool()), nil
|
||||
}
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case []uint64:
|
||||
buf := make([]byte, 0, len(v)*8)
|
||||
for _, val := range v {
|
||||
buf = ssz.MarshalUint64(buf, val)
|
||||
}
|
||||
return buf, nil
|
||||
case uint64:
|
||||
return ssz.MarshalUint64(make([]byte, 0), v), nil
|
||||
case uint32:
|
||||
return ssz.MarshalUint32(make([]byte, 0), v), nil
|
||||
case bool:
|
||||
return ssz.MarshalBool(make([]byte, 0), v), nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type for SSZ marshalling: %T", value)
|
||||
}
|
||||
}
|
||||
68
proto/ssz_query/BUILD.bazel
Normal file
68
proto/ssz_query/BUILD.bazel
Normal file
@@ -0,0 +1,68 @@
|
||||
load("@rules_proto//proto:defs.bzl", "proto_library")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
|
||||
load("//proto:ssz_proto_library.bzl", "ssz_proto_files")
|
||||
load("//tools:ssz.bzl", "SSZ_DEPS", "ssz_gen_marshal")
|
||||
|
||||
# gazelle:ignore
|
||||
|
||||
proto_library(
|
||||
name = "proto",
|
||||
srcs = ["ssz_query.proto"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//proto/eth/ext:proto",
|
||||
],
|
||||
)
|
||||
|
||||
go_proto_library(
|
||||
name = "go_proto",
|
||||
compilers = [
|
||||
"@com_github_prysmaticlabs_protoc_gen_go_cast//:go_cast_grpc",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v6/proto/ssz_query",
|
||||
proto = ":proto",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//proto/eth/ext:go_default_library",
|
||||
"@com_github_golang_protobuf//proto:go_default_library",
|
||||
"@org_golang_google_protobuf//reflect/protoreflect:go_default_library",
|
||||
"@org_golang_google_protobuf//runtime/protoimpl:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
# SSZ generation for test proto messages
|
||||
ssz_gen_marshal(
|
||||
name = "ssz_generated",
|
||||
out = "ssz_query.ssz.go",
|
||||
go_proto = ":go_proto",
|
||||
objs = [
|
||||
"FixedTestContainer",
|
||||
"FixedNestedContainer",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
":ssz_generated", # keep
|
||||
],
|
||||
embed = [":go_proto"],
|
||||
importpath = "github.com/OffchainLabs/prysm/v6/proto/ssz_query",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = SSZ_DEPS + [
|
||||
"//proto/eth/ext:go_default_library",
|
||||
"@com_github_golang_protobuf//proto:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
ssz_proto_files(
|
||||
name = "ssz_proto_files",
|
||||
srcs = ["ssz_query.proto"],
|
||||
config = select({
|
||||
"//conditions:default": "mainnet",
|
||||
"//proto:ssz_mainnet": "mainnet",
|
||||
"//proto:ssz_minimal": "minimal",
|
||||
}),
|
||||
)
|
||||
289
proto/ssz_query/ssz_query.pb.go
generated
Executable file
289
proto/ssz_query/ssz_query.pb.go
generated
Executable file
@@ -0,0 +1,289 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.33.0
|
||||
// protoc v3.21.7
|
||||
// source: proto/ssz_query/ssz_query.proto
|
||||
|
||||
package ssz_query
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
_ "github.com/OffchainLabs/prysm/v6/proto/eth/ext"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type FixedNestedContainer struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Value1 uint64 `protobuf:"varint,1,opt,name=value1,proto3" json:"value1,omitempty"`
|
||||
Value2 []byte `protobuf:"bytes,2,opt,name=value2,proto3" json:"value2,omitempty" ssz-size:"32"`
|
||||
}
|
||||
|
||||
func (x *FixedNestedContainer) Reset() {
|
||||
*x = FixedNestedContainer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FixedNestedContainer) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FixedNestedContainer) ProtoMessage() {}
|
||||
|
||||
func (x *FixedNestedContainer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[0]
|
||||
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 FixedNestedContainer.ProtoReflect.Descriptor instead.
|
||||
func (*FixedNestedContainer) Descriptor() ([]byte, []int) {
|
||||
return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *FixedNestedContainer) GetValue1() uint64 {
|
||||
if x != nil {
|
||||
return x.Value1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FixedNestedContainer) GetValue2() []byte {
|
||||
if x != nil {
|
||||
return x.Value2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FixedTestContainer struct {
|
||||
state protoimpl.MessageState
|
||||
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"`
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) Reset() {
|
||||
*x = FixedTestContainer{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FixedTestContainer) ProtoMessage() {}
|
||||
|
||||
func (x *FixedTestContainer) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_proto_ssz_query_ssz_query_proto_msgTypes[1]
|
||||
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 FixedTestContainer.ProtoReflect.Descriptor instead.
|
||||
func (*FixedTestContainer) Descriptor() ([]byte, []int) {
|
||||
return file_proto_ssz_query_ssz_query_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) GetFieldUint32() uint32 {
|
||||
if x != nil {
|
||||
return x.FieldUint32
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) GetFieldUint64() uint64 {
|
||||
if x != nil {
|
||||
return x.FieldUint64
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) GetFieldBool() bool {
|
||||
if x != nil {
|
||||
return x.FieldBool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) GetFieldBytes32() []byte {
|
||||
if x != nil {
|
||||
return x.FieldBytes32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) GetNested() *FixedNestedContainer {
|
||||
if x != nil {
|
||||
return x.Nested
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) GetVectorField() []uint64 {
|
||||
if x != nil {
|
||||
return x.VectorField
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *FixedTestContainer) 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{
|
||||
0x0a, 0x1f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72,
|
||||
0x79, 0x2f, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x12, 0x09, 0x73, 0x73, 0x7a, 0x5f, 0x71, 0x75, 0x65, 0x72, 0x79, 0x1a, 0x1b, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x74, 0x68, 0x2f, 0x65, 0x78, 0x74, 0x2f, 0x6f, 0x70, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4e, 0x0a, 0x14, 0x46, 0x69, 0x78,
|
||||
0x65, 0x64, 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, 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,
|
||||
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,
|
||||
0x74, 0x33, 0x32, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x75, 0x69, 0x6e,
|
||||
0x74, 0x36, 0x34, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64,
|
||||
0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,
|
||||
0x62, 0x6f, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x69, 0x65, 0x6c,
|
||||
0x64, 0x42, 0x6f, 0x6f, 0x6c, 0x12, 0x2b, 0x0a, 0x0d, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x62,
|
||||
0x79, 0x74, 0x65, 0x73, 0x33, 0x32, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5,
|
||||
0x18, 0x02, 0x33, 0x32, 0x52, 0x0c, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73,
|
||||
0x33, 0x32, 0x12, 0x37, 0x0a, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01,
|
||||
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, 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,
|
||||
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 (
|
||||
file_proto_ssz_query_ssz_query_proto_rawDescOnce sync.Once
|
||||
file_proto_ssz_query_ssz_query_proto_rawDescData = file_proto_ssz_query_ssz_query_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_proto_ssz_query_ssz_query_proto_rawDescGZIP() []byte {
|
||||
file_proto_ssz_query_ssz_query_proto_rawDescOnce.Do(func() {
|
||||
file_proto_ssz_query_ssz_query_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_ssz_query_ssz_query_proto_rawDescData)
|
||||
})
|
||||
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_goTypes = []interface{}{
|
||||
(*FixedNestedContainer)(nil), // 0: ssz_query.FixedNestedContainer
|
||||
(*FixedTestContainer)(nil), // 1: ssz_query.FixedTestContainer
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func init() { file_proto_ssz_query_ssz_query_proto_init() }
|
||||
func file_proto_ssz_query_ssz_query_proto_init() {
|
||||
if File_proto_ssz_query_ssz_query_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_proto_ssz_query_ssz_query_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FixedNestedContainer); 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[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FixedTestContainer); 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{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_proto_ssz_query_ssz_query_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_proto_ssz_query_ssz_query_proto_goTypes,
|
||||
DependencyIndexes: file_proto_ssz_query_ssz_query_proto_depIdxs,
|
||||
MessageInfos: file_proto_ssz_query_ssz_query_proto_msgTypes,
|
||||
}.Build()
|
||||
File_proto_ssz_query_ssz_query_proto = out.File
|
||||
file_proto_ssz_query_ssz_query_proto_rawDesc = nil
|
||||
file_proto_ssz_query_ssz_query_proto_goTypes = nil
|
||||
file_proto_ssz_query_ssz_query_proto_depIdxs = nil
|
||||
}
|
||||
43
proto/ssz_query/ssz_query.proto
Normal file
43
proto/ssz_query/ssz_query.proto
Normal file
@@ -0,0 +1,43 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package ssz_query;
|
||||
|
||||
import "proto/eth/ext/options.proto";
|
||||
|
||||
option go_package = "github.com/OffchainLabs/prysm/v6/proto/ssz_query";
|
||||
|
||||
|
||||
// ===== FIXED-SIZE TEST CONTAINERS =====
|
||||
// These containers are designed to test SSZ query functionality with comprehensive coverage
|
||||
// of all fixed-size SSZ types according to the SSZ specification.
|
||||
|
||||
// FixedNestedContainer - nested container for testing nested field access
|
||||
// Tests: nested container navigation, field offset calculations within nested structures
|
||||
message FixedNestedContainer {
|
||||
uint64 value1 = 1; // Test: uint64 basic type, offset calculation in nested context
|
||||
bytes value2 = 2 [ (ethereum.eth.ext.ssz_size) = "32" ]; // Test: fixed-size bytes in nested container
|
||||
}
|
||||
|
||||
// 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)
|
||||
message FixedTestContainer {
|
||||
// Basic integer types - test different integer sizes and their SSZ serialization
|
||||
uint32 field_uint32 = 3; // Test: uint32 basic type, offset: 0
|
||||
uint64 field_uint64 = 4; // Test: uint64 basic type, offset: 4
|
||||
|
||||
// Boolean type - test boolean serialization (1 byte in SSZ)
|
||||
bool field_bool = 5; // Test: boolean basic type, offset: 12
|
||||
|
||||
// Fixed-size bytes - test byte array
|
||||
bytes field_bytes32 = 8 [ (ethereum.eth.ext.ssz_size) = "32" ]; // Test: 32-byte array, offset: 13
|
||||
|
||||
// Nested container - test container nesting and field access
|
||||
FixedNestedContainer nested = 9; // Test: nested container navigation (8+32=40 bytes), offset: 45
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
238
proto/ssz_query/ssz_query.ssz.go
Normal file
238
proto/ssz_query/ssz_query.ssz.go
Normal file
@@ -0,0 +1,238 @@
|
||||
// Code generated by fastssz. DO NOT EDIT.
|
||||
package ssz_query
|
||||
|
||||
import (
|
||||
ssz "github.com/prysmaticlabs/fastssz"
|
||||
)
|
||||
|
||||
// MarshalSSZ ssz marshals the FixedNestedContainer object
|
||||
func (f *FixedNestedContainer) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(f)
|
||||
}
|
||||
|
||||
// MarshalSSZTo ssz marshals the FixedNestedContainer object to a target array
|
||||
func (f *FixedNestedContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
|
||||
// Field (0) 'Value1'
|
||||
dst = ssz.MarshalUint64(dst, f.Value1)
|
||||
|
||||
// Field (1) 'Value2'
|
||||
if size := len(f.Value2); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("--.Value2", size, 32)
|
||||
return
|
||||
}
|
||||
dst = append(dst, f.Value2...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalSSZ ssz unmarshals the FixedNestedContainer object
|
||||
func (f *FixedNestedContainer) UnmarshalSSZ(buf []byte) error {
|
||||
var err error
|
||||
size := uint64(len(buf))
|
||||
if size != 40 {
|
||||
return ssz.ErrSize
|
||||
}
|
||||
|
||||
// Field (0) 'Value1'
|
||||
f.Value1 = ssz.UnmarshallUint64(buf[0:8])
|
||||
|
||||
// Field (1) 'Value2'
|
||||
if cap(f.Value2) == 0 {
|
||||
f.Value2 = make([]byte, 0, len(buf[8:40]))
|
||||
}
|
||||
f.Value2 = append(f.Value2, buf[8:40]...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SizeSSZ returns the ssz encoded size in bytes for the FixedNestedContainer object
|
||||
func (f *FixedNestedContainer) SizeSSZ() (size int) {
|
||||
size = 40
|
||||
return
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the FixedNestedContainer object
|
||||
func (f *FixedNestedContainer) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(f)
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the FixedNestedContainer object with a hasher
|
||||
func (f *FixedNestedContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'Value1'
|
||||
hh.PutUint64(f.Value1)
|
||||
|
||||
// Field (1) 'Value2'
|
||||
if size := len(f.Value2); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("--.Value2", size, 32)
|
||||
return
|
||||
}
|
||||
hh.PutBytes(f.Value2)
|
||||
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalSSZ ssz marshals the FixedTestContainer object
|
||||
func (f *FixedTestContainer) MarshalSSZ() ([]byte, error) {
|
||||
return ssz.MarshalSSZ(f)
|
||||
}
|
||||
|
||||
// MarshalSSZTo ssz marshals the FixedTestContainer object to a target array
|
||||
func (f *FixedTestContainer) MarshalSSZTo(buf []byte) (dst []byte, err error) {
|
||||
dst = buf
|
||||
|
||||
// Field (0) 'FieldUint32'
|
||||
dst = ssz.MarshalUint32(dst, f.FieldUint32)
|
||||
|
||||
// Field (1) 'FieldUint64'
|
||||
dst = ssz.MarshalUint64(dst, f.FieldUint64)
|
||||
|
||||
// Field (2) 'FieldBool'
|
||||
dst = ssz.MarshalBool(dst, f.FieldBool)
|
||||
|
||||
// Field (3) 'FieldBytes32'
|
||||
if size := len(f.FieldBytes32); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("--.FieldBytes32", size, 32)
|
||||
return
|
||||
}
|
||||
dst = append(dst, f.FieldBytes32...)
|
||||
|
||||
// Field (4) 'Nested'
|
||||
if f.Nested == nil {
|
||||
f.Nested = new(FixedNestedContainer)
|
||||
}
|
||||
if dst, err = f.Nested.MarshalSSZTo(dst); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Field (5) 'VectorField'
|
||||
if size := len(f.VectorField); size != 24 {
|
||||
err = ssz.ErrVectorLengthFn("--.VectorField", size, 24)
|
||||
return
|
||||
}
|
||||
for ii := 0; ii < 24; ii++ {
|
||||
dst = ssz.MarshalUint64(dst, f.VectorField[ii])
|
||||
}
|
||||
|
||||
// Field (6) 'TrailingField'
|
||||
if size := len(f.TrailingField); size != 56 {
|
||||
err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56)
|
||||
return
|
||||
}
|
||||
dst = append(dst, f.TrailingField...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalSSZ ssz unmarshals the FixedTestContainer object
|
||||
func (f *FixedTestContainer) UnmarshalSSZ(buf []byte) error {
|
||||
var err error
|
||||
size := uint64(len(buf))
|
||||
if size != 333 {
|
||||
return ssz.ErrSize
|
||||
}
|
||||
|
||||
// Field (0) 'FieldUint32'
|
||||
f.FieldUint32 = ssz.UnmarshallUint32(buf[0:4])
|
||||
|
||||
// Field (1) 'FieldUint64'
|
||||
f.FieldUint64 = ssz.UnmarshallUint64(buf[4:12])
|
||||
|
||||
// Field (2) 'FieldBool'
|
||||
f.FieldBool, err = ssz.DecodeBool(buf[12:13])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Field (3) 'FieldBytes32'
|
||||
if cap(f.FieldBytes32) == 0 {
|
||||
f.FieldBytes32 = make([]byte, 0, len(buf[13:45]))
|
||||
}
|
||||
f.FieldBytes32 = append(f.FieldBytes32, buf[13:45]...)
|
||||
|
||||
// Field (4) 'Nested'
|
||||
if f.Nested == nil {
|
||||
f.Nested = new(FixedNestedContainer)
|
||||
}
|
||||
if err = f.Nested.UnmarshalSSZ(buf[45:85]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Field (5) 'VectorField'
|
||||
f.VectorField = ssz.ExtendUint64(f.VectorField, 24)
|
||||
for ii := 0; ii < 24; ii++ {
|
||||
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]))
|
||||
}
|
||||
f.TrailingField = append(f.TrailingField, buf[277:333]...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SizeSSZ returns the ssz encoded size in bytes for the FixedTestContainer object
|
||||
func (f *FixedTestContainer) SizeSSZ() (size int) {
|
||||
size = 333
|
||||
return
|
||||
}
|
||||
|
||||
// HashTreeRoot ssz hashes the FixedTestContainer object
|
||||
func (f *FixedTestContainer) HashTreeRoot() ([32]byte, error) {
|
||||
return ssz.HashWithDefaultHasher(f)
|
||||
}
|
||||
|
||||
// HashTreeRootWith ssz hashes the FixedTestContainer object with a hasher
|
||||
func (f *FixedTestContainer) HashTreeRootWith(hh *ssz.Hasher) (err error) {
|
||||
indx := hh.Index()
|
||||
|
||||
// Field (0) 'FieldUint32'
|
||||
hh.PutUint32(f.FieldUint32)
|
||||
|
||||
// Field (1) 'FieldUint64'
|
||||
hh.PutUint64(f.FieldUint64)
|
||||
|
||||
// Field (2) 'FieldBool'
|
||||
hh.PutBool(f.FieldBool)
|
||||
|
||||
// Field (3) 'FieldBytes32'
|
||||
if size := len(f.FieldBytes32); size != 32 {
|
||||
err = ssz.ErrBytesLengthFn("--.FieldBytes32", size, 32)
|
||||
return
|
||||
}
|
||||
hh.PutBytes(f.FieldBytes32)
|
||||
|
||||
// Field (4) 'Nested'
|
||||
if err = f.Nested.HashTreeRootWith(hh); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Field (5) 'VectorField'
|
||||
{
|
||||
if size := len(f.VectorField); size != 24 {
|
||||
err = ssz.ErrVectorLengthFn("--.VectorField", size, 24)
|
||||
return
|
||||
}
|
||||
subIndx := hh.Index()
|
||||
for _, i := range f.VectorField {
|
||||
hh.AppendUint64(i)
|
||||
}
|
||||
hh.Merkleize(subIndx)
|
||||
}
|
||||
|
||||
// Field (6) 'TrailingField'
|
||||
if size := len(f.TrailingField); size != 56 {
|
||||
err = ssz.ErrBytesLengthFn("--.TrailingField", size, 56)
|
||||
return
|
||||
}
|
||||
hh.PutBytes(f.TrailingField)
|
||||
|
||||
hh.Merkleize(indx)
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user