mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-23 07:04:35 +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 ../../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 ../../libevm/cmd/internalise -file gen_header_rlp.go Header.EncodeRLP
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
|
|
@ -27,40 +28,50 @@ import (
|
|||
// HeaderHooks are required for all types registered with [RegisterExtras] for
|
||||
// [Header] payloads.
|
||||
type HeaderHooks interface {
|
||||
MarshalJSON(*Header) ([]byte, error) //nolint:govet // Type-specific override hook
|
||||
UnmarshalJSON(*Header, []byte) error //nolint:govet
|
||||
EncodeRLP(*Header, io.Writer) 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 {
|
||||
rlp.Encoder
|
||||
rlp.Decoder
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
} = (*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.
|
||||
func (h *Header) EncodeRLP(w io.Writer) error {
|
||||
if r := registeredExtras; r.Registered() {
|
||||
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))
|
||||
return h.hooks().EncodeRLP(h, w)
|
||||
}
|
||||
|
||||
// DecodeRLP implements the [rlp.Decoder] interface.
|
||||
func (h *Header) DecodeRLP(s *rlp.Stream) error {
|
||||
if r := registeredExtras; r.Registered() {
|
||||
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)
|
||||
return h.hooks().DecodeRLP(h, s)
|
||||
}
|
||||
|
||||
func (h *Header) extraPayload() *pseudo.Type {
|
||||
|
|
@ -81,10 +92,19 @@ type NOOPHeaderHooks struct{}
|
|||
|
||||
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 {
|
||||
return h.encodeRLP(w)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
|
|
@ -31,19 +33,33 @@ import (
|
|||
)
|
||||
|
||||
type stubHeaderHooks struct {
|
||||
rlpSuffix []byte
|
||||
gotRawRLPToDecode []byte
|
||||
setHeaderToOnDecode Header
|
||||
suffix []byte
|
||||
gotRawJSONToUnmarshal, gotRawRLPToDecode []byte
|
||||
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 {
|
||||
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 {
|
||||
if _, err := w.Write(fakeHeaderRLP(h, hh.rlpSuffix)); err != nil {
|
||||
if _, err := w.Write(fakeHeaderRLP(h, hh.suffix)); err != nil {
|
||||
return err
|
||||
}
|
||||
return hh.errEncode
|
||||
|
|
@ -55,7 +71,7 @@ func (hh *stubHeaderHooks) DecodeRLP(h *Header, s *rlp.Stream) error {
|
|||
return err
|
||||
}
|
||||
hh.gotRawRLPToDecode = r
|
||||
*h = hh.setHeaderToOnDecode
|
||||
*h = hh.setHeaderToOnUnmarshalOrDecode
|
||||
return hh.errDecode
|
||||
}
|
||||
|
||||
|
|
@ -66,14 +82,36 @@ func TestHeaderHooks(t *testing.T) {
|
|||
extras := RegisterExtras[stubHeaderHooks, *stubHeaderHooks, struct{}]()
|
||||
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{
|
||||
ParentHash: rng.Hash(),
|
||||
t.Run("MarshalJSON", func(t *testing.T) {
|
||||
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)
|
||||
require.NoError(t, err, "rlp.EncodeToBytes(%T)", hdr)
|
||||
assert.Equal(t, fakeHeaderRLP(hdr, suffix), got)
|
||||
|
|
@ -85,7 +123,7 @@ func TestHeaderHooks(t *testing.T) {
|
|||
|
||||
hdr := new(Header)
|
||||
stub := &stubHeaderHooks{
|
||||
setHeaderToOnDecode: Header{
|
||||
setHeaderToOnUnmarshalOrDecode: Header{
|
||||
Extra: []byte("arr4n was here"),
|
||||
},
|
||||
}
|
||||
|
|
@ -94,20 +132,46 @@ func TestHeaderHooks(t *testing.T) {
|
|||
require.NoErrorf(t, err, "rlp.DecodeBytes(%#x)", input)
|
||||
|
||||
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) {
|
||||
errMarshal := errors.New("whoops")
|
||||
errUnmarshal := errors.New("is it broken?")
|
||||
errEncode := errors.New("uh oh")
|
||||
errDecode := errors.New("something bad happened")
|
||||
|
||||
hdr := new(Header)
|
||||
extras.Header.Set(hdr, &stubHeaderHooks{
|
||||
errEncode: errEncode,
|
||||
errDecode: errDecode,
|
||||
})
|
||||
setStub := func() {
|
||||
extras.Header.Set(hdr, &stubHeaderHooks{
|
||||
errMarshal: errMarshal,
|
||||
errUnmarshal: errUnmarshal,
|
||||
errEncode: errEncode,
|
||||
errDecode: errDecode,
|
||||
})
|
||||
}
|
||||
|
||||
assert.Equal(t, errEncode, rlp.Encode(io.Discard, hdr), "via rlp.Encode()")
|
||||
assert.Equal(t, errDecode, rlp.DecodeBytes([]byte{0}, hdr), "via rlp.DecodeBytes()")
|
||||
setStub()
|
||||
// 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)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (h Header) MarshalJSON() ([]byte, error) {
|
||||
func (h Header) marshalJSON() ([]byte, error) {
|
||||
type Header struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
|
|
@ -64,7 +64,7 @@ func (h Header) MarshalJSON() ([]byte, error) {
|
|||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
func (h *Header) unmarshalJSON(input []byte) error {
|
||||
type Header struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
|
|
|
|||
Loading…
Reference in a new issue