From 6fd8d25f12114e2c36979bf6dcdc894893a37d40 Mon Sep 17 00:00:00 2001 From: terence Date: Mon, 18 Aug 2025 09:30:20 -0700 Subject: [PATCH 1/2] Fix fastssz to support vectors/lists of composite types - Add special handling for TypeVector with TypeContainer elements - Generate code that calls HashTreeRootWith() for each composite element - Fixes panic when using composite types with ssz_size annotations --- sszgen/hash.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sszgen/hash.go b/sszgen/hash.go index f7b4ba3..58217fe 100755 --- a/sszgen/hash.go +++ b/sszgen/hash.go @@ -183,6 +183,21 @@ func (v *Value) hashTreeRoot(name string, appendBytes bool) string { return fmt.Sprintf("hh.PutBool(%s)", name) case TypeVector: + if v.e.t == TypeContainer { + // Handle vectors of composite types + tmpl := `{ + subIndx := hh.Index() + for _, elem := range {{.name}} { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.Merkleize(subIndx) + }` + return execTmpl(tmpl, map[string]interface{}{ + "name": name, + }) + } return v.hashRoots(false, v.e.t) case TypeList: From 8e4cc9ddd324aab5eac385a2c21b66f5913d5437 Mon Sep 17 00:00:00 2001 From: terence Date: Mon, 18 Aug 2025 13:49:30 -0700 Subject: [PATCH 2/2] Add test for vectors of composite types --- sszgen/hash_composite_vector_test.go | 123 +++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 sszgen/hash_composite_vector_test.go diff --git a/sszgen/hash_composite_vector_test.go b/sszgen/hash_composite_vector_test.go new file mode 100644 index 0000000..e123510 --- /dev/null +++ b/sszgen/hash_composite_vector_test.go @@ -0,0 +1,123 @@ +package main + +import ( + "bytes" + "testing" + + ssz "github.com/prysmaticlabs/fastssz" +) + +// Test struct for composite types +type TestComposite struct { + Field1 uint64 + Field2 []byte `ssz-size:"32"` +} + +// Test struct with vector of composite types +type TestVectorComposite struct { + CompositeVector [3]TestComposite `ssz-size:"3"` +} + +func (t *TestComposite) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + hh.PutUint64(t.Field1) + hh.PutBytes(t.Field2) + hh.Merkleize(indx) + return +} + +func (t *TestComposite) HashTreeRoot() ([32]byte, error) { + hh := ssz.DefaultHasherPool.Get() + defer ssz.DefaultHasherPool.Put(hh) + err := t.HashTreeRootWith(hh) + if err != nil { + return [32]byte{}, err + } + return hh.HashRoot() +} + +func (t *TestVectorComposite) HashTreeRootWith(hh *ssz.Hasher) (err error) { + indx := hh.Index() + + // This tests the new code path for vectors of composite types + { + subIndx := hh.Index() + for _, elem := range t.CompositeVector { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.Merkleize(subIndx) + } + + hh.Merkleize(indx) + return +} + +func (t *TestVectorComposite) HashTreeRoot() ([32]byte, error) { + hh := ssz.DefaultHasherPool.Get() + defer ssz.DefaultHasherPool.Put(hh) + err := t.HashTreeRootWith(hh) + if err != nil { + return [32]byte{}, err + } + return hh.HashRoot() +} + +func TestVectorCompositeHashTreeRoot(t *testing.T) { + // Create test data + testData := &TestVectorComposite{ + CompositeVector: [3]TestComposite{ + {Field1: 1, Field2: make([]byte, 32)}, + {Field1: 2, Field2: make([]byte, 32)}, + {Field1: 3, Field2: make([]byte, 32)}, + }, + } + + // Fill field2 with test data + for i := 0; i < 32; i++ { + testData.CompositeVector[0].Field2[i] = byte(i) + testData.CompositeVector[1].Field2[i] = byte(i + 32) + testData.CompositeVector[2].Field2[i] = byte(i + 64) + } + + // Test that HashTreeRoot doesn't return error + root, err := testData.HashTreeRoot() + if err != nil { + t.Fatalf("HashTreeRoot failed: %v", err) + } + + // Test that root is not empty + emptyRoot := [32]byte{} + if bytes.Equal(root[:], emptyRoot[:]) { + t.Fatal("HashTreeRoot returned empty root") + } + + // Test consistency - same input should produce same root + root2, err := testData.HashTreeRoot() + if err != nil { + t.Fatalf("Second HashTreeRoot failed: %v", err) + } + + if !bytes.Equal(root[:], root2[:]) { + t.Fatal("HashTreeRoot is not consistent") + } + + // Test that different data produces different root + testData2 := &TestVectorComposite{ + CompositeVector: [3]TestComposite{ + {Field1: 4, Field2: make([]byte, 32)}, // Different field1 + {Field1: 2, Field2: make([]byte, 32)}, + {Field1: 3, Field2: make([]byte, 32)}, + }, + } + + root3, err := testData2.HashTreeRoot() + if err != nil { + t.Fatalf("Third HashTreeRoot failed: %v", err) + } + + if bytes.Equal(root[:], root3[:]) { + t.Fatal("Different inputs produced same hash root") + } +}