update to newest spec

This commit is contained in:
MariusVanDerWijden 2026-06-17 11:08:36 +02:00
parent ac6751b845
commit fcefb32655
No known key found for this signature in database
8 changed files with 349 additions and 53 deletions

View file

@ -22,36 +22,85 @@ import (
"github.com/karalabe/ssz"
)
// ExecutionPayloadEnvelopeAmsterdam is the request body of
// POST /amsterdam/payloads.
// ExecutionPayloadEnvelopeAmsterdam is the monolithic request body of
// POST /{fork}/payloads spanning every fork from Paris onward. The wire shape
// is fork-driven by the codec (see the per-fork ExecutionPayloadEnvelope
// catalogue in refactor-ssz.md):
//
// - Paris / Shanghai: bare payload, no envelope fields
// - Cancun+: + ParentBeaconBlockRoot
// - Prague+: + ExecutionRequests
//
// ParentBeaconBlockRoot is a pointer so the codec can distinguish "absent for
// this fork" (nil, Paris/Shanghai) from "present and zero".
type ExecutionPayloadEnvelopeAmsterdam struct {
Payload *ExecutionPayload
ParentBeaconBlockRoot common.Hash
ExecutionRequests [][]byte
ParentBeaconBlockRoot *common.Hash // Cancun+
ExecutionRequests [][]byte // Prague+
}
func (e *ExecutionPayloadEnvelopeAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
// offset(payload) + 32(root) + offset(requests)
size := uint32(40)
// offset(payload) is always present; the root and the requests offset only
// exist from Cancun and Prague respectively (matching the OnFork gating).
size := uint32(4)
if siz.Fork() >= ssz.ForkDencun {
size += 32 // ParentBeaconBlockRoot
}
if siz.Fork() >= ssz.ForkPectra {
size += 4 // offset(requests)
}
if fixed {
return size
}
size += ssz.SizeDynamicObject(siz, e.Payload)
size += ssz.SizeSliceOfDynamicBytes(siz, e.ExecutionRequests)
if siz.Fork() >= ssz.ForkPectra {
size += ssz.SizeSliceOfDynamicBytes(siz, e.ExecutionRequests)
}
return size
}
func (e *ExecutionPayloadEnvelopeAmsterdam) DefineSSZ(c *ssz.Codec) {
ssz.DefineDynamicObjectOffset(c, &e.Payload)
ssz.DefineStaticBytes(c, &e.ParentBeaconBlockRoot)
ssz.DefineSliceOfDynamicBytesOffset(c, &e.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
ssz.DefineStaticBytesPointerOnFork(c, &e.ParentBeaconBlockRoot, fromCancun)
ssz.DefineSliceOfDynamicBytesOffsetOnFork(c, &e.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
ssz.DefineDynamicObjectContent(c, &e.Payload)
ssz.DefineSliceOfDynamicBytesContent(c, &e.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
ssz.DefineSliceOfDynamicBytesContentOnFork(c, &e.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
}
// BlobsBundleV2 is the cell-proof blob bundle carried by BuiltPayload
// from Osaka onwards.
// BlobsBundleV1 is the single-proof blob bundle carried by BuiltPayload for
// Cancun and Prague: one KZG proof per blob.
type BlobsBundleV1 struct {
Commitments [][48]byte
Proofs [][48]byte
Blobs []*Blob
}
func (b *BlobsBundleV1) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
// 3 offsets
size := uint32(12)
if fixed {
return size
}
size += ssz.SizeSliceOfStaticBytes(siz, b.Commitments)
size += ssz.SizeSliceOfStaticBytes(siz, b.Proofs)
size += ssz.SizeSliceOfStaticObjects(siz, b.Blobs)
return size
}
func (b *BlobsBundleV1) DefineSSZ(c *ssz.Codec) {
ssz.DefineSliceOfStaticBytesOffset(c, &b.Commitments, MaxBlobCommitmentsPerBlock)
ssz.DefineSliceOfStaticBytesOffset(c, &b.Proofs, MaxBlobCommitmentsPerBlock)
ssz.DefineSliceOfStaticObjectsOffset(c, &b.Blobs, MaxBlobCommitmentsPerBlock)
ssz.DefineSliceOfStaticBytesContent(c, &b.Commitments, MaxBlobCommitmentsPerBlock)
ssz.DefineSliceOfStaticBytesContent(c, &b.Proofs, MaxBlobCommitmentsPerBlock)
ssz.DefineSliceOfStaticObjectsContent(c, &b.Blobs, MaxBlobCommitmentsPerBlock)
}
// BlobsBundleV2 is the cell-proof blob bundle carried by BuiltPayload from Osaka
// onwards: CellsPerExtBlob cell proofs per blob. The wire layout matches
// BlobsBundleV1 (three slices); only the proofs-list bound differs.
type BlobsBundleV2 struct {
Commitments [][48]byte
Proofs [][48]byte
@ -80,37 +129,67 @@ func (b *BlobsBundleV2) DefineSSZ(c *ssz.Codec) {
ssz.DefineSliceOfStaticObjectsContent(c, &b.Blobs, MaxBlobCommitmentsPerBlock)
}
// BuiltPayloadAmsterdam is the response of GET /amsterdam/payloads/{id}.
// BuiltPayloadAmsterdam is the monolithic response of GET /{fork}/payloads/{id}
// spanning every fork from Paris onward. The wire shape is fork-driven by the
// codec (see the per-fork BuiltPayload catalogue in refactor-ssz.md):
//
// - Paris / Shanghai: Payload, BlockValue only
// - Cancun: + BlobsBundleV1, ShouldOverrideBuilder
// - Prague: + ExecutionRequests (placed before ShouldOverrideBuilder)
// - Osaka+: BlobsBundle revision becomes V2 (cell proofs)
//
// Exactly one of BlobsBundleV1 / BlobsBundleV2 is active for any given fork
// (V1 = Cancun..Prague, V2 = Osaka+); the other is nil and contributes neither
// an offset nor content. ShouldOverrideBuilder is a pointer so the codec can
// distinguish "absent for this fork" (nil, Paris/Shanghai) from "present and
// false".
type BuiltPayloadAmsterdam struct {
Payload *ExecutionPayload
BlockValue *uint256.Int
BlobsBundle *BlobsBundleV2
ExecutionRequests [][]byte
ShouldOverrideBuilder bool
BlobsBundleV1 *BlobsBundleV1 // Cancun, Prague
BlobsBundleV2 *BlobsBundleV2 // Osaka+
ExecutionRequests [][]byte // Prague+
ShouldOverrideBuilder *bool // Cancun+
}
func (p *BuiltPayloadAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
// offset(payload) + 32(value) + offset(bundle) + offset(requests) + 1(override)
size := uint32(4 + 32 + 4 + 4 + 1)
// offset(payload) + 32(value) are always present; the bundle offset and the
// override byte exist from Cancun, and the requests offset from Prague.
size := uint32(4 + 32)
if siz.Fork() >= ssz.ForkDencun {
size += 4 // offset(blobs_bundle)
size++ // should_override_builder
}
if siz.Fork() >= ssz.ForkPectra {
size += 4 // offset(execution_requests)
}
if fixed {
return size
}
size += ssz.SizeDynamicObject(siz, p.Payload)
size += ssz.SizeDynamicObject(siz, p.BlobsBundle)
size += ssz.SizeSliceOfDynamicBytes(siz, p.ExecutionRequests)
if siz.Fork() >= forkOsaka {
size += ssz.SizeDynamicObject(siz, p.BlobsBundleV2)
} else if siz.Fork() >= ssz.ForkDencun {
size += ssz.SizeDynamicObject(siz, p.BlobsBundleV1)
}
if siz.Fork() >= ssz.ForkPectra {
size += ssz.SizeSliceOfDynamicBytes(siz, p.ExecutionRequests)
}
return size
}
func (p *BuiltPayloadAmsterdam) DefineSSZ(c *ssz.Codec) {
ssz.DefineDynamicObjectOffset(c, &p.Payload)
ssz.DefineUint256(c, &p.BlockValue)
ssz.DefineDynamicObjectOffset(c, &p.BlobsBundle)
ssz.DefineSliceOfDynamicBytesOffset(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
ssz.DefineBool(c, &p.ShouldOverrideBuilder)
ssz.DefineDynamicObjectOffsetOnFork(c, &p.BlobsBundleV1, cancunToOsaka)
ssz.DefineDynamicObjectOffsetOnFork(c, &p.BlobsBundleV2, fromOsaka)
ssz.DefineSliceOfDynamicBytesOffsetOnFork(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
ssz.DefineBoolPointerOnFork(c, &p.ShouldOverrideBuilder, fromCancun)
ssz.DefineDynamicObjectContent(c, &p.Payload)
ssz.DefineDynamicObjectContent(c, &p.BlobsBundle)
ssz.DefineSliceOfDynamicBytesContent(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
ssz.DefineDynamicObjectContentOnFork(c, &p.BlobsBundleV1, cancunToOsaka)
ssz.DefineDynamicObjectContentOnFork(c, &p.BlobsBundleV2, fromOsaka)
ssz.DefineSliceOfDynamicBytesContentOnFork(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
}
// ForkchoiceUpdateAmsterdam is the request body of POST /amsterdam/forkchoice.

View file

@ -37,6 +37,19 @@ const (
forkAmsterdam = ssz.ForkPectra + 2
)
// AtLeast reports whether the ssz fork sf is at or beyond the geth fork f in
// the codec's ordering. It encapsulates the local forkOsaka/forkAmsterdam
// aliases so callers outside this package can compare against post-Pectra forks
// without naming the unexported constants. Pre-Engine-API forks (no SSZ wire
// representation) compare as not-reached.
func AtLeast(sf ssz.Fork, f forks.Fork) bool {
target, ok := ForkFor(f)
if !ok {
return false
}
return sf >= target
}
// ForkFor maps a geth params/forks.Fork onto the karalabe/ssz Fork value
// the codec multiplexes on. The bool is false for forks that predate the
// Engine API (and thus have no SSZ wire representation here).

View file

@ -36,6 +36,13 @@ var (
fromCancun = ssz.ForkFilter{Added: ssz.ForkDencun}
// beacon-root attribute, introduced in Cancun (Dencun).
// (shares the Cancun boundary but kept named for clarity at call sites)
// execution-requests group, introduced in Prague (Pectra).
fromPrague = ssz.ForkFilter{Added: ssz.ForkPectra}
// single-proof blobs bundle (BlobsBundleV1), active Cancun..Prague; replaced
// by the cell-proof BlobsBundleV2 from Osaka on.
cancunToOsaka = ssz.ForkFilter{Added: ssz.ForkDencun, Removed: forkOsaka}
// cell-proof blobs bundle (BlobsBundleV2), introduced in Osaka.
fromOsaka = ssz.ForkFilter{Added: forkOsaka}
// bal + slot group, introduced in Amsterdam.
fromAmsterdam = ssz.ForkFilter{Added: forkAmsterdam}
)

View file

@ -17,6 +17,7 @@
package ssz
import (
"encoding/binary"
"reflect"
"testing"
@ -157,3 +158,157 @@ func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
}
encDecOnFork(t, bare, forkAmsterdam, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
}
// minimalPayload builds an ExecutionPayload carrying exactly the fields the
// given fork's wire shape requires (BaseFeePerGas is always present; blob-gas
// pointers from Cancun; slot from Amsterdam), so it round-trips cleanly at fork.
func minimalPayload(fork ssz.Fork) *ExecutionPayload {
p := &ExecutionPayload{BaseFeePerGas: uint256.NewInt(7e9)}
if fork >= ssz.ForkDencun {
blob, excess := uint64(0), uint64(0)
p.BlobGasUsed, p.ExcessBlobGas = &blob, &excess
}
if fork >= forkAmsterdam {
slot := uint64(0)
p.SlotNumber = &slot
}
return p
}
// allEngineForks enumerates the codec forks the Engine API v2 spec covers
// (Paris onward), paired with the spec's fixed-part sizes for the envelope and
// built-payload wrappers.
var allEngineForks = []struct {
name string
fork ssz.Fork
envelopeFixed uint32 // ExecutionPayloadEnvelope fixed part
builtPayloadFix uint32 // BuiltPayload fixed part
hasBeaconRoot bool // Cancun+
hasRequests bool // Prague+
hasBundle bool // Cancun+
bundleIsV2 bool // Osaka+
}{
// envelope fixed: offset(payload)=4 [+root 32 from Cancun] [+offset(requests) 4 from Prague]
// built fixed: offset(payload)=4 + value 32 [+offset(bundle) 4 + override 1 from Cancun] [+offset(requests) 4 from Prague]
{"paris", ssz.ForkParis, 4, 36, false, false, false, false},
{"shanghai", ssz.ForkShapella, 4, 36, false, false, false, false},
{"cancun", ssz.ForkDencun, 36, 41, true, false, true, false},
{"prague", ssz.ForkPectra, 40, 45, true, true, true, false},
{"osaka", forkOsaka, 40, 45, true, true, true, true},
{"amsterdam", forkAmsterdam, 40, 45, true, true, true, true},
}
// TestExecutionPayloadEnvelopePerFork verifies the envelope wire shape matches
// the per-fork ExecutionPayloadEnvelope catalogue in refactor-ssz.md for every
// fork the spec covers: bare payload for Paris/Shanghai, +parent_beacon_block_root
// from Cancun, +execution_requests from Prague.
func TestExecutionPayloadEnvelopePerFork(t *testing.T) {
for _, tc := range allEngineForks {
t.Run(tc.name, func(t *testing.T) {
env := &ExecutionPayloadEnvelopeAmsterdam{Payload: minimalPayload(tc.fork)}
if tc.hasBeaconRoot {
env.ParentBeaconBlockRoot = &common.Hash{0x55}
}
if tc.hasRequests {
env.ExecutionRequests = [][]byte{{0x01, 0x02}}
}
encDecOnFork(t, env, tc.fork, func() *ExecutionPayloadEnvelopeAmsterdam {
return new(ExecutionPayloadEnvelopeAmsterdam)
})
// Pre-Cancun must not carry a beacon root; pre-Prague must not carry
// requests — the codec drops them, so a decoded copy stays empty.
buf := make([]byte, ssz.SizeOnFork(env, tc.fork))
if err := ssz.EncodeToBytesOnFork(buf, env, tc.fork); err != nil {
t.Fatalf("encode: %v", err)
}
// payload is the first (dynamic) field, so its offset equals the
// fixed-part length — the spec's per-fork envelope size.
if off := readOffset(buf); off != tc.envelopeFixed {
t.Errorf("envelope fixed size = %d, want %d", off, tc.envelopeFixed)
}
got := new(ExecutionPayloadEnvelopeAmsterdam)
if err := ssz.DecodeFromBytesOnFork(buf, got, tc.fork); err != nil {
t.Fatalf("decode: %v", err)
}
if !tc.hasBeaconRoot && got.ParentBeaconBlockRoot != nil {
t.Errorf("%s: beacon root decoded but fork predates Cancun", tc.name)
}
if tc.hasBeaconRoot && got.ParentBeaconBlockRoot == nil {
t.Errorf("%s: beacon root missing after roundtrip", tc.name)
}
if !tc.hasRequests && len(got.ExecutionRequests) > 0 {
t.Errorf("%s: requests decoded but fork predates Prague", tc.name)
}
})
}
}
// TestBuiltPayloadPerFork verifies the BuiltPayload wire shape matches the
// per-fork BuiltPayload catalogue in refactor-ssz.md: {payload, block_value}
// for Paris/Shanghai, +blobs_bundle(V1)+should_override_builder from Cancun,
// +execution_requests from Prague, blobs_bundle→V2 from Osaka.
func TestBuiltPayloadPerFork(t *testing.T) {
for _, tc := range allEngineForks {
t.Run(tc.name, func(t *testing.T) {
bp := &BuiltPayloadAmsterdam{
Payload: minimalPayload(tc.fork),
BlockValue: uint256.NewInt(1000),
}
if tc.hasBundle {
bundle := func() ([][48]byte, [][48]byte, []*Blob) {
return [][48]byte{{0x01}}, [][48]byte{{0x02}}, []*Blob{{Bytes: make([]byte, BytesPerBlob)}}
}
c, p, b := bundle()
if tc.bundleIsV2 {
bp.BlobsBundleV2 = &BlobsBundleV2{Commitments: c, Proofs: p, Blobs: b}
} else {
bp.BlobsBundleV1 = &BlobsBundleV1{Commitments: c, Proofs: p, Blobs: b}
}
override := true
bp.ShouldOverrideBuilder = &override
}
if tc.hasRequests {
bp.ExecutionRequests = [][]byte{{0xaa}}
}
encDecOnFork(t, bp, tc.fork, func() *BuiltPayloadAmsterdam { return new(BuiltPayloadAmsterdam) })
buf := make([]byte, ssz.SizeOnFork(bp, tc.fork))
if err := ssz.EncodeToBytesOnFork(buf, bp, tc.fork); err != nil {
t.Fatalf("encode: %v", err)
}
// payload is the first (dynamic) field, so its offset equals the
// fixed-part length — the spec's per-fork built-payload size.
if off := readOffset(buf); off != tc.builtPayloadFix {
t.Errorf("built-payload fixed size = %d, want %d", off, tc.builtPayloadFix)
}
got := new(BuiltPayloadAmsterdam)
if err := ssz.DecodeFromBytesOnFork(buf, got, tc.fork); err != nil {
t.Fatalf("decode: %v", err)
}
// Exactly the right bundle revision is populated (or neither pre-Cancun).
if tc.bundleIsV2 && got.BlobsBundleV1 != nil {
t.Errorf("%s: V1 bundle decoded for an Osaka+ fork", tc.name)
}
if tc.hasBundle && !tc.bundleIsV2 && got.BlobsBundleV2 != nil {
t.Errorf("%s: V2 bundle decoded for a pre-Osaka fork", tc.name)
}
if !tc.hasBundle && (got.BlobsBundleV1 != nil || got.BlobsBundleV2 != nil) {
t.Errorf("%s: bundle decoded but fork predates Cancun", tc.name)
}
if !tc.hasBundle && got.ShouldOverrideBuilder != nil {
t.Errorf("%s: should_override_builder decoded but fork predates Cancun", tc.name)
}
if !tc.hasRequests && len(got.ExecutionRequests) > 0 {
t.Errorf("%s: requests decoded but fork predates Prague", tc.name)
}
})
}
}
// readOffset reads the first 4-byte little-endian SSZ offset from buf. For a
// container whose first field is dynamic, this offset equals the length of the
// fixed part (where the variable region begins).
func readOffset(buf []byte) uint32 {
return binary.LittleEndian.Uint32(buf[:4])
}

View file

@ -34,7 +34,7 @@ func (b *Blob) DefineSSZ(c *ssz.Codec) {
ssz.DefineCheckedStaticBytes(c, &b.Bytes, BytesPerBlob)
}
// BlobCell wraps a single BYTES_PER_CELL = 1024 byte cell. Same wrapper
// BlobCell wraps a single BYTES_PER_CELL = 2048 byte cell. Same wrapper
// rationale as Blob.
type BlobCell struct {
Bytes []byte
@ -59,7 +59,7 @@ func (b *Bitvector128) DefineSSZ(c *ssz.Codec) {
}
// OptionalBlobCell models `Optional[ByteVector[BYTES_PER_CELL]]` =
// `List[BlobCell, 1]`. Length 0 = absent, length 1 = present (1024 bytes).
// `List[BlobCell, 1]`. Length 0 = absent, length 1 = present (2048 bytes).
type OptionalBlobCell struct {
Cells []*BlobCell
}

View file

@ -56,42 +56,83 @@ func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork
}
// buildBuiltPayloadAmsterdam packages an engine.ExecutionPayloadEnvelope into
// the SSZ BuiltPayload shape. BlockValue/BlobsBundle/Requests come straight
// across; the inner payload goes through the SSZ converter.
// the SSZ BuiltPayload shape for the URL fork. BlockValue/Requests come straight
// across; the inner payload goes through the SSZ converter. The blobs bundle is
// emitted as V1 (Cancun/Prague) or V2 (Osaka+) per the fork; pre-Cancun forks
// carry no bundle (and no should_override_builder), so those fields are left
// nil and the codec drops them.
func buildBuiltPayloadAmsterdam(env *engine.ExecutionPayloadEnvelope, sf ssz.Fork) *sszt.BuiltPayloadAmsterdam {
out := &sszt.BuiltPayloadAmsterdam{
Payload: sszt.ExecutionPayloadFromEngine(env.ExecutionPayload, sf),
BlockValue: new(uint256.Int),
ExecutionRequests: env.Requests,
ShouldOverrideBuilder: env.Override,
Payload: sszt.ExecutionPayloadFromEngine(env.ExecutionPayload, sf),
BlockValue: new(uint256.Int),
}
if env.BlockValue != nil {
out.BlockValue.SetFromBig(env.BlockValue)
}
if env.BlobsBundle != nil {
out.BlobsBundle = convertBlobsBundle(env.BlobsBundle)
} else {
out.BlobsBundle = new(sszt.BlobsBundleV2)
// execution_requests and should_override_builder exist from Prague and
// Cancun respectively; the codec gates them, so it is safe to always set
// the values the EL produced — the gated-off forks simply ignore them.
if sszt.AtLeast(sf, forks.Prague) {
out.ExecutionRequests = env.Requests
}
if sszt.AtLeast(sf, forks.Cancun) {
override := env.Override
out.ShouldOverrideBuilder = &override
}
// Select the bundle revision. Cancun/Prague use V1 (one proof per blob);
// Osaka+ uses V2 (cell proofs). Pre-Cancun forks have no bundle.
switch {
case sszt.AtLeast(sf, forks.Osaka):
if env.BlobsBundle != nil {
out.BlobsBundleV2 = convertBlobsBundleV2(env.BlobsBundle)
} else {
out.BlobsBundleV2 = new(sszt.BlobsBundleV2)
}
case sszt.AtLeast(sf, forks.Cancun):
if env.BlobsBundle != nil {
out.BlobsBundleV1 = convertBlobsBundleV1(env.BlobsBundle)
} else {
out.BlobsBundleV1 = new(sszt.BlobsBundleV1)
}
}
return out
}
// convertBlobsBundle copies the JSON BlobsBundle into the SSZ V2 layout.
// Inputs are length-validated by the caller's miner pipeline.
func convertBlobsBundle(b *engine.BlobsBundle) *sszt.BlobsBundleV2 {
// convertBlobsBundleV2 copies the JSON BlobsBundle into the SSZ V2 (cell-proof)
// layout. Inputs are length-validated by the caller's miner pipeline.
func convertBlobsBundleV2(b *engine.BlobsBundle) *sszt.BlobsBundleV2 {
out := &sszt.BlobsBundleV2{
Commitments: make([][48]byte, len(b.Commitments)),
Proofs: make([][48]byte, len(b.Proofs)),
Blobs: make([]*sszt.Blob, len(b.Blobs)),
}
for i, c := range b.Commitments {
copy(out.Commitments[i][:], c)
}
for i, p := range b.Proofs {
copy(out.Proofs[i][:], p)
}
for i, blob := range b.Blobs {
out.Blobs[i] = &sszt.Blob{Bytes: append([]byte(nil), blob...)}
}
fillBundle(out.Commitments, out.Proofs, out.Blobs, b)
return out
}
// convertBlobsBundleV1 copies the JSON BlobsBundle into the SSZ V1 (single-proof)
// layout used by Cancun/Prague.
func convertBlobsBundleV1(b *engine.BlobsBundle) *sszt.BlobsBundleV1 {
out := &sszt.BlobsBundleV1{
Commitments: make([][48]byte, len(b.Commitments)),
Proofs: make([][48]byte, len(b.Proofs)),
Blobs: make([]*sszt.Blob, len(b.Blobs)),
}
fillBundle(out.Commitments, out.Proofs, out.Blobs, b)
return out
}
// fillBundle copies the commitments/proofs/blobs from a JSON BlobsBundle into
// the destination SSZ slices (shared by the V1 and V2 converters, whose wire
// layout is identical).
func fillBundle(commitments, proofs [][48]byte, blobs []*sszt.Blob, b *engine.BlobsBundle) {
for i, c := range b.Commitments {
copy(commitments[i][:], c)
}
for i, p := range b.Proofs {
copy(proofs[i][:], p)
}
for i, blob := range b.Blobs {
blobs[i] = &sszt.Blob{Bytes: append([]byte(nil), blob...)}
}
}

View file

@ -50,8 +50,9 @@ func (rt *Router) handleNewPayload(w http.ResponseWriter, r *http.Request, fork
writeProblem(w, http.StatusUnprocessableEntity, ErrInvalidBody, err.Error())
return
}
root := env.ParentBeaconBlockRoot
status, err := rt.backend.NewPayload(r.Context(), *data, versionedHashes, &root, env.ExecutionRequests)
// ParentBeaconBlockRoot is absent (nil) for Paris/Shanghai and present from
// Cancun on; pass it through as-is so the EL sees the correct optionality.
status, err := rt.backend.NewPayload(r.Context(), *data, versionedHashes, env.ParentBeaconBlockRoot, env.ExecutionRequests)
if err != nil {
mapBackendErr(w, err)
return

View file

@ -149,7 +149,7 @@ func TestRouterNewPayload(t *testing.T) {
ExcessBlobGas: &excess,
SlotNumber: &slot,
},
ParentBeaconBlockRoot: common.Hash{0x55},
ParentBeaconBlockRoot: &common.Hash{0x55},
}
resp := sszPost(t, srv, "/amsterdam/payloads", env, amsterdam)
if resp.StatusCode != 200 {
@ -257,7 +257,7 @@ func TestRouterAdvertisedForkRoutable(t *testing.T) {
BlobGasUsed: &blob,
ExcessBlobGas: &excess,
},
ParentBeaconBlockRoot: common.Hash{0x55},
ParentBeaconBlockRoot: &common.Hash{0x55},
}
resp = sszPost(t, srv, "/osaka/payloads", penv, osaka)
if resp.StatusCode != 200 {