From 0568bec935062ce0c417fc693071cd2df0762558 Mon Sep 17 00:00:00 2001 From: fernantho Date: Mon, 13 Oct 2025 23:39:15 +0200 Subject: [PATCH] SSZ-QL: use FastSSZ-generated HashTreeRoot through SSZObject in sszInfo (#15805) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- ..._ssz-ql-use-fastssz-generated-functions.md | 3 + encoding/ssz/query/BUILD.bazel | 1 + encoding/ssz/query/analyzer.go | 5 +- encoding/ssz/query/query_test.go | 60 ++++++++++++++++++- encoding/ssz/query/ssz_info.go | 2 + encoding/ssz/query/ssz_object.go | 22 +++++++ encoding/ssz/query/testutil/runner.go | 5 +- encoding/ssz/query/testutil/type.go | 6 +- 8 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 changelog/fernantho_ssz-ql-use-fastssz-generated-functions.md create mode 100644 encoding/ssz/query/ssz_object.go diff --git a/changelog/fernantho_ssz-ql-use-fastssz-generated-functions.md b/changelog/fernantho_ssz-ql-use-fastssz-generated-functions.md new file mode 100644 index 0000000000..1743dca5cd --- /dev/null +++ b/changelog/fernantho_ssz-ql-use-fastssz-generated-functions.md @@ -0,0 +1,3 @@ +### Added + +- Delegate sszInfo HashTreeRoot to FastSSZ-generated implementations via SSZObject, enabling roots calculation for generated types while avoiding duplicate logic. diff --git a/encoding/ssz/query/BUILD.bazel b/encoding/ssz/query/BUILD.bazel index 93caf3b170..837d17054c 100644 --- a/encoding/ssz/query/BUILD.bazel +++ b/encoding/ssz/query/BUILD.bazel @@ -11,6 +11,7 @@ go_library( "path.go", "query.go", "ssz_info.go", + "ssz_object.go", "ssz_type.go", "tag_parser.go", "vector.go", diff --git a/encoding/ssz/query/analyzer.go b/encoding/ssz/query/analyzer.go index 5369d11561..52ba85ad6d 100644 --- a/encoding/ssz/query/analyzer.go +++ b/encoding/ssz/query/analyzer.go @@ -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 { diff --git a/encoding/ssz/query/query_test.go b/encoding/ssz/query/query_test.go index d67ab37bde..934f28a5f9 100644 --- a/encoding/ssz/query/query_test.go +++ b/encoding/ssz/query/query_test.go @@ -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 diff --git a/encoding/ssz/query/ssz_info.go b/encoding/ssz/query/ssz_info.go index e360013443..0ba3369196 100644 --- a/encoding/ssz/query/ssz_info.go +++ b/encoding/ssz/query/ssz_info.go @@ -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 diff --git a/encoding/ssz/query/ssz_object.go b/encoding/ssz/query/ssz_object.go new file mode 100644 index 0000000000..a56b15983d --- /dev/null +++ b/encoding/ssz/query/ssz_object.go @@ -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() +} diff --git a/encoding/ssz/query/testutil/runner.go b/encoding/ssz/query/testutil/runner.go index e639dd3fc7..610e2d6be0 100644 --- a/encoding/ssz/query/testutil/runner.go +++ b/encoding/ssz/query/testutil/runner.go @@ -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 diff --git a/encoding/ssz/query/testutil/type.go b/encoding/ssz/query/testutil/type.go index 645fd7d24c..6e300adc20 100644 --- a/encoding/ssz/query/testutil/type.go +++ b/encoding/ssz/query/testutil/type.go @@ -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 }