mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +00:00
feat: types.HeaderHooks JSON round-trip support (#94)
## Why this should be merged JSON equivalent of #89. ## How this works The check for registered extras, previously used in `{En,De}codeRLP()` methods is abstracted into a `Header.hooks() HeaderHooks` method that either returns (a) an instance of the registered type or (b) a `NOOPHeaderHooks` if no registration was performed. This is then used for all hooks, new (JSON) and old (RLP). ## How this was tested Extension of existing unit tests.
This commit is contained in:
parent
44f23c8869
commit
4575ced555
4 changed files with 126 additions and 41 deletions
|
|
@ -60,6 +60,7 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
||||||
|
//go:generate go run ../../libevm/cmd/internalise -file gen_header_json.go Header.MarshalJSON Header.UnmarshalJSON
|
||||||
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
|
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
|
||||||
//go:generate go run ../../libevm/cmd/internalise -file gen_header_rlp.go Header.EncodeRLP
|
//go:generate go run ../../libevm/cmd/internalise -file gen_header_rlp.go Header.EncodeRLP
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
|
@ -27,40 +28,50 @@ import (
|
||||||
// HeaderHooks are required for all types registered with [RegisterExtras] for
|
// HeaderHooks are required for all types registered with [RegisterExtras] for
|
||||||
// [Header] payloads.
|
// [Header] payloads.
|
||||||
type HeaderHooks interface {
|
type HeaderHooks interface {
|
||||||
|
MarshalJSON(*Header) ([]byte, error) //nolint:govet // Type-specific override hook
|
||||||
|
UnmarshalJSON(*Header, []byte) error //nolint:govet
|
||||||
EncodeRLP(*Header, io.Writer) error
|
EncodeRLP(*Header, io.Writer) error
|
||||||
DecodeRLP(*Header, *rlp.Stream) error
|
DecodeRLP(*Header, *rlp.Stream) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hooks returns the Header's registered HeaderHooks, if any, otherwise a
|
||||||
|
// [NOOPHeaderHooks] suitable for running default behaviour.
|
||||||
|
func (h *Header) hooks() HeaderHooks {
|
||||||
|
if r := registeredExtras; r.Registered() {
|
||||||
|
return r.Get().hooks.hooksFromHeader(h)
|
||||||
|
}
|
||||||
|
return new(NOOPHeaderHooks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExtraPayloads[HPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
|
||||||
|
return e.Header.Get(h)
|
||||||
|
}
|
||||||
|
|
||||||
var _ interface {
|
var _ interface {
|
||||||
rlp.Encoder
|
rlp.Encoder
|
||||||
rlp.Decoder
|
rlp.Decoder
|
||||||
|
json.Marshaler
|
||||||
|
json.Unmarshaler
|
||||||
} = (*Header)(nil)
|
} = (*Header)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON implements the [json.Marshaler] interface.
|
||||||
|
func (h *Header) MarshalJSON() ([]byte, error) {
|
||||||
|
return h.hooks().MarshalJSON(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the [json.Unmarshaler] interface.
|
||||||
|
func (h *Header) UnmarshalJSON(b []byte) error {
|
||||||
|
return h.hooks().UnmarshalJSON(h, b)
|
||||||
|
}
|
||||||
|
|
||||||
// EncodeRLP implements the [rlp.Encoder] interface.
|
// EncodeRLP implements the [rlp.Encoder] interface.
|
||||||
func (h *Header) EncodeRLP(w io.Writer) error {
|
func (h *Header) EncodeRLP(w io.Writer) error {
|
||||||
if r := registeredExtras; r.Registered() {
|
return h.hooks().EncodeRLP(h, w)
|
||||||
return r.Get().hooks.hooksFromHeader(h).EncodeRLP(h, w)
|
|
||||||
}
|
|
||||||
return h.encodeRLP(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeHeaderRLPDirectly bypasses the [Header.DecodeRLP] method to avoid
|
|
||||||
// infinite recursion.
|
|
||||||
func decodeHeaderRLPDirectly(h *Header, s *rlp.Stream) error {
|
|
||||||
type withoutMethods Header
|
|
||||||
return s.Decode((*withoutMethods)(h))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeRLP implements the [rlp.Decoder] interface.
|
// DecodeRLP implements the [rlp.Decoder] interface.
|
||||||
func (h *Header) DecodeRLP(s *rlp.Stream) error {
|
func (h *Header) DecodeRLP(s *rlp.Stream) error {
|
||||||
if r := registeredExtras; r.Registered() {
|
return h.hooks().DecodeRLP(h, s)
|
||||||
return r.Get().hooks.hooksFromHeader(h).DecodeRLP(h, s)
|
|
||||||
}
|
|
||||||
return decodeHeaderRLPDirectly(h, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ExtraPayloads[HPtr, SA]) hooksFromHeader(h *Header) HeaderHooks {
|
|
||||||
return e.Header.Get(h)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Header) extraPayload() *pseudo.Type {
|
func (h *Header) extraPayload() *pseudo.Type {
|
||||||
|
|
@ -81,10 +92,19 @@ type NOOPHeaderHooks struct{}
|
||||||
|
|
||||||
var _ HeaderHooks = (*NOOPHeaderHooks)(nil)
|
var _ HeaderHooks = (*NOOPHeaderHooks)(nil)
|
||||||
|
|
||||||
|
func (*NOOPHeaderHooks) MarshalJSON(h *Header) ([]byte, error) { //nolint:govet
|
||||||
|
return h.marshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NOOPHeaderHooks) UnmarshalJSON(h *Header, b []byte) error { //nolint:govet
|
||||||
|
return h.unmarshalJSON(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (*NOOPHeaderHooks) EncodeRLP(h *Header, w io.Writer) error {
|
func (*NOOPHeaderHooks) EncodeRLP(h *Header, w io.Writer) error {
|
||||||
return h.encodeRLP(w)
|
return h.encodeRLP(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
|
func (*NOOPHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
|
||||||
return decodeHeaderRLPDirectly(h, s)
|
type withoutMethods Header
|
||||||
|
return s.Decode((*withoutMethods)(h))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@
|
||||||
package types_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -31,19 +33,33 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type stubHeaderHooks struct {
|
type stubHeaderHooks struct {
|
||||||
rlpSuffix []byte
|
suffix []byte
|
||||||
gotRawRLPToDecode []byte
|
gotRawJSONToUnmarshal, gotRawRLPToDecode []byte
|
||||||
setHeaderToOnDecode Header
|
setHeaderToOnUnmarshalOrDecode Header
|
||||||
|
|
||||||
errEncode, errDecode error
|
errMarshal, errUnmarshal, errEncode, errDecode error
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeHeaderJSON(h *Header, suffix []byte) []byte {
|
||||||
|
return []byte(fmt.Sprintf(`"%#x:%#x"`, h.ParentHash, suffix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func fakeHeaderRLP(h *Header, suffix []byte) []byte {
|
func fakeHeaderRLP(h *Header, suffix []byte) []byte {
|
||||||
return append(crypto.Keccak256(h.ParentHash[:]), suffix...)
|
return append(crypto.Keccak256(h.ParentHash[:]), suffix...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hh *stubHeaderHooks) MarshalJSON(h *Header) ([]byte, error) { //nolint:govet
|
||||||
|
return fakeHeaderJSON(h, hh.suffix), hh.errMarshal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hh *stubHeaderHooks) UnmarshalJSON(h *Header, b []byte) error { //nolint:govet
|
||||||
|
hh.gotRawJSONToUnmarshal = b
|
||||||
|
*h = hh.setHeaderToOnUnmarshalOrDecode
|
||||||
|
return hh.errUnmarshal
|
||||||
|
}
|
||||||
|
|
||||||
func (hh *stubHeaderHooks) EncodeRLP(h *Header, w io.Writer) error {
|
func (hh *stubHeaderHooks) EncodeRLP(h *Header, w io.Writer) error {
|
||||||
if _, err := w.Write(fakeHeaderRLP(h, hh.rlpSuffix)); err != nil {
|
if _, err := w.Write(fakeHeaderRLP(h, hh.suffix)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return hh.errEncode
|
return hh.errEncode
|
||||||
|
|
@ -55,7 +71,7 @@ func (hh *stubHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
hh.gotRawRLPToDecode = r
|
hh.gotRawRLPToDecode = r
|
||||||
*h = hh.setHeaderToOnDecode
|
*h = hh.setHeaderToOnUnmarshalOrDecode
|
||||||
return hh.errDecode
|
return hh.errDecode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,14 +82,36 @@ func TestHeaderHooks(t *testing.T) {
|
||||||
extras := RegisterExtras[stubHeaderHooks, *stubHeaderHooks, struct{}]()
|
extras := RegisterExtras[stubHeaderHooks, *stubHeaderHooks, struct{}]()
|
||||||
rng := ethtest.NewPseudoRand(13579)
|
rng := ethtest.NewPseudoRand(13579)
|
||||||
|
|
||||||
t.Run("EncodeRLP", func(t *testing.T) {
|
suffix := rng.Bytes(8)
|
||||||
suffix := rng.Bytes(8)
|
hdr := &Header{
|
||||||
|
ParentHash: rng.Hash(),
|
||||||
|
}
|
||||||
|
extras.Header.Get(hdr).suffix = append([]byte{}, suffix...)
|
||||||
|
|
||||||
hdr := &Header{
|
t.Run("MarshalJSON", func(t *testing.T) {
|
||||||
ParentHash: rng.Hash(),
|
got, err := json.Marshal(hdr)
|
||||||
|
require.NoError(t, err, "json.Marshal(%T)", hdr)
|
||||||
|
assert.Equal(t, fakeHeaderJSON(hdr, suffix), got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UnmarshalJSON", func(t *testing.T) {
|
||||||
|
hdr := new(Header)
|
||||||
|
stub := &stubHeaderHooks{
|
||||||
|
setHeaderToOnUnmarshalOrDecode: Header{
|
||||||
|
Extra: []byte("can you solve this puzzle? 0xbda01b6cf56c303bd3f581599c0d5c0b"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
extras.Header.Get(hdr).rlpSuffix = append([]byte{}, suffix...)
|
extras.Header.Set(hdr, stub)
|
||||||
|
|
||||||
|
input := fmt.Sprintf("%q", "hello, JSON world")
|
||||||
|
err := json.Unmarshal([]byte(input), hdr)
|
||||||
|
require.NoErrorf(t, err, "json.Unmarshal()")
|
||||||
|
|
||||||
|
assert.Equal(t, input, string(stub.gotRawJSONToUnmarshal), "raw JSON received by hook")
|
||||||
|
assert.Equal(t, &stub.setHeaderToOnUnmarshalOrDecode, hdr, "%T after JSON unmarshalling with hook", hdr)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EncodeRLP", func(t *testing.T) {
|
||||||
got, err := rlp.EncodeToBytes(hdr)
|
got, err := rlp.EncodeToBytes(hdr)
|
||||||
require.NoError(t, err, "rlp.EncodeToBytes(%T)", hdr)
|
require.NoError(t, err, "rlp.EncodeToBytes(%T)", hdr)
|
||||||
assert.Equal(t, fakeHeaderRLP(hdr, suffix), got)
|
assert.Equal(t, fakeHeaderRLP(hdr, suffix), got)
|
||||||
|
|
@ -85,7 +123,7 @@ func TestHeaderHooks(t *testing.T) {
|
||||||
|
|
||||||
hdr := new(Header)
|
hdr := new(Header)
|
||||||
stub := &stubHeaderHooks{
|
stub := &stubHeaderHooks{
|
||||||
setHeaderToOnDecode: Header{
|
setHeaderToOnUnmarshalOrDecode: Header{
|
||||||
Extra: []byte("arr4n was here"),
|
Extra: []byte("arr4n was here"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -94,20 +132,46 @@ func TestHeaderHooks(t *testing.T) {
|
||||||
require.NoErrorf(t, err, "rlp.DecodeBytes(%#x)", input)
|
require.NoErrorf(t, err, "rlp.DecodeBytes(%#x)", input)
|
||||||
|
|
||||||
assert.Equal(t, input, stub.gotRawRLPToDecode, "raw RLP received by hooks")
|
assert.Equal(t, input, stub.gotRawRLPToDecode, "raw RLP received by hooks")
|
||||||
assert.Equalf(t, &stub.setHeaderToOnDecode, hdr, "%T after RLP decoding with hook", hdr)
|
assert.Equalf(t, &stub.setHeaderToOnUnmarshalOrDecode, hdr, "%T after RLP decoding with hook", hdr)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("error_propagation", func(t *testing.T) {
|
t.Run("error_propagation", func(t *testing.T) {
|
||||||
|
errMarshal := errors.New("whoops")
|
||||||
|
errUnmarshal := errors.New("is it broken?")
|
||||||
errEncode := errors.New("uh oh")
|
errEncode := errors.New("uh oh")
|
||||||
errDecode := errors.New("something bad happened")
|
errDecode := errors.New("something bad happened")
|
||||||
|
|
||||||
hdr := new(Header)
|
hdr := new(Header)
|
||||||
extras.Header.Set(hdr, &stubHeaderHooks{
|
setStub := func() {
|
||||||
errEncode: errEncode,
|
extras.Header.Set(hdr, &stubHeaderHooks{
|
||||||
errDecode: errDecode,
|
errMarshal: errMarshal,
|
||||||
})
|
errUnmarshal: errUnmarshal,
|
||||||
|
errEncode: errEncode,
|
||||||
|
errDecode: errDecode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, errEncode, rlp.Encode(io.Discard, hdr), "via rlp.Encode()")
|
setStub()
|
||||||
assert.Equal(t, errDecode, rlp.DecodeBytes([]byte{0}, hdr), "via rlp.DecodeBytes()")
|
// The { } blocks are defensive, avoiding accidentally having the wrong
|
||||||
|
// error checked in a future refactor. The verbosity is acceptable for
|
||||||
|
// clarity in tests.
|
||||||
|
{
|
||||||
|
_, err := json.Marshal(hdr)
|
||||||
|
assert.ErrorIs(t, err, errMarshal, "via json.Marshal()") //nolint:testifylint // require is inappropriate here as we wish to keep going
|
||||||
|
}
|
||||||
|
{
|
||||||
|
err := json.Unmarshal([]byte("{}"), hdr)
|
||||||
|
assert.Equal(t, errUnmarshal, err, "via json.Unmarshal()")
|
||||||
|
}
|
||||||
|
|
||||||
|
setStub() // [stubHeaderHooks] completely overrides the Header
|
||||||
|
{
|
||||||
|
err := rlp.Encode(io.Discard, hdr)
|
||||||
|
assert.Equal(t, errEncode, err, "via rlp.Encode()")
|
||||||
|
}
|
||||||
|
{
|
||||||
|
err := rlp.DecodeBytes([]byte{0}, hdr)
|
||||||
|
assert.Equal(t, errDecode, err, "via rlp.DecodeBytes()")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import (
|
||||||
var _ = (*headerMarshaling)(nil)
|
var _ = (*headerMarshaling)(nil)
|
||||||
|
|
||||||
// MarshalJSON marshals as JSON.
|
// MarshalJSON marshals as JSON.
|
||||||
func (h Header) MarshalJSON() ([]byte, error) {
|
func (h Header) marshalJSON() ([]byte, error) {
|
||||||
type Header struct {
|
type Header struct {
|
||||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||||
|
|
@ -64,7 +64,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals from JSON.
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
func (h *Header) unmarshalJSON(input []byte) error {
|
||||||
type Header struct {
|
type Header struct {
|
||||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue