Skip to content

Commit

Permalink
ssz, cmd/sszgen, tests: mass add missing beacon types, add encoders
Browse files Browse the repository at this point in the history
  • Loading branch information
karalabe committed Jul 8, 2024
1 parent 67adb6c commit 83b09c0
Show file tree
Hide file tree
Showing 40 changed files with 1,016 additions and 339 deletions.
14 changes: 9 additions & 5 deletions cmd/sszgen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,12 @@ func generateSizeSSZ(ctx *genContext, typ *sszContainer) ([]byte, error) {
fmt.Fprintf(&b, " return size\n")
fmt.Fprintf(&b, " }\n")
for i := range typ.opsets {
if _, ok := typ.opsets[i].(*opsetDynamic); ok {
fmt.Fprintf(&b, " size += obj.%s.SizeSSZ(false)\n", typ.fields[i])
if opset, ok := typ.opsets[i].(*opsetDynamic); ok {
call := generateCall(opset.size, "", "obj."+typ.fields[i])
fmt.Fprintf(&b, " size += ssz.%s\n", call)
}
}
fmt.Fprintf(&b, "\n")
fmt.Fprintf(&b, " return size\n")
fmt.Fprintf(&b, "}\n")
} else {
Expand All @@ -253,10 +255,12 @@ func generateSizeSSZ(ctx *genContext, typ *sszContainer) ([]byte, error) {
fmt.Fprintf(&b, " return size\n")
fmt.Fprintf(&b, " }\n")
for i := range typ.opsets {
if _, ok := typ.opsets[i].(*opsetDynamic); ok {
fmt.Fprintf(&b, " size += obj.%s.SizeSSZ(false)\n", typ.fields[i])
if opset, ok := typ.opsets[i].(*opsetDynamic); ok {
call := generateCall(opset.size, "", "obj."+typ.fields[i])
fmt.Fprintf(&b, " size += ssz.%s\n", call)
}
}
fmt.Fprintf(&b, "\n")
fmt.Fprintf(&b, " return size\n")
fmt.Fprintf(&b, "}\n")
}
Expand Down Expand Up @@ -326,7 +330,7 @@ func generateDefineSSZ(ctx *genContext, typ *sszContainer) ([]byte, error) {
field := typ.fields[i]
if opset, ok := (typ.opsets[i]).(*opsetDynamic); ok {
call := generateCall(opset.defineContent, "codec", "obj."+field, opset.limits...)
fmt.Fprintf(&b, " ssz.%s // Field ("+indexRule+") - "+nameRule+" - ? bytes\n", call, i, field)
fmt.Fprintf(&b, " ssz.%s // Field ("+indexRule+") - "+nameRule+" - ? bytes\n", call, i, field)
}
}
}
Expand Down
190 changes: 179 additions & 11 deletions cmd/sszgen/opset.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type opsetStatic struct {
// codec operates on a given dynamic type. Ideally these would be some go/types
// function values, but alas too much pain, especially with generics.
type opsetDynamic struct {
size string // SizeXYZ method for the SizeSSZ method
defineOffset string // DefineXYZOffset method for the ssz.Codec
defineContent string // DefineXYZContent method for the ssz.Codec
encodeOffset string // EncodeXYZOffset method for the ssz.Encoder
Expand Down Expand Up @@ -101,6 +102,22 @@ func (p *parseContext) resolveArrayOpset(typ types.Type, size int, tags *sizeTag
"DecodeStaticBytes({{.Codec}}, {{.Field}}[:])",
[]int{size},
}, nil

case types.Uint64:
if tags != nil {
if (len(tags.size) != 1 && len(tags.size) != 2) ||
(len(tags.size) == 1 && tags.size[0] != size) ||
(len(tags.size) == 2 && (tags.size[0] != size || tags.size[1] != 8)) {
return nil, fmt.Errorf("array of byte basic type tag conflict: field is %d bytes, tag wants %v bytes", size, tags.size)
}
}
return &opsetStatic{
"DefineArrayOfUint64s({{.Codec}}, {{.Field}}[:])",
"EncodeArrayOfUint64s({{.Codec}}, {{.Field}}[:])",
"DecodeArrayOfUint64s({{.Codec}}, {{.Field}}[:])",
[]int{size, 8},
}, nil

default:
return nil, fmt.Errorf("unsupported array item basic type: %s", typ)
}
Expand Down Expand Up @@ -157,11 +174,14 @@ func (p *parseContext) resolveSliceOpset(typ types.Type, tags *sizeTag) (opset,
switch typ.Kind() {
case types.Byte:
// Slice of bytes. If we have ssz-size, it's a static slice
if tags.size != nil {
if len(tags.size) > 0 {
if (len(tags.size) != 1 && len(tags.size) != 2) ||
(len(tags.size) == 2 && tags.size[1] != 1) {
return nil, fmt.Errorf("static slice of byte basic type tag conflict: needs [N] or [N, 1] tag, has %v", tags.size)
}
if len(tags.limit) > 0 {
return nil, fmt.Errorf("static slice of byte basic type cannot have ssz-max tag")
}
return &opsetStatic{
"DefineCheckedStaticBytes({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeCheckedStaticBytes({{.Codec}}, &{{.Field}})",
Expand All @@ -171,12 +191,13 @@ func (p *parseContext) resolveSliceOpset(typ types.Type, tags *sizeTag) (opset,
}
// Not a static slice of bytes, we need to pull ssz-max for the limits
if tags.limit == nil {
return nil, fmt.Errorf("slice of byte basic type requires ssz-max tag")
return nil, fmt.Errorf("dynamic slice of byte basic type requires ssz-max tag")
}
if len(tags.limit) != 1 {
return nil, fmt.Errorf("dynamic slice of byte basic type tag conflict: needs [N] tag, has %v", tags.limit)
}
return &opsetDynamic{
"SizeDynamicBytes({{.Field}})",
"DefineDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"DefineDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeDynamicBytesOffset({{.Codec}}, &{{.Field}})",
Expand All @@ -185,30 +206,176 @@ func (p *parseContext) resolveSliceOpset(typ types.Type, tags *sizeTag) (opset,
"DecodeDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
[]int{0}, tags.limit,
}, nil

case types.Uint64:
// Slice of uint64s. If we have ssz-size, it's a static slice
if len(tags.size) > 0 {
if (len(tags.size) != 1 && len(tags.size) != 2) ||
(len(tags.size) == 2 && tags.size[1] != 8) {
return nil, fmt.Errorf("static slice of uint64 basic type tag conflict: needs [N] or [N, 8] tag, has %v", tags.size)
}
if len(tags.limit) > 0 {
return nil, fmt.Errorf("static slice of uint64 basic type cannot have ssz-max tag")
}
return &opsetStatic{
"DefineCheckedStaticUint64({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeCheckedStaticUint64({{.Codec}}, &{{.Field}})",
"DecodeCheckedStaticUint64({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
[]int{tags.size[0]},
}, nil
}
// Not a static slice of bytes, we need to pull ssz-max for the limits
if tags.limit == nil {
return nil, fmt.Errorf("dynamic slice of uint64 basic type requires ssz-max tag")
}
if len(tags.limit) != 1 {
return nil, fmt.Errorf("dynamic slice of uint64 basic type tag conflict: needs [N] tag, has %v", tags.limit)
}
return &opsetDynamic{
"SizeSliceOfUint64s({{.Field}})",
"DefineSliceOfUint64sOffset({{.Codec}}, &{{.Field}})",
"DefineSliceOfUint64sContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeSliceOfUint64sOffset({{.Codec}}, &{{.Field}})",
"EncodeSliceOfUint64sContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"DecodeSliceOfUint64sOffset({{.Codec}}, &{{.Field}})",
"DecodeSliceOfUint64sContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
nil, tags.limit,
}, nil

default:
return nil, fmt.Errorf("unsupported slice item basic type: %s", typ)
}
case *types.Pointer:
if types.Implements(typ, p.staticObjectIface) {
if len(tags.size) > 0 {
return nil, fmt.Errorf("static slice of static objects not yet implemented")
}
if len(tags.limit) != 1 {
return nil, fmt.Errorf("dynamic slice of static objects type tag conflict: needs [N] tag, has %v", tags.limit)
}
return &opsetDynamic{
"SizeSliceOfStaticObjects({{.Field}})",
"DefineSliceOfStaticObjectsOffset({{.Codec}}, &{{.Field}})",
"DefineSliceOfStaticObjectsContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeSliceOfStaticObjectsOffset({{.Codec}}, &{{.Field}})",
"EncodeSliceOfStaticObjectsContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"DecodeSliceOfStaticObjectsOffset({{.Codec}}, &{{.Field}})",
"DecodeSliceOfStaticObjectsContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
nil, tags.limit,
}, nil
}
if types.Implements(typ, p.dynamicObjectIface) {
if len(tags.size) > 0 {
return nil, fmt.Errorf("static slice of dynamic objects not yet implemented")
}
if len(tags.limit) != 1 {
return nil, fmt.Errorf("dynamic slice of dynamic objects type tag conflict: needs [N] tag, has %v", tags.limit)
}
return &opsetDynamic{
"SizeSliceOfDynamicObjects({{.Field}})",
"DefineSliceOfDynamicObjectsOffset({{.Codec}}, &{{.Field}})",
"DefineSliceOfDynamicObjectsContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeSliceOfDynamicObjectsOffset({{.Codec}}, &{{.Field}})",
"EncodeSliceOfDynamicObjectsContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"DecodeSliceOfDynamicObjectsOffset({{.Codec}}, &{{.Field}})",
"DecodeSliceOfDynamicObjectsContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
nil, tags.limit,
}, nil

}
return nil, fmt.Errorf("unsupported pointer slice item type %s", typ.String())

case *types.Array:
return p.resolveSliceOfArrayOpset(typ.Elem(), int(typ.Len()), tags)

case *types.Slice:
return p.resolveSliceOfSliceOpset(typ.Elem())
return p.resolveSliceOfSliceOpset(typ.Elem(), tags)

case *types.Named:
return p.resolveSliceOpset(typ.Underlying(), tags)

default:
return nil, fmt.Errorf("unsupported slice item type: %s", typ)
}
}

func (p *parseContext) resolveSliceOfSliceOpset(typ types.Type) (*opsetDynamic, error) {
func (p *parseContext) resolveSliceOfArrayOpset(typ types.Type, innerSize int, tags *sizeTag) (opset, error) {
switch typ := typ.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Byte:
// Slice of array of bytes. If we have ssz-size, it's a static slice.
if len(tags.size) > 0 {
if (len(tags.size) != 1 && len(tags.size) != 2) ||
(len(tags.size) == 2 && tags.size[1] != innerSize) {
return nil, fmt.Errorf("static slice of array of byte basic type tag conflict: needs [N] or [N, %d] tag, has %v", innerSize, tags.size)
}
if len(tags.limit) > 0 {
return nil, fmt.Errorf("static slice of array of byte basic type cannot have ssz-max tag")
}
return &opsetStatic{
"DefineCheckedArrayOfStaticBytes({{.Codec}}, &{{.Field}}, {{.MaxItems}})",
"EncodeCheckedArrayOfStaticBytes({{.Codec}}, &{{.Field}})",
"DecodeCheckedArrayOfStaticBytes({{.Codec}}, &{{.Field}}, {{.MaxItems}})",
[]int{tags.size[0], innerSize},
}, nil
}
// Not a static slice of array of bytes, we need to pull ssz-max for the limits
if tags.limit == nil {
return nil, fmt.Errorf("dynamic slice of array of byte basic type requires ssz-max tag")
}
if len(tags.limit) != 1 {
return nil, fmt.Errorf("dynamic slice of array of byte basic type tag conflict: needs [N] tag, has %v", tags.limit)
}
return &opsetDynamic{
"DefineSliceOfDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"DefineSliceOfDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxItems}}, {{.MaxSize}})",
"EncodeSliceOfDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"EncodeSliceOfDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxItems}}, {{.MaxSize}})",
"DecodeSliceOfDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"DecodeSliceOfDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxItems}}, {{.MaxSize}})",
[]int{0, 0}, nil,
"SizeSliceOfStaticBytes({{.Field}})",
"DefineSliceOfStaticBytesOffset({{.Codec}}, &{{.Field}})",
"DefineSliceOfStaticBytesContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"EncodeSliceOfStaticBytesOffset({{.Codec}}, &{{.Field}})",
"EncodeSliceOfStaticBytesContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
"DecodeSliceOfStaticBytesOffset({{.Codec}}, &{{.Field}})",
"DecodeSliceOfStaticBytesContent({{.Codec}}, &{{.Field}}, {{.MaxSize}})",
nil, tags.limit,
}, nil
default:
return nil, fmt.Errorf("unsupported array-of-array item basic type: %s", typ)
}
default:
return nil, fmt.Errorf("unsupported array-of-array item type: %s", typ)
}
}

func (p *parseContext) resolveSliceOfSliceOpset(typ types.Type, tags *sizeTag) (*opsetDynamic, error) {
switch typ := typ.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Byte:
// Slice of slice of bytes. At this point we have 2D possibilities of
// ssz-size and ssz-max combinations, each resulting in a different
// call that we have to make. Reject any conflicts in the tags, after
// which assemble the required combo.
switch {
case len(tags.size) > 0 && len(tags.limit) == 0:
return nil, fmt.Errorf("static slice of static slice of bytes not implemented yet")

case len(tags.size) == 0 && len(tags.limit) > 0:
if len(tags.limit) != 2 {
return nil, fmt.Errorf("dynamic slice of dynamic slice of byte basic type tag conflict: needs [N, M] ssz-max tag, has %v", tags.limit)
}
return &opsetDynamic{
"SizeSliceOfDynamicBytes({{.Field}})",
"DefineSliceOfDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"DefineSliceOfDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxItems}}, {{.MaxSize}})",
"EncodeSliceOfDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"EncodeSliceOfDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxItems}}, {{.MaxSize}})",
"DecodeSliceOfDynamicBytesOffset({{.Codec}}, &{{.Field}})",
"DecodeSliceOfDynamicBytesContent({{.Codec}}, &{{.Field}}, {{.MaxItems}}, {{.MaxSize}})",
nil, tags.limit,
}, nil

default:
return nil, fmt.Errorf("not implemented yet")
}
default:
return nil, fmt.Errorf("unsupported slice-of-slice item basic type: %s", typ)
}
Expand Down Expand Up @@ -250,6 +417,7 @@ func (p *parseContext) resolvePointerOpset(typ *types.Pointer, tags *sizeTag) (o
return nil, fmt.Errorf("dynamic object type cannot have any ssz tags")
}
return &opsetDynamic{
"SizeDynamicObject({{.Field}})",
"DefineDynamicObjectOffset({{.Codec}}, &{{.Field}})",
"DefineDynamicObjectContent({{.Codec}}, &{{.Field}})",
"EncodeDynamicObjectOffset({{.Codec}}, &{{.Field}})",
Expand Down
9 changes: 9 additions & 0 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ func DefineDynamicObjectContent[T newableDynamicObject[U], U any](c *Codec, obj
DecodeDynamicObjectContent(c.dec, obj)
}

// DefineArrayOfUint64s defines the next field as a static array of uint64s.
func DefineArrayOfUint64s[T ~uint64](c *Codec, ns []T) {
if c.enc != nil {
EncodeArrayOfUint64s(c.enc, ns)
return
}
DecodeArrayOfUint64s(c.dec, ns)
}

// DefineSliceOfUint64sOffset defines the next field as a dynamic slice of uint64s.
func DefineSliceOfUint64sOffset[T ~uint64](c *Codec, ns *[]T) {
if c.enc != nil {
Expand Down
26 changes: 26 additions & 0 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,32 @@ func DecodeDynamicObjectContent[T newableDynamicObject[U], U any](dec *Decoder,
dec.flushDynamics()
}

// DecodeArrayOfUint64s parses a static array of uint64s.
func DecodeArrayOfUint64s[T ~uint64](dec *Decoder, ns []T) {
if dec.err != nil {
return
}
if dec.inReader != nil {
for i := 0; i < len(ns); i++ {
_, dec.err = io.ReadFull(dec.inReader, dec.buf[:8])
if dec.err != nil {
return
}
ns[i] = T(binary.LittleEndian.Uint64(dec.buf[:8]))
dec.inRead += 8
}
} else {
for i := 0; i < len(ns); i++ {
if len(dec.inBuffer) < 8 {
dec.err = io.ErrUnexpectedEOF
return
}
ns[i] = T(binary.LittleEndian.Uint64(dec.inBuffer))
dec.inBuffer = dec.inBuffer[8:]
}
}
}

// DecodeSliceOfUint64sOffset parses a dynamic slice of uint64s.
func DecodeSliceOfUint64sOffset[T ~uint64](dec *Decoder, ns *[]T) {
dec.decodeOffset(false)
Expand Down
24 changes: 24 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,30 @@ func EncodeDynamicObjectContent(enc *Encoder, obj DynamicObject) {
obj.DefineSSZ(enc.codec)
}

// EncodeArrayOfUint64s serializes a static array of uint64s.
//
// The reason the ns is passed by pointer and not by value is to prevent it from
// escaping to the heap (and incurring an allocation) when passing it to the
// output stream.
func EncodeArrayOfUint64s[T ~uint64](enc *Encoder, ns []T) {
// Internally this method is essentially calling EncodeUint64 on all numbers
// in a loop. Practically, we've inlined that call to make things a *lot* faster.
if enc.outWriter != nil {
for _, n := range ns {
if enc.err != nil {
return
}
binary.LittleEndian.PutUint64(enc.buf[:8], (uint64)(n))
_, enc.err = enc.outWriter.Write(enc.buf[:8])
}
} else {
for _, n := range ns {
binary.LittleEndian.PutUint64(enc.outBuffer, (uint64)(n))
enc.outBuffer = enc.outBuffer[8:]
}
}
}

// EncodeSliceOfUint64sOffset serializes a dynamic slice of uint64s.
func EncodeSliceOfUint64sOffset[T ~uint64](enc *Encoder, ns []T) {
if enc.outWriter != nil {
Expand Down
9 changes: 9 additions & 0 deletions sizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ func SizeDynamicObject[T DynamicObject](obj T) uint32 {
return obj.SizeSSZ(false)
}

// SizeSliceOfStaticBytes returns the serialized size of the dynamic part of a dynamic
// list of static blobs.
func SizeSliceOfStaticBytes[T commonBytesLengths](blobs []T) uint32 {
if len(blobs) == 0 {
return 0
}
return uint32(len(blobs) * len(blobs[0]))
}

// SizeSliceOfDynamicBytes returns the serialized size of the dynamic part of a dynamic
// list of dynamic blobs.
func SizeSliceOfDynamicBytes(blobs [][]byte) uint32 {
Expand Down
Loading

0 comments on commit 83b09c0

Please sign in to comment.