SSZ-QL: use FastSSZ-generated HashTreeRoot through SSZObject in sszInfo (#15805)

* stored CL object to enable the usage Fastssz's HashTreeRoot(). added basic test

* refactorization - using interfaces instead of storing original object

* added tests covering ssz custom types

* renamed hash_tree_root to ssz_interface as it contains MarshalSSZ and UnmarshalSSZ functions

* run gazelle

* renamed test and improved comments

* refactored test and extend to marshalSSZ and UnmarshalSSZ

* added changelog

* updated comment

* Changed SSZIface name to SSZObject. Removed MarshalSSZ and UnmarshalSSZ function signatures from interface as they are not used still. Refactored tests.

* renamed file ssz_interface.go to ssz_object.go. merge test from ssz_interface_test.go into query_test.go.
reordered source SSZObject field from sszInfo struct

* sticked SSZObject interface to HashTreeRoot() function, the only one needed so far

* run gazelle :)

---------

Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
fernantho
2025-10-13 23:39:15 +02:00
committed by GitHub
parent e463bcd1e1
commit 0568bec935
8 changed files with 98 additions and 6 deletions

View File

@@ -0,0 +1,3 @@
### Added
- Delegate sszInfo HashTreeRoot to FastSSZ-generated implementations via SSZObject, enabling roots calculation for generated types while avoiding duplicate logic.

View File

@@ -11,6 +11,7 @@ go_library(
"path.go",
"query.go",
"ssz_info.go",
"ssz_object.go",
"ssz_type.go",
"tag_parser.go",
"vector.go",

View File

@@ -10,7 +10,7 @@ import (
const offsetBytes = 4
// AnalyzeObject analyzes given object and returns its SSZ information.
func AnalyzeObject(obj any) (*sszInfo, error) {
func AnalyzeObject(obj SSZObject) (*sszInfo, error) {
value := dereferencePointer(obj)
info, err := analyzeType(value.Type(), nil)
@@ -18,6 +18,9 @@ func AnalyzeObject(obj any) (*sszInfo, error) {
return nil, fmt.Errorf("could not analyze type %s: %w", value.Type().Name(), err)
}
// Store the original object interface
info.source = obj
// Populate variable-length information using the actual value.
err = PopulateVariableLengthInfo(info, value.Interface())
if err != nil {

View File

@@ -302,7 +302,7 @@ func getFixedTestContainerSpec() testutil.TestSpec {
return testutil.TestSpec{
Name: "FixedTestContainer",
Type: sszquerypb.FixedTestContainer{},
Type: &sszquerypb.FixedTestContainer{},
Instance: testContainer,
PathTests: []testutil.PathTest{
// Basic types
@@ -364,6 +364,62 @@ func getFixedTestContainerSpec() testutil.TestSpec {
}
}
func TestSSZObject_batch(t *testing.T) {
tests := []struct {
name string
obj any
}{
{
name: "FixedNestedContainer",
obj: &sszquerypb.FixedNestedContainer{
Value1: 42,
Value2: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
},
},
{
name: "FixedTestContainer",
obj: createFixedTestContainer(),
},
{
name: "VariableNestedContainer",
obj: &sszquerypb.VariableNestedContainer{
Value1: 84,
FieldListUint64: []uint64{1, 2, 3, 4, 5},
NestedListField: [][]byte{
{0x0a, 0x0b, 0x0c},
{0x1a, 0x1b, 0x1c, 0x1d},
},
},
},
{
name: "VariableTestContainer",
obj: createVariableTestContainer(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Analyze the object to get its sszInfo
object, ok := tt.obj.(query.SSZObject)
require.Equal(t, true, ok, "Expected object to implement SSZObject")
info, err := query.AnalyzeObject(object)
require.NoError(t, err)
require.NotNil(t, info, "Expected non-nil SSZ info")
// Ensure the original object implements SSZObject
originalFunctions, ok := tt.obj.(query.SSZObject)
require.Equal(t, ok, true, "Original object does not implement SSZObject")
// Call HashTreeRoot on the sszInfo and compare results
hashTreeRoot, err := info.HashTreeRoot()
require.NoError(t, err, "HashTreeRoot should not return an error")
expectedHashTreeRoot, err := originalFunctions.HashTreeRoot()
require.NoError(t, err, "HashTreeRoot on original object should not return an error")
require.Equal(t, expectedHashTreeRoot, hashTreeRoot, "HashTreeRoot from sszInfo should match original object's HashTreeRoot")
})
}
}
func createVariableTestContainer() *sszquerypb.VariableTestContainer {
leadingField := make([]byte, 32)
for i := range leadingField {
@@ -439,7 +495,7 @@ func getVariableTestContainerSpec() testutil.TestSpec {
return testutil.TestSpec{
Name: "VariableTestContainer",
Type: sszquerypb.VariableTestContainer{},
Type: &sszquerypb.VariableTestContainer{},
Instance: testContainer,
PathTests: []testutil.PathTest{
// Fixed leading field

View File

@@ -13,6 +13,8 @@ type sszInfo struct {
sszType SSZType
// Type in Go. Need this for unmarshaling.
typ reflect.Type
// Original object being analyzed
source SSZObject
// isVariable is true if the struct contains any variable-size fields.
isVariable bool

View File

@@ -0,0 +1,22 @@
package query
import "errors"
type SSZObject interface {
HashTreeRoot() ([32]byte, error)
}
// HashTreeRoot calls the HashTreeRoot method on the stored interface if it implements SSZObject.
// Returns the 32-byte hash tree root or an error if the interface doesn't support hashing.
func (info *sszInfo) HashTreeRoot() ([32]byte, error) {
if info == nil {
return [32]byte{}, errors.New("sszInfo is nil")
}
if info.source == nil {
return [32]byte{}, errors.New("sszInfo.source is nil")
}
// Check if the value implements the Hashable interface
return info.source.HashTreeRoot()
}

View File

@@ -10,7 +10,10 @@ import (
func RunStructTest(t *testing.T, spec TestSpec) {
t.Run(spec.Name, func(t *testing.T) {
info, err := query.AnalyzeObject(spec.Type)
object, ok := spec.Type.(query.SSZObject)
require.Equal(t, true, ok, "spec.Type must implement SSZObject interface")
require.NotNil(t, object, "spec.Type must not be nil")
info, err := query.AnalyzeObject(object)
require.NoError(t, err)
testInstance := spec.Instance

View File

@@ -1,5 +1,7 @@
package testutil
import "github.com/OffchainLabs/prysm/v6/encoding/ssz/query"
type PathTest struct {
Path string
Expected any
@@ -7,7 +9,7 @@ type PathTest struct {
type TestSpec struct {
Name string
Type any
Instance any
Type query.SSZObject
Instance query.SSZObject
PathTests []PathTest
}