Skip to content

Commit 61a5354

Browse files
committed
beacon/engine, rpc: add PremarshaledJSON for execution payload envelopes
1 parent fc8c104 commit 61a5354

File tree

3 files changed

+885
-1
lines changed

3 files changed

+885
-1
lines changed

beacon/engine/premarshal.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright 2026 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package engine
18+
19+
import (
20+
"encoding/hex"
21+
"encoding/json"
22+
"slices"
23+
24+
"github.com/ethereum/go-ethereum/common/hexutil"
25+
)
26+
27+
// estimateBlobsBundleSize returns a rough estimate of the JSON size for a BlobsBundle.
28+
func estimateBlobsBundleSize(b *BlobsBundle) int {
29+
size := 80 // JSON structure overhead
30+
for _, blob := range b.Blobs {
31+
size += len(blob)*2 + 6
32+
}
33+
for _, c := range b.Commitments {
34+
size += len(c)*2 + 6
35+
}
36+
for _, p := range b.Proofs {
37+
size += len(p)*2 + 6
38+
}
39+
return size
40+
}
41+
42+
// marshalBlobsBundle writes BlobsBundle as JSON and appends it to buf.
43+
func marshalBlobsBundle(buf []byte, b *BlobsBundle) []byte {
44+
buf = append(buf, `{"commitments":`...)
45+
buf = marshalHexBytesArray(buf, b.Commitments)
46+
47+
buf = append(buf, `,"proofs":`...)
48+
buf = marshalHexBytesArray(buf, b.Proofs)
49+
50+
buf = append(buf, `,"blobs":`...)
51+
buf = marshalHexBytesArray(buf, b.Blobs)
52+
53+
buf = append(buf, '}')
54+
return buf
55+
}
56+
57+
// marshalHexBytesArray writes an array of hex-encoded byte slices to buf.
58+
func marshalHexBytesArray(buf []byte, items []hexutil.Bytes) []byte {
59+
buf = append(buf, '[')
60+
for i, item := range items {
61+
if i > 0 {
62+
buf = append(buf, ',')
63+
}
64+
buf = writeHexBytes(buf, item)
65+
}
66+
buf = append(buf, ']')
67+
return buf
68+
}
69+
70+
// writeHexBytes writes a hex-encoded byte slice as a JSON string ("0x...") to buf.
71+
// NOTE: This function avoids allocations by pre-allocating the buffer space needed;
72+
// otherwise, we would use hexutil.Encode() and append the result to the buffer.
73+
// hexutil.Encode() uses 64% more memory than writing to buffer directly.
74+
func writeHexBytes(buf []byte, data []byte) []byte {
75+
buf = append(buf, '"', '0', 'x')
76+
buf = slices.Grow(buf, len(data)*2+1)
77+
cur := len(buf)
78+
buf = buf[:cur+len(data)*2]
79+
hex.Encode(buf[cur:], data)
80+
buf = append(buf, '"')
81+
return buf
82+
}
83+
84+
// PremarshaledJSON implements rpc.JSONPremarshaled. It returns pre-serialized
85+
// JSON by delegating small fields to their existing MarshalJSON methods and
86+
// hand-rolling only the BlobsBundle.
87+
func (e ExecutionPayloadEnvelope) PremarshaledJSON() ([]byte, error) {
88+
// Marshal the execution payload using its gencodec MarshalJSON.
89+
payload, err := e.ExecutionPayload.MarshalJSON()
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
// Marshal the block value.
95+
blockValue, err := json.Marshal((*hexutil.Big)(e.BlockValue))
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
// Marshal the execution requests.
101+
var requests []byte
102+
if e.Requests != nil {
103+
hexRequests := make([]hexutil.Bytes, len(e.Requests))
104+
for i, req := range e.Requests {
105+
hexRequests[i] = req
106+
}
107+
requests, err = json.Marshal(hexRequests)
108+
if err != nil {
109+
return nil, err
110+
}
111+
}
112+
113+
// Marshal the override.
114+
override, err := json.Marshal(e.Override)
115+
if err != nil {
116+
return nil, err
117+
}
118+
119+
// Marshal the witness.
120+
var witness []byte
121+
if e.Witness != nil {
122+
witness, err = json.Marshal(e.Witness)
123+
if err != nil {
124+
return nil, err
125+
}
126+
}
127+
128+
// Estimate buffer size.
129+
size := len(payload) + len(blockValue) + len(requests) + len(override) + len(witness)
130+
if e.BlobsBundle != nil {
131+
size += estimateBlobsBundleSize(e.BlobsBundle)
132+
}
133+
size += 128 // JSON bloat (keys, braces, commas, etc.)
134+
buf := make([]byte, 0, size)
135+
136+
// Write the execution payload to the buffer
137+
buf = append(buf, `{"executionPayload":`...)
138+
buf = append(buf, payload...)
139+
140+
// Write the block value to the buffer
141+
buf = append(buf, `,"blockValue":`...)
142+
buf = append(buf, blockValue...)
143+
144+
// Write the blobs bundle to the buffer
145+
buf = append(buf, `,"blobsBundle":`...)
146+
if e.BlobsBundle != nil {
147+
buf = marshalBlobsBundle(buf, e.BlobsBundle)
148+
} else {
149+
buf = append(buf, "null"...)
150+
}
151+
152+
// Write the execution requests to the buffer
153+
buf = append(buf, `,"executionRequests":`...)
154+
if requests != nil {
155+
buf = append(buf, requests...)
156+
} else {
157+
buf = append(buf, "null"...)
158+
}
159+
160+
// Write the override to the buffer
161+
buf = append(buf, `,"shouldOverrideBuilder":`...)
162+
buf = append(buf, override...)
163+
164+
// Write the witness to the buffer if present
165+
if witness != nil {
166+
buf = append(buf, `,"witness":`...)
167+
buf = append(buf, witness...)
168+
}
169+
170+
// Close the envelope
171+
buf = append(buf, '}')
172+
return buf, nil
173+
}

0 commit comments

Comments
 (0)