diff --git a/beacon/engine/codec_bapl.go b/beacon/engine/codec_bapl.go
index d07882710c..9db390d48a 100644
--- a/beacon/engine/codec_bapl.go
+++ b/beacon/engine/codec_bapl.go
@@ -16,166 +16,54 @@
package engine
-import "encoding/json"
-
-// estimateBlobAndProofV1Size returns a rough estimate of the JSON size for a BlobAndProofV1.
-func estimateBlobAndProofV1Size(item *BlobAndProofV1) int {
- if item == nil {
- return 4
- }
- return len(item.Blob)*2 + len(item.Proof)*2 + 30
-}
-
-// marshalBlobAndProofV1 writes a BlobAndProofV1 as JSON and appends it to buf.
-func marshalBlobAndProofV1(buf []byte, item *BlobAndProofV1) []byte {
- if item == nil {
- return append(buf, "null"...)
- }
- buf = append(buf, `{"blob":`...)
- buf = writeHexBytes(buf, item.Blob)
-
- buf = append(buf, `,"proof":`...)
- buf = writeHexBytes(buf, item.Proof)
-
- buf = append(buf, '}')
- return buf
-}
-
-// estimateBlobAndProofV2Size returns a rough estimate of the JSON size for a BlobAndProofV2.
-func estimateBlobAndProofV2Size(item *BlobAndProofV2) int {
- if item == nil {
- return 4
- }
- size := len(item.Blob)*2 + 30
- for _, proof := range item.CellProofs {
- size += len(proof)*2 + 6
- }
- return size
-}
-
-// marshalBlobAndProofV2 writes a BlobAndProofV2 as JSON and appends it to buf.
-func marshalBlobAndProofV2(buf []byte, item *BlobAndProofV2) []byte {
- if item == nil {
- return append(buf, "null"...)
- }
- buf = append(buf, `{"blob":`...)
- buf = writeHexBytes(buf, item.Blob)
-
- buf = append(buf, `,"proofs":`...)
- buf = marshalHexBytesArray(buf, item.CellProofs)
-
- buf = append(buf, '}')
- return buf
-}
+import (
+ jsonw "github.com/fjl/jsonw"
+)
// MarshalJSON implements json.Marshaler.
func (list BlobAndProofListV1) MarshalJSON() ([]byte, error) {
- // Estimate buffer size.
- size := 2
- for _, item := range list {
- size += estimateBlobAndProofV1Size(item) + 1
- }
- buf := make([]byte, 0, size)
-
- // Write the array elements to the buffer.
- buf = append(buf, '[')
- for i, item := range list {
- if i > 0 {
- buf = append(buf, ',')
+ var b jsonw.Buffer
+ b.Array(func() {
+ for _, item := range list {
+ marshalBlobAndProofV1(&b, item)
}
- buf = marshalBlobAndProofV1(buf, item)
- }
- buf = append(buf, ']')
- return buf, nil
+ })
+ return b.Output(), nil
}
-// UnmarshalJSON implements json.Unmarshaler.
-func (list *BlobAndProofListV1) UnmarshalJSON(input []byte) error {
- if isJSONNull(input) {
- *list = nil
- return nil
+func marshalBlobAndProofV1(b *jsonw.Buffer, item *BlobAndProofV1) {
+ if item == nil {
+ b.Null()
+ } else {
+ b.Object(func() {
+ b.Key("blob")
+ b.HexBytes(item.Blob)
+ b.Key("proof")
+ b.HexBytes(item.Proof)
+ })
}
- items := make(BlobAndProofListV1, 0)
- if err := decodeJSONArray(input, func(value json.RawMessage) error {
- if isJSONNull(value) {
- items = append(items, nil)
- return nil
- }
- item := new(BlobAndProofV1)
- if err := decodeJSONObject(value, func(key string, value json.RawMessage) error {
- switch key {
- case "blob":
- return item.Blob.UnmarshalJSON(value)
- case "proof":
- return item.Proof.UnmarshalJSON(value)
- }
- return nil
- }); err != nil {
- return err
- }
- items = append(items, item)
- return nil
- }); err != nil {
- return err
- }
- *list = items
- return nil
}
// MarshalJSON implements json.Marshaler.
func (list BlobAndProofListV2) MarshalJSON() ([]byte, error) {
- // Estimate buffer size.
- size := 2
- for _, item := range list {
- size += estimateBlobAndProofV2Size(item) + 1
- }
- buf := make([]byte, 0, size)
-
- // Write the array elements to the buffer.
- buf = append(buf, '[')
- for i, item := range list {
- if i > 0 {
- buf = append(buf, ',')
+ var b jsonw.Buffer
+ b.Array(func() {
+ for _, item := range list {
+ marshalBlobAndProofV2(&b, item)
}
- buf = marshalBlobAndProofV2(buf, item)
- }
- buf = append(buf, ']')
- return buf, nil
+ })
+ return b.Output(), nil
}
-// UnmarshalJSON implements json.Unmarshaler.
-func (list *BlobAndProofListV2) UnmarshalJSON(input []byte) error {
- if isJSONNull(input) {
- *list = nil
- return nil
+func marshalBlobAndProofV2(b *jsonw.Buffer, item *BlobAndProofV2) {
+ if item == nil {
+ b.Null()
+ } else {
+ b.Object(func() {
+ b.Key("blob")
+ b.HexBytes(item.Blob)
+ b.Key("proofs")
+ appendHexBytesArray(b, item.CellProofs)
+ })
}
- items := make(BlobAndProofListV2, 0)
- if err := decodeJSONArray(input, func(value json.RawMessage) error {
- if isJSONNull(value) {
- items = append(items, nil)
- return nil
- }
- item := new(BlobAndProofV2)
- if err := decodeJSONObject(value, func(key string, value json.RawMessage) error {
- switch key {
- case "blob":
- return item.Blob.UnmarshalJSON(value)
- case "proofs":
- proofs, err := unmarshalHexBytesArray(value)
- if err != nil {
- return err
- }
- item.CellProofs = proofs
- }
- return nil
- }); err != nil {
- return err
- }
- items = append(items, item)
- return nil
- }); err != nil {
- return err
- }
- *list = items
- return nil
}
diff --git a/beacon/engine/codec_bapl_test.go b/beacon/engine/codec_bapl_test.go
deleted file mode 100644
index 6a94c4d530..0000000000
--- a/beacon/engine/codec_bapl_test.go
+++ /dev/null
@@ -1,329 +0,0 @@
-// Copyright 2026 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "reflect"
- "testing"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-// canonicalBlobAndProofV1 is a reference type for BlobAndProofV1 that uses
-// standard json.Marshal (no custom MarshalJSON).
-type canonicalBlobAndProofV1 struct {
- Blob hexutil.Bytes `json:"blob"`
- Proof hexutil.Bytes `json:"proof"`
-}
-
-// canonicalBlobAndProofV2 is a reference type for BlobAndProofV2 that uses
-// standard json.Marshal (no custom MarshalJSON).
-type canonicalBlobAndProofV2 struct {
- Blob hexutil.Bytes `json:"blob"`
- CellProofs []hexutil.Bytes `json:"proofs"`
-}
-
-func toCanonicalBlobAndProofListV1(list BlobAndProofListV1) []*canonicalBlobAndProofV1 {
- canonical := make([]*canonicalBlobAndProofV1, len(list))
- for i, item := range list {
- if item == nil {
- continue
- }
- canonical[i] = &canonicalBlobAndProofV1{
- Blob: item.Blob,
- Proof: item.Proof,
- }
- }
- return canonical
-}
-
-func toCanonicalBlobAndProofListV2(list BlobAndProofListV2) []*canonicalBlobAndProofV2 {
- canonical := make([]*canonicalBlobAndProofV2, len(list))
- for i, item := range list {
- if item == nil {
- continue
- }
- canonical[i] = &canonicalBlobAndProofV2{
- Blob: item.Blob,
- CellProofs: item.CellProofs,
- }
- }
- return canonical
-}
-
-func TestBlobAndProofListV1MarshalJSON(t *testing.T) {
- tests := []struct {
- name string
- list BlobAndProofListV1
- }{
- {
- name: "multiple items",
- list: BlobAndProofListV1{
- {
- Blob: hexutil.Bytes{0x01, 0x02},
- Proof: hexutil.Bytes{0x03, 0x04},
- },
- {
- Blob: hexutil.Bytes{},
- Proof: hexutil.Bytes{0x05},
- },
- },
- },
- {
- name: "nil item",
- list: BlobAndProofListV1{
- nil,
- {
- Blob: hexutil.Bytes{0xaa},
- Proof: hexutil.Bytes{0xbb},
- },
- },
- },
- {
- name: "empty list",
- list: BlobAndProofListV1{},
- },
- {
- name: "nil list",
- list: nil,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.list.MarshalJSON()
- if err != nil {
- t.Fatalf("MarshalJSON error: %v", err)
- }
- want, err := json.Marshal(toCanonicalBlobAndProofListV1(tt.list))
- if err != nil {
- t.Fatalf("canonical marshal error: %v", err)
- }
- if !bytes.Equal(compactJSON(got), compactJSON(want)) {
- t.Errorf("JSON mismatch\ngot: %s\nwant: %s", got, want)
- }
- })
- }
-}
-
-func TestBlobAndProofListV1UnmarshalJSON(t *testing.T) {
- tests := []struct {
- name string
- json string
- want BlobAndProofListV1
- }{
- {
- name: "multiple items",
- json: `[{"blob":"0x0102","proof":"0x0304"},{"blob":"0x","proof":"0x05"}]`,
- want: BlobAndProofListV1{
- {
- Blob: hexutil.Bytes{0x01, 0x02},
- Proof: hexutil.Bytes{0x03, 0x04},
- },
- {
- Blob: hexutil.Bytes{},
- Proof: hexutil.Bytes{0x05},
- },
- },
- },
- {
- name: "nil item",
- json: `[null,{"blob":"0xaa","proof":"0xbb"}]`,
- want: BlobAndProofListV1{
- nil,
- {
- Blob: hexutil.Bytes{0xaa},
- Proof: hexutil.Bytes{0xbb},
- },
- },
- },
- {
- name: "null list",
- json: `null`,
- want: nil,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var got BlobAndProofListV1
- if err := got.UnmarshalJSON([]byte(tt.json)); err != nil {
- t.Fatalf("UnmarshalJSON error: %v", err)
- }
- if !reflect.DeepEqual(got, tt.want) {
- t.Fatalf("decoded mismatch\ngot: %#v\nwant: %#v", got, tt.want)
- }
- })
- }
-}
-
-func TestBlobAndProofListV2MarshalJSON(t *testing.T) {
- tests := []struct {
- name string
- list BlobAndProofListV2
- }{
- {
- name: "multiple items",
- list: BlobAndProofListV2{
- {
- Blob: hexutil.Bytes{0x01, 0x02},
- CellProofs: []hexutil.Bytes{{0x03, 0x04}, {0x05}},
- },
- {
- Blob: hexutil.Bytes{},
- CellProofs: []hexutil.Bytes{},
- },
- },
- },
- {
- name: "nil item",
- list: BlobAndProofListV2{
- nil,
- {
- Blob: hexutil.Bytes{0xaa},
- CellProofs: []hexutil.Bytes{{0xbb}},
- },
- },
- },
- {
- name: "nil proofs slice",
- list: BlobAndProofListV2{
- {
- Blob: hexutil.Bytes{0xcc},
- CellProofs: nil,
- },
- },
- },
- {
- name: "empty list",
- list: BlobAndProofListV2{},
- },
- {
- name: "nil list",
- list: nil,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.list.MarshalJSON()
- if err != nil {
- t.Fatalf("MarshalJSON error: %v", err)
- }
- want, err := json.Marshal(toCanonicalBlobAndProofListV2(tt.list))
- if err != nil {
- t.Fatalf("canonical marshal error: %v", err)
- }
- if !bytes.Equal(compactJSON(got), compactJSON(want)) {
- t.Errorf("JSON mismatch\ngot: %s\nwant: %s", got, want)
- }
- })
- }
-}
-
-func TestBlobAndProofListV2UnmarshalJSON(t *testing.T) {
- tests := []struct {
- name string
- json string
- want BlobAndProofListV2
- }{
- {
- name: "multiple items",
- json: `[{"blob":"0x0102","proofs":["0x0304","0x05"]},{"blob":"0x","proofs":[]}]`,
- want: BlobAndProofListV2{
- {
- Blob: hexutil.Bytes{0x01, 0x02},
- CellProofs: []hexutil.Bytes{{0x03, 0x04}, {0x05}},
- },
- {
- Blob: hexutil.Bytes{},
- CellProofs: []hexutil.Bytes{},
- },
- },
- },
- {
- name: "nil item and nil proofs",
- json: `[null,{"blob":"0xcc","proofs":null}]`,
- want: BlobAndProofListV2{
- nil,
- {
- Blob: hexutil.Bytes{0xcc},
- CellProofs: nil,
- },
- },
- },
- {
- name: "null list",
- json: `null`,
- want: nil,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var got BlobAndProofListV2
- if err := got.UnmarshalJSON([]byte(tt.json)); err != nil {
- t.Fatalf("UnmarshalJSON error: %v", err)
- }
- if !reflect.DeepEqual(got, tt.want) {
- t.Fatalf("decoded mismatch\ngot: %#v\nwant: %#v", got, tt.want)
- }
- })
- }
-}
-
-func TestBlobAndProofFieldCoverage(t *testing.T) {
- tests := []struct {
- name string
- typ reflect.Type
- expected []string
- }{
- {
- name: "BlobAndProofV1",
- typ: reflect.TypeOf(BlobAndProofV1{}),
- expected: []string{
- "Blob",
- "Proof",
- },
- },
- {
- name: "BlobAndProofV2",
- typ: reflect.TypeOf(BlobAndProofV2{}),
- expected: []string{
- "Blob",
- "CellProofs",
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if tt.typ.NumField() != len(tt.expected) {
- t.Fatalf("%s has %d fields, expected %d; update marshal_bap_list.go",
- tt.name, tt.typ.NumField(), len(tt.expected))
- }
- for i, name := range tt.expected {
- if tt.typ.Field(i).Name != name {
- t.Errorf("field %d: got %q, want %q; update marshal_bap_list.go",
- i, tt.typ.Field(i).Name, name)
- }
- }
- })
- }
-}
diff --git a/beacon/engine/codec_epe.go b/beacon/engine/codec_epe.go
index 985a9485a9..98460e6ef0 100644
--- a/beacon/engine/codec_epe.go
+++ b/beacon/engine/codec_epe.go
@@ -19,61 +19,25 @@ package engine
import (
"encoding/json"
"errors"
- "math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
+ jsonw "github.com/fjl/jsonw"
)
-// estimateBlobsBundleSize returns a rough estimate of the JSON size for a BlobsBundle.
-func estimateBlobsBundleSize(b *BlobsBundle) int {
- size := 80 // JSON structure overhead
- for _, blob := range b.Blobs {
- size += len(blob)*2 + 6
- }
- for _, c := range b.Commitments {
- size += len(c)*2 + 6
- }
- for _, p := range b.Proofs {
- size += len(p)*2 + 6
- }
- return size
-}
-
// marshalBlobsBundle writes BlobsBundle as JSON and appends it to buf.
-func marshalBlobsBundle(buf []byte, b *BlobsBundle) []byte {
- buf = append(buf, `{"commitments":`...)
- buf = marshalHexBytesArray(buf, b.Commitments)
-
- buf = append(buf, `,"proofs":`...)
- buf = marshalHexBytesArray(buf, b.Proofs)
-
- buf = append(buf, `,"blobs":`...)
- buf = marshalHexBytesArray(buf, b.Blobs)
-
- buf = append(buf, '}')
- return buf
-}
-
-func unmarshalBlobsBundle(input []byte) (*BlobsBundle, error) {
- if isJSONNull(input) {
- return nil, nil
+func marshalBlobsBundle(b *jsonw.Buffer, bundle *BlobsBundle) {
+ if bundle == nil {
+ b.Null()
+ return
}
- var bundle BlobsBundle
- if err := decodeJSONObject(input, func(key string, value json.RawMessage) error {
- var err error
- switch key {
- case "commitments":
- bundle.Commitments, err = unmarshalHexBytesArray(value)
- case "proofs":
- bundle.Proofs, err = unmarshalHexBytesArray(value)
- case "blobs":
- bundle.Blobs, err = unmarshalHexBytesArray(value)
- }
- return err
- }); err != nil {
- return nil, err
- }
- return &bundle, nil
+ b.Object(func() {
+ b.Key("commitments")
+ appendHexBytesArray(b, bundle.Commitments)
+ b.Key("proofs")
+ appendHexBytesArray(b, bundle.Proofs)
+ b.Key("blobs")
+ appendHexBytesArray(b, bundle.Blobs)
+ })
}
// MarshalJSON implements json.Marshaler.
@@ -82,38 +46,12 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
return nil, errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope")
}
- // Marshal the execution payload using its gencodec MarshalJSON.
+ // Pre-marshal the execution payload using its gencodec MarshalJSON.
payload, err := e.ExecutionPayload.MarshalJSON()
if err != nil {
return nil, err
}
-
- // Marshal the block value.
- blockValue, err := json.Marshal((*hexutil.Big)(e.BlockValue))
- if err != nil {
- return nil, err
- }
-
- // Marshal the execution requests.
- var requests []byte
- if e.Requests != nil {
- hexRequests := make([]hexutil.Bytes, len(e.Requests))
- for i, req := range e.Requests {
- hexRequests[i] = req
- }
- requests, err = json.Marshal(hexRequests)
- if err != nil {
- return nil, err
- }
- }
-
- // Marshal the override.
- override, err := json.Marshal(e.Override)
- if err != nil {
- return nil, err
- }
-
- // Marshal the witness.
+ // Pre-marshal the witness.
var witness []byte
if e.Witness != nil {
witness, err = json.Marshal(e.Witness)
@@ -122,131 +60,40 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
}
}
- // Estimate buffer size.
- size := len(payload) + len(blockValue) + len(requests) + len(override) + len(witness)
- if e.BlobsBundle != nil {
- size += estimateBlobsBundleSize(e.BlobsBundle)
- }
- size += 256 // JSON bloat (keys, braces, commas, etc. and room for growth)
- buf := make([]byte, 0, size)
-
// Write the execution payload to the buffer
- buf = append(buf, `{"executionPayload":`...)
- buf = append(buf, payload...)
-
- // Write the block value to the buffer
- buf = append(buf, `,"blockValue":`...)
- buf = append(buf, blockValue...)
-
- // Write the blobs bundle to the buffer
- buf = append(buf, `,"blobsBundle":`...)
- if e.BlobsBundle != nil {
- buf = marshalBlobsBundle(buf, e.BlobsBundle)
- } else {
- buf = append(buf, "null"...)
- }
-
- // Write the execution requests to the buffer
- buf = append(buf, `,"executionRequests":`...)
- if requests != nil {
- buf = append(buf, requests...)
- } else {
- buf = append(buf, "null"...)
- }
-
- // Write the override to the buffer
- buf = append(buf, `,"shouldOverrideBuilder":`...)
- buf = append(buf, override...)
-
- // Write the witness to the buffer if present
- if witness != nil {
- buf = append(buf, `,"witness":`...)
- buf = append(buf, witness...)
- }
-
- // Close the envelope
- buf = append(buf, '}')
- return buf, nil
-}
-
-// UnmarshalJSON implements json.Unmarshaler.
-func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
- var (
- payloadSeen bool
- blockValueSeen bool
- )
- *e = ExecutionPayloadEnvelope{}
- if err := decodeJSONObject(input, func(key string, value json.RawMessage) error {
- switch key {
- case "executionPayload":
- payloadSeen = true
- if isJSONNull(value) {
- e.ExecutionPayload = nil
- return nil
- }
- var payload ExecutableData
- if err := payload.UnmarshalJSON(value); err != nil {
- return err
- }
- e.ExecutionPayload = &payload
- case "blockValue":
- blockValueSeen = true
- if isJSONNull(value) {
- e.BlockValue = nil
- return nil
- }
- var blockValue hexutil.Big
- if err := blockValue.UnmarshalJSON(value); err != nil {
- return err
- }
- e.BlockValue = (*big.Int)(&blockValue)
- case "blobsBundle":
- bundle, err := unmarshalBlobsBundle(value)
- if err != nil {
- return err
- }
- e.BlobsBundle = bundle
- case "executionRequests":
- requests, err := unmarshalHexBytesArray(value)
- if err != nil {
- return err
- }
- if requests == nil {
- e.Requests = nil
- return nil
- }
- e.Requests = make([][]byte, len(requests))
- for i, req := range requests {
- e.Requests[i] = req
- }
- case "shouldOverrideBuilder":
- if isJSONNull(value) {
- e.Override = false
- return nil
- }
- if err := json.Unmarshal(value, &e.Override); err != nil {
- return err
- }
- case "witness":
- if isJSONNull(value) {
- e.Witness = nil
- return nil
- }
- var witness hexutil.Bytes
- if err := witness.UnmarshalJSON(value); err != nil {
- return err
- }
- e.Witness = &witness
+ var b jsonw.Buffer
+ b.Object(func() {
+ b.Key("executionPayload")
+ b.RawValue(payload)
+ b.Key("blockValue")
+ b.MustValue((*hexutil.Big)(e.BlockValue))
+ b.Key("blobsBundle")
+ marshalBlobsBundle(&b, e.BlobsBundle)
+ b.Key("executionRequests")
+ if e.Requests == nil {
+ b.Null()
+ } else {
+ b.Array(func() {
+ for _, r := range e.Requests {
+ b.HexBytes(r)
+ }
+ })
}
- return nil
- }); err != nil {
- return err
- }
- if !payloadSeen || e.ExecutionPayload == nil {
- return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope")
- }
- if !blockValueSeen || e.BlockValue == nil {
- return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
- }
- return nil
+ b.Key("shouldOverrideBuilder")
+ b.Bool(e.Override)
+ if e.Witness != nil {
+ b.Key("witness")
+ b.RawValue(witness)
+ }
+ })
+
+ return b.Output(), nil
+}
+
+func appendHexBytesArray[T ~[]byte](b *jsonw.Buffer, slice []T) {
+ b.Array(func() {
+ for _, elem := range slice {
+ b.HexBytes(elem)
+ }
+ })
}
diff --git a/beacon/engine/codec_epe_test.go b/beacon/engine/codec_epe_test.go
index aa36d39bce..e4ed5b6578 100644
--- a/beacon/engine/codec_epe_test.go
+++ b/beacon/engine/codec_epe_test.go
@@ -27,42 +27,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
)
-// canonicalEnvelope is a reference type for ExecutionPayloadEnvelope that uses
-// standard json.Marshal (no custom MarshalJSON). It mirrors the gencodec type
-// overrides so its output matches what the generated code would produce.
-type canonicalEnvelope struct {
- ExecutionPayload *ExecutableData `json:"executionPayload"`
- BlockValue *hexutil.Big `json:"blockValue"`
- BlobsBundle *BlobsBundle `json:"blobsBundle"`
- Requests []hexutil.Bytes `json:"executionRequests"`
- Override bool `json:"shouldOverrideBuilder"`
- Witness *hexutil.Bytes `json:"witness,omitempty"`
-}
-
-func toCanonical(e *ExecutionPayloadEnvelope) *canonicalEnvelope {
- c := &canonicalEnvelope{
- ExecutionPayload: e.ExecutionPayload,
- BlockValue: (*hexutil.Big)(e.BlockValue),
- BlobsBundle: e.BlobsBundle,
- Override: e.Override,
- Witness: e.Witness,
- }
- if e.Requests != nil {
- c.Requests = make([]hexutil.Bytes, len(e.Requests))
- for i, r := range e.Requests {
- c.Requests[i] = r
- }
- }
- return c
-}
-
-// compactJSON returns the compacted form of a JSON byte slice.
-func compactJSON(data []byte) []byte {
- var buf bytes.Buffer
- json.Compact(&buf, data)
- return buf.Bytes()
-}
-
func makeTestPayload() *ExecutableData {
return &ExecutableData{
ParentHash: common.HexToHash("0x01"),
@@ -82,93 +46,6 @@ func makeTestPayload() *ExecutableData {
}
}
-func TestMarshalJSON(t *testing.T) {
- witness := hexutil.Bytes{0xde, 0xad}
- tests := []struct {
- name string
- env ExecutionPayloadEnvelope
- }{
- {
- name: "full envelope with blobs",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(12345),
- BlobsBundle: &BlobsBundle{
- Commitments: []hexutil.Bytes{{0x01, 0x02}},
- Proofs: []hexutil.Bytes{{0x03, 0x04}},
- Blobs: []hexutil.Bytes{{0x05, 0x06}},
- },
- Requests: [][]byte{{0xaa}, {0xbb, 0xcc}},
- Override: true,
- Witness: &witness,
- },
- },
- {
- name: "nil BlobsBundle",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(0),
- },
- },
- {
- name: "nil Requests",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(1),
- Requests: nil,
- },
- },
- {
- name: "empty Requests",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(1),
- Requests: [][]byte{},
- },
- },
- {
- name: "nil Witness",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(1),
- Witness: nil,
- },
- },
- {
- name: "empty blobs bundle arrays",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(1),
- BlobsBundle: &BlobsBundle{
- Commitments: []hexutil.Bytes{},
- Proofs: []hexutil.Bytes{},
- Blobs: []hexutil.Bytes{},
- },
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Hand-rolled marshal.
- got, err := tt.env.MarshalJSON()
- if err != nil {
- t.Fatalf("MarshalJSON error: %v", err)
- }
-
- // Canonical marshal via reference struct.
- want, err := json.Marshal(toCanonical(&tt.env))
- if err != nil {
- t.Fatalf("canonical marshal error: %v", err)
- }
-
- if !bytes.Equal(compactJSON(got), compactJSON(want)) {
- t.Errorf("JSON mismatch\ngot: %s\nwant: %s", got, want)
- }
- })
- }
-}
-
func TestMarshalJSONRoundtrip(t *testing.T) {
witness := hexutil.Bytes{0xde, 0xad}
original := ExecutionPayloadEnvelope{
@@ -190,7 +67,7 @@ func TestMarshalJSONRoundtrip(t *testing.T) {
}
var decoded ExecutionPayloadEnvelope
- if err := decoded.UnmarshalJSON(data); err != nil {
+ if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("UnmarshalJSON error: %v", err)
}
@@ -214,100 +91,6 @@ func TestMarshalJSONRoundtrip(t *testing.T) {
}
}
-func TestUnmarshalJSON(t *testing.T) {
- witness := hexutil.Bytes{0xde, 0xad}
- tests := []struct {
- name string
- env ExecutionPayloadEnvelope
- }{
- {
- name: "full envelope with blobs",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(12345),
- BlobsBundle: &BlobsBundle{
- Commitments: []hexutil.Bytes{{0x01, 0x02}},
- Proofs: []hexutil.Bytes{{0x03, 0x04}},
- Blobs: []hexutil.Bytes{{0x05, 0x06}},
- },
- Requests: [][]byte{{0xaa}, {0xbb, 0xcc}},
- Override: true,
- Witness: &witness,
- },
- },
- {
- name: "null optional fields",
- env: ExecutionPayloadEnvelope{
- ExecutionPayload: makeTestPayload(),
- BlockValue: big.NewInt(1),
- BlobsBundle: nil,
- Requests: nil,
- Override: false,
- Witness: nil,
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- input, err := json.Marshal(toCanonical(&tt.env))
- if err != nil {
- t.Fatalf("canonical marshal error: %v", err)
- }
-
- var got ExecutionPayloadEnvelope
- if err := got.UnmarshalJSON(input); err != nil {
- t.Fatalf("UnmarshalJSON error: %v", err)
- }
-
- gotJSON, err := json.Marshal(toCanonical(&got))
- if err != nil {
- t.Fatalf("canonical marshal after unmarshal error: %v", err)
- }
- if !bytes.Equal(compactJSON(gotJSON), compactJSON(input)) {
- t.Errorf("JSON mismatch after unmarshal\ngot: %s\nwant: %s", gotJSON, input)
- }
- })
- }
-}
-
-func TestUnmarshalJSONMissingRequiredFields(t *testing.T) {
- tests := []struct {
- name string
- json func(t *testing.T) []byte
- }{
- {
- name: "missing executionPayload",
- json: func(t *testing.T) []byte {
- return []byte(`{"blockValue":"0x1"}`)
- },
- },
- {
- name: "missing blockValue",
- json: func(t *testing.T) []byte {
- input, err := json.Marshal(struct {
- ExecutionPayload *ExecutableData `json:"executionPayload"`
- }{
- ExecutionPayload: makeTestPayload(),
- })
- if err != nil {
- t.Fatalf("marshal input: %v", err)
- }
- return input
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var env ExecutionPayloadEnvelope
- if err := env.UnmarshalJSON(tt.json(t)); err == nil {
- t.Fatal("expected error")
- }
- })
- }
-}
-
func TestMarshalJSONNilPayload(t *testing.T) {
env := ExecutionPayloadEnvelope{
ExecutionPayload: nil,
diff --git a/beacon/engine/codec_helper.go b/beacon/engine/codec_helper.go
deleted file mode 100644
index 5672cd3f74..0000000000
--- a/beacon/engine/codec_helper.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2026 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package engine
-
-import (
- "bytes"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "slices"
-
- "github.com/ethereum/go-ethereum/common/hexutil"
-)
-
-// marshalHexBytesArray writes an array of hex-encoded byte slices to buf.
-// A nil slice is written as "null" to match encoding/json semantics.
-func marshalHexBytesArray(buf []byte, items []hexutil.Bytes) []byte {
- if items == nil {
- return append(buf, "null"...)
- }
- buf = append(buf, '[')
- for i, item := range items {
- if i > 0 {
- buf = append(buf, ',')
- }
- buf = writeHexBytes(buf, item)
- }
- buf = append(buf, ']')
- return buf
-}
-
-// writeHexBytes writes a hex-encoded byte slice as a JSON string ("0x...") to buf.
-func writeHexBytes(buf []byte, data []byte) []byte {
- buf = append(buf, '"', '0', 'x')
- buf = slices.Grow(buf, len(data)*2+1)
- cur := len(buf)
- buf = buf[:cur+len(data)*2]
- hex.Encode(buf[cur:], data)
- buf = append(buf, '"')
- return buf
-}
-
-func decodeJSONObject(input []byte, fn func(key string, value json.RawMessage) error) error {
- dec := json.NewDecoder(bytes.NewReader(input))
- tok, err := dec.Token()
- if err != nil {
- return err
- }
- delim, ok := tok.(json.Delim)
- if !ok || delim != '{' {
- return fmt.Errorf("expected JSON object")
- }
- for dec.More() {
- tok, err := dec.Token()
- if err != nil {
- return err
- }
- key, ok := tok.(string)
- if !ok {
- return fmt.Errorf("expected JSON object key")
- }
- var value json.RawMessage
- if err := dec.Decode(&value); err != nil {
- return err
- }
- if err := fn(key, value); err != nil {
- return err
- }
- }
- tok, err = dec.Token()
- if err != nil {
- return err
- }
- delim, ok = tok.(json.Delim)
- if !ok || delim != '}' {
- return fmt.Errorf("expected end of JSON object")
- }
- if _, err := dec.Token(); err != io.EOF {
- if err == nil {
- return fmt.Errorf("unexpected trailing data")
- }
- return err
- }
- return nil
-}
-
-func decodeJSONArray(input []byte, fn func(value json.RawMessage) error) error {
- dec := json.NewDecoder(bytes.NewReader(input))
- tok, err := dec.Token()
- if err != nil {
- return err
- }
- delim, ok := tok.(json.Delim)
- if !ok || delim != '[' {
- return fmt.Errorf("expected JSON array")
- }
- for dec.More() {
- var value json.RawMessage
- if err := dec.Decode(&value); err != nil {
- return err
- }
- if err := fn(value); err != nil {
- return err
- }
- }
- tok, err = dec.Token()
- if err != nil {
- return err
- }
- delim, ok = tok.(json.Delim)
- if !ok || delim != ']' {
- return fmt.Errorf("expected end of JSON array")
- }
- if _, err := dec.Token(); err != io.EOF {
- if err == nil {
- return fmt.Errorf("unexpected trailing data")
- }
- return err
- }
- return nil
-}
-
-func isJSONNull(input []byte) bool {
- return bytes.Equal(bytes.TrimSpace(input), []byte("null"))
-}
-
-func unmarshalHexBytesArray(input []byte) ([]hexutil.Bytes, error) {
- if isJSONNull(input) {
- return nil, nil
- }
- items := make([]hexutil.Bytes, 0)
- if err := decodeJSONArray(input, func(value json.RawMessage) error {
- var item hexutil.Bytes
- if err := item.UnmarshalJSON(value); err != nil {
- return err
- }
- items = append(items, item)
- return nil
- }); err != nil {
- return nil, err
- }
- return items, nil
-}
diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go
new file mode 100644
index 0000000000..a125daa030
--- /dev/null
+++ b/beacon/engine/gen_epe.go
@@ -0,0 +1,53 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package engine
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var _ = (*executionPayloadEnvelopeMarshaling)(nil)
+
+// UnmarshalJSON unmarshals from JSON.
+func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
+ type ExecutionPayloadEnvelope struct {
+ ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
+ BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
+ BlobsBundle *BlobsBundle `json:"blobsBundle"`
+ Requests []hexutil.Bytes `json:"executionRequests"`
+ Override *bool `json:"shouldOverrideBuilder"`
+ Witness *hexutil.Bytes `json:"witness,omitempty"`
+ }
+ var dec ExecutionPayloadEnvelope
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ExecutionPayload == nil {
+ return errors.New("missing required field 'executionPayload' for ExecutionPayloadEnvelope")
+ }
+ e.ExecutionPayload = dec.ExecutionPayload
+ if dec.BlockValue == nil {
+ return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope")
+ }
+ e.BlockValue = (*big.Int)(dec.BlockValue)
+ if dec.BlobsBundle != nil {
+ e.BlobsBundle = dec.BlobsBundle
+ }
+ if dec.Requests != nil {
+ e.Requests = make([][]byte, len(dec.Requests))
+ for k, v := range dec.Requests {
+ e.Requests[k] = v
+ }
+ }
+ if dec.Override != nil {
+ e.Override = *dec.Override
+ }
+ if dec.Witness != nil {
+ e.Witness = dec.Witness
+ }
+ return nil
+}
diff --git a/beacon/engine/types.go b/beacon/engine/types.go
index cfd0dba395..8824e40f5e 100644
--- a/beacon/engine/types.go
+++ b/beacon/engine/types.go
@@ -125,7 +125,7 @@ type StatelessPayloadStatusV1 struct {
ValidationError *string `json:"validationError"`
}
-//go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
+//go:generate go run github.com/fjl/gencodec -enc=false -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
@@ -136,6 +136,12 @@ type ExecutionPayloadEnvelope struct {
Witness *hexutil.Bytes `json:"witness,omitempty"`
}
+// JSON type overrides for ExecutionPayloadEnvelope.
+type executionPayloadEnvelopeMarshaling struct {
+ BlockValue *hexutil.Big
+ Requests []hexutil.Bytes
+}
+
// BlobsBundle includes the marshalled sidecar data. Note this structure is
// shared by BlobsBundleV1 and BlobsBundleV2 for the sake of simplicity.
//
diff --git a/go.mod b/go.mod
index 17897a62c0..2cd1c5bcaf 100644
--- a/go.mod
+++ b/go.mod
@@ -83,6 +83,7 @@ require (
require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
+ github.com/fjl/jsonw v0.0.0-20260519133611-b234a3a62a01 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
@@ -121,7 +122,7 @@ require (
github.com/deepmap/oapi-codegen v1.6.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/emicklei/dot v1.6.2 // indirect
- github.com/fjl/gencodec v0.1.0 // indirect
+ github.com/fjl/gencodec v0.1.2 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
diff --git a/go.sum b/go.sum
index bad8a44cfd..9fa5364940 100644
--- a/go.sum
+++ b/go.sum
@@ -125,6 +125,12 @@ github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeD
github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/fjl/gencodec v0.1.0 h1:B3K0xPfc52cw52BBgUbSPxYo+HlLfAgWMVKRWXUXBcs=
github.com/fjl/gencodec v0.1.0/go.mod h1:Um1dFHPONZGTHog1qD1NaWjXJW/SPB38wPv0O8uZ2fI=
+github.com/fjl/gencodec v0.1.2 h1:nf+MMsmuii5ZQMbS6/xjZoe5LRkN0415FOJOSwmnuW8=
+github.com/fjl/gencodec v0.1.2/go.mod h1:chDHL3wKXuBgauP8x3XNZkl5EIAR5SoCTmmmDTZRzmw=
+github.com/fjl/jsonw v0.0.0-20260518201611-f2cd7df7ef66 h1:B+iPMRxXE3dbWXwQX6Un0MLOGGXXjj04gvWYD3pDrQE=
+github.com/fjl/jsonw v0.0.0-20260518201611-f2cd7df7ef66/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU=
+github.com/fjl/jsonw v0.0.0-20260519133611-b234a3a62a01 h1:2fvSvrrQMWs3l3MY1Ot4cPzv3Iww17ha1KJwjymX+Ks=
+github.com/fjl/jsonw v0.0.0-20260519133611-b234a3a62a01/go.mod h1:2KMLevM6FXEJnfhtk7naXu9vZdVfOma1GlnGdPRlumU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=