Skip to content

Commit

Permalink
ssz, cmd/sszgen, tests: support big.Int as uint256, fix tag errors
Browse files Browse the repository at this point in the history
  • Loading branch information
karalabe committed Jul 23, 2024
1 parent 843f9bc commit adeb27c
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 10 deletions.
16 changes: 16 additions & 0 deletions cmd/sszgen/opset.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,22 @@ func (p *parseContext) resolvePointerOpset(typ *types.Pointer, tags *sizeTag) (o
[]int{32},
}, nil
}
if isBigInt(typ.Elem()) {
if tags != nil {
if tags.limit != nil {
return nil, fmt.Errorf("big.Int (uint256) basic type cannot have ssz-max tag")
}
if len(tags.size) != 1 || tags.size[0] != 32 {
return nil, fmt.Errorf("big.Int (uint256) basic type tag conflict: filed is [32] bytes, tag wants %v", tags.size)
}
}
return &opsetStatic{
"DefineUint256BigInt({{.Codec}}, &{{.Field}})",
"EncodeUint256BigInt({{.Codec}}, &{{.Field}})",
"DecodeUint256BigInt({{.Codec}}, &{{.Field}})",
[]int{32},
}, nil
}
if types.Implements(typ, p.staticObjectIface) {
if tags != nil {
return nil, fmt.Errorf("static object type cannot have any ssz tags")
Expand Down
2 changes: 1 addition & 1 deletion cmd/sszgen/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func parseTags(input string) (bool, *sizeTag, error) {
}
}
)
for _, tag := range strings.Split(input, " ") {
for _, tag := range strings.Fields(input) {
parts := strings.Split(tag, ":")
if len(parts) != 2 {
return false, nil, fmt.Errorf("invalid tag %s", tag)
Expand Down
2 changes: 1 addition & 1 deletion cmd/sszgen/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (p *parseContext) makeContainer(named *types.Named, typ *types.Struct) (*ss
}
ignore, tags, err := parseTags(typ.Tag(i))
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse field %s.%s tags: %v", named.Obj().Name(), f.Name(), err)
}
if ignore {
continue
Expand Down
15 changes: 15 additions & 0 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package ssz

import (
"math/big"

"github.com/holiman/uint256"
"github.com/prysmaticlabs/go-bitfield"
)
Expand Down Expand Up @@ -90,6 +92,19 @@ func DefineUint256(c *Codec, n **uint256.Int) {
HashUint256(c.has, *n)
}

// DefineUint256BigInt defines the next field as a uint256.
func DefineUint256BigInt(c *Codec, n **big.Int) {
if c.enc != nil {
EncodeUint256BigInt(c.enc, *n)
return
}
if c.dec != nil {
DecodeUint256BigInt(c.dec, n)
return
}
HashUint256BigInt(c.has, *n)
}

// DefineStaticBytes defines the next field as static binary blob. This method
// can be used for byte arrays.
func DefineStaticBytes[T commonBytesLengths](c *Codec, blob *T) {
Expand Down
31 changes: 29 additions & 2 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"encoding/binary"
"fmt"
"io"
"math/big"
"math/bits"
"unsafe"

Expand Down Expand Up @@ -58,8 +59,9 @@ type Decoder struct {

err error // Any write error to halt future encoding calls

codec *Codec // Self-referencing to pass DefineSSZ calls through (API trick)
buf [32]byte // Integer conversion buffer
codec *Codec // Self-referencing to pass DefineSSZ calls through (API trick)
buf [32]byte // Integer conversion buffer
bufInt uint256.Int // Big.Int conversion buffer (not pointer, alloc free)

length uint32 // Message length being decoded
lengths []uint32 // Stack of lengths from outer calls
Expand Down Expand Up @@ -155,6 +157,31 @@ func DecodeUint256(dec *Decoder, n **uint256.Int) {
}
}

// DecodeUint256BigInt parses a uint256 into a big.Int.
func DecodeUint256BigInt(dec *Decoder, n **big.Int) {
if dec.err != nil {
return
}
if dec.inReader != nil {
_, dec.err = io.ReadFull(dec.inReader, dec.buf[:32])
if dec.err != nil {
return
}
dec.inRead += 32

dec.bufInt.UnmarshalSSZ(dec.buf[:32])
*n = dec.bufInt.ToBig() // TODO(karalabe): make this alloc free (https://github.com/holiman/uint256/pull/177)
} else {
if len(dec.inBuffer) < 32 {
dec.err = io.ErrUnexpectedEOF
return
}
dec.bufInt.UnmarshalSSZ(dec.inBuffer[:32])
*n = dec.bufInt.ToBig() // TODO(karalabe): make this alloc free (https://github.com/holiman/uint256/pull/177)
dec.inBuffer = dec.inBuffer[32:]
}
}

// DecodeStaticBytes parses a static binary blob.
func DecodeStaticBytes[T commonBytesLengths](dec *Decoder, blob *T) {
if dec.err != nil {
Expand Down
36 changes: 33 additions & 3 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package ssz
import (
"encoding/binary"
"io"
"math/big"
"unsafe"

"github.com/holiman/uint256"
Expand Down Expand Up @@ -68,9 +69,11 @@ type Encoder struct {
outWriter io.Writer // Underlying output stream to write into (streaming mode)
outBuffer []byte // Underlying output stream to write into (buffered mode)

err error // Any write error to halt future encoding calls
codec *Codec // Self-referencing to pass DefineSSZ calls through (API trick)
buf [32]byte // Integer conversion buffer
err error // Any write error to halt future encoding calls
codec *Codec // Self-referencing to pass DefineSSZ calls through (API trick)

buf [32]byte // Integer conversion buffer
bufInt uint256.Int // Big.Int conversion buffer (not pointer, alloc free)

offset uint32 // Offset tracker for dynamic fields
}
Expand Down Expand Up @@ -135,6 +138,33 @@ func EncodeUint256(enc *Encoder, n *uint256.Int) {
}
}

// EncodeUint256BigInt serializes a big.Ing as uint256.
//
// Note, a nil pointer is serialized as zero.
// Note, an overflow will be silently dropped.
func EncodeUint256BigInt(enc *Encoder, n *big.Int) {
if enc.outWriter != nil {
if enc.err != nil {
return
}
if n != nil {
enc.bufInt.SetFromBig(n)
enc.bufInt.MarshalSSZInto(enc.buf[:32])
_, enc.err = enc.outWriter.Write(enc.buf[:32])
} else {
_, enc.err = enc.outWriter.Write(uint256Zero)
}
} else {
if n != nil {
enc.bufInt.SetFromBig(n)
enc.bufInt.MarshalSSZInto(enc.outBuffer)
} else {
copy(enc.outBuffer, uint256Zero)
}
enc.outBuffer = enc.outBuffer[32:]
}
}

// EncodeStaticBytes serializes a static binary blob.
//
// The blob is passed by pointer to avoid high stack copy costs and a potential
Expand Down
14 changes: 14 additions & 0 deletions hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package ssz
import (
"crypto/sha256"
"encoding/binary"
"math/big"
bitops "math/bits"
"runtime"
"unsafe"
Expand Down Expand Up @@ -90,6 +91,19 @@ func HashUint256(h *Hasher, n *uint256.Int) {
h.insertChunk(buffer, 0)
}

// HashUint256BigInt hashes a big.Int as uint256.
//
// Note, a nil pointer is hashed as zero.
func HashUint256BigInt(h *Hasher, n *big.Int) {
var buffer [32]byte
if n != nil {
var bufint uint256.Int // No pointer, alloc free
bufint.SetFromBig(n)
bufint.MarshalSSZInto(buffer[:])
}
h.insertChunk(buffer, 0)
}

// HashStaticBytes hashes a static binary blob.
//
// The blob is passed by pointer to avoid high stack copy costs and a potential
Expand Down
13 changes: 10 additions & 3 deletions tests/consensus_specs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func TestConsensusSpecs(t *testing.T) {
testConsensusSpecType[*types.Withdrawal](t, "Withdrawal")

// Add some API variations to test different codec implementations
testConsensusSpecType[*types.ExecutionPayloadVariation](t, "ExecutionPayload", "bellatrix")
testConsensusSpecType[*types.HistoricalBatchVariation](t, "HistoricalBatch")
testConsensusSpecType[*types.WithdrawalVariation](t, "Withdrawal")

Expand Down Expand Up @@ -224,6 +225,8 @@ func testConsensusSpecType[T newableObject[U], U any](t *testing.T, kind string,
// BenchmarkConsensusSpecs iterates over all the (supported) consensus SSZ types and
// runs the encoding/decoding/hashing benchmark round.
func BenchmarkConsensusSpecs(b *testing.B) {
benchmarkConsensusSpecType[*types.ExecutionPayloadVariation](b, "bellatrix", "ExecutionPayload")

benchmarkConsensusSpecType[*types.AggregateAndProof](b, "deneb", "AggregateAndProof")
benchmarkConsensusSpecType[*types.Attestation](b, "deneb", "Attestation")
benchmarkConsensusSpecType[*types.AttestationData](b, "deneb", "AttestationData")
Expand Down Expand Up @@ -440,9 +443,6 @@ func FuzzConsensusSpecsFork(f *testing.F) {
func FuzzConsensusSpecsHistoricalBatch(f *testing.F) {
fuzzConsensusSpecType[*types.HistoricalBatch](f, "HistoricalBatch")
}
func FuzzConsensusSpecsHistoricalBatchVariation(f *testing.F) {
fuzzConsensusSpecType[*types.HistoricalBatchVariation](f, "HistoricalBatch")
}
func FuzzConsensusSpecsHistoricalSummary(f *testing.F) {
fuzzConsensusSpecType[*types.HistoricalSummary](f, "HistoricalSummary")
}
Expand Down Expand Up @@ -479,6 +479,13 @@ func FuzzConsensusSpecsVoluntaryExit(f *testing.F) {
func FuzzConsensusSpecsWithdrawal(f *testing.F) {
fuzzConsensusSpecType[*types.Withdrawal](f, "Withdrawal")
}

func FuzzConsensusSpecsExecutionPayloadVariation(f *testing.F) {
fuzzConsensusSpecType[*types.ExecutionPayloadVariation](f, "ExecutionPayload")
}
func FuzzConsensusSpecsHistoricalBatchVariation(f *testing.F) {
fuzzConsensusSpecType[*types.HistoricalBatchVariation](f, "HistoricalBatch")
}
func FuzzConsensusSpecsWithdrawalVariation(f *testing.F) {
fuzzConsensusSpecType[*types.WithdrawalVariation](f, "Withdrawal")
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions tests/testtypes/consensus-spec-tests/types_variation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

package consensus_spec_tests

import (
"math/big"
)

//go:generate go run ../../../cmd/sszgen -type WithdrawalVariation -out gen_withdrawal_variation_ssz.go
//go:generate go run ../../../cmd/sszgen -type HistoricalBatchVariation -out gen_historical_batch_variation_ssz.go
//go:generate go run ../../../cmd/sszgen -type ExecutionPayloadVariation -out gen_execution_payload_variation_ssz.go

type WithdrawalVariation struct {
Index uint64
Expand All @@ -18,3 +23,20 @@ type HistoricalBatchVariation struct {
BlockRoots [8192]Hash
StateRoots []Hash `ssz-size:"8192"` // Static array defined via ssz-size tag
}

type ExecutionPayloadVariation struct {
ParentHash Hash
FeeRecipient Address
StateRoot Hash
ReceiptsRoot Hash
LogsBloom LogsBloom
PrevRandao Hash
BlockNumber uint64
GasLimit uint64
GasUsed uint64
Timestamp uint64
ExtraData []byte `ssz-max:"32"`
BaseFeePerGas *big.Int // Big.Int instead of the recommended uint256.Int
BlockHash Hash
Transactions [][]byte `ssz-max:"1048576,1073741824"`
}

0 comments on commit adeb27c

Please sign in to comment.