mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-21 20:26:41 +00:00
update to newest spec
This commit is contained in:
parent
ac6751b845
commit
fcefb32655
8 changed files with 349 additions and 53 deletions
|
|
@ -22,36 +22,85 @@ import (
|
||||||
"github.com/karalabe/ssz"
|
"github.com/karalabe/ssz"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecutionPayloadEnvelopeAmsterdam is the request body of
|
// ExecutionPayloadEnvelopeAmsterdam is the monolithic request body of
|
||||||
// POST /amsterdam/payloads.
|
// 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 {
|
type ExecutionPayloadEnvelopeAmsterdam struct {
|
||||||
Payload *ExecutionPayload
|
Payload *ExecutionPayload
|
||||||
ParentBeaconBlockRoot common.Hash
|
ParentBeaconBlockRoot *common.Hash // Cancun+
|
||||||
ExecutionRequests [][]byte
|
ExecutionRequests [][]byte // Prague+
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExecutionPayloadEnvelopeAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
func (e *ExecutionPayloadEnvelopeAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||||
// offset(payload) + 32(root) + offset(requests)
|
// offset(payload) is always present; the root and the requests offset only
|
||||||
size := uint32(40)
|
// 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 {
|
if fixed {
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
size += ssz.SizeDynamicObject(siz, e.Payload)
|
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
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExecutionPayloadEnvelopeAmsterdam) DefineSSZ(c *ssz.Codec) {
|
func (e *ExecutionPayloadEnvelopeAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||||
ssz.DefineDynamicObjectOffset(c, &e.Payload)
|
ssz.DefineDynamicObjectOffset(c, &e.Payload)
|
||||||
ssz.DefineStaticBytes(c, &e.ParentBeaconBlockRoot)
|
ssz.DefineStaticBytesPointerOnFork(c, &e.ParentBeaconBlockRoot, fromCancun)
|
||||||
ssz.DefineSliceOfDynamicBytesOffset(c, &e.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
|
ssz.DefineSliceOfDynamicBytesOffsetOnFork(c, &e.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
|
||||||
|
|
||||||
ssz.DefineDynamicObjectContent(c, &e.Payload)
|
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
|
// BlobsBundleV1 is the single-proof blob bundle carried by BuiltPayload for
|
||||||
// from Osaka onwards.
|
// 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 {
|
type BlobsBundleV2 struct {
|
||||||
Commitments [][48]byte
|
Commitments [][48]byte
|
||||||
Proofs [][48]byte
|
Proofs [][48]byte
|
||||||
|
|
@ -80,37 +129,67 @@ func (b *BlobsBundleV2) DefineSSZ(c *ssz.Codec) {
|
||||||
ssz.DefineSliceOfStaticObjectsContent(c, &b.Blobs, MaxBlobCommitmentsPerBlock)
|
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 {
|
type BuiltPayloadAmsterdam struct {
|
||||||
Payload *ExecutionPayload
|
Payload *ExecutionPayload
|
||||||
BlockValue *uint256.Int
|
BlockValue *uint256.Int
|
||||||
BlobsBundle *BlobsBundleV2
|
BlobsBundleV1 *BlobsBundleV1 // Cancun, Prague
|
||||||
ExecutionRequests [][]byte
|
BlobsBundleV2 *BlobsBundleV2 // Osaka+
|
||||||
ShouldOverrideBuilder bool
|
ExecutionRequests [][]byte // Prague+
|
||||||
|
ShouldOverrideBuilder *bool // Cancun+
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BuiltPayloadAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
func (p *BuiltPayloadAmsterdam) SizeSSZ(siz *ssz.Sizer, fixed bool) uint32 {
|
||||||
// offset(payload) + 32(value) + offset(bundle) + offset(requests) + 1(override)
|
// offset(payload) + 32(value) are always present; the bundle offset and the
|
||||||
size := uint32(4 + 32 + 4 + 4 + 1)
|
// 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 {
|
if fixed {
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
size += ssz.SizeDynamicObject(siz, p.Payload)
|
size += ssz.SizeDynamicObject(siz, p.Payload)
|
||||||
size += ssz.SizeDynamicObject(siz, p.BlobsBundle)
|
if siz.Fork() >= forkOsaka {
|
||||||
size += ssz.SizeSliceOfDynamicBytes(siz, p.ExecutionRequests)
|
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
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BuiltPayloadAmsterdam) DefineSSZ(c *ssz.Codec) {
|
func (p *BuiltPayloadAmsterdam) DefineSSZ(c *ssz.Codec) {
|
||||||
ssz.DefineDynamicObjectOffset(c, &p.Payload)
|
ssz.DefineDynamicObjectOffset(c, &p.Payload)
|
||||||
ssz.DefineUint256(c, &p.BlockValue)
|
ssz.DefineUint256(c, &p.BlockValue)
|
||||||
ssz.DefineDynamicObjectOffset(c, &p.BlobsBundle)
|
ssz.DefineDynamicObjectOffsetOnFork(c, &p.BlobsBundleV1, cancunToOsaka)
|
||||||
ssz.DefineSliceOfDynamicBytesOffset(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
|
ssz.DefineDynamicObjectOffsetOnFork(c, &p.BlobsBundleV2, fromOsaka)
|
||||||
ssz.DefineBool(c, &p.ShouldOverrideBuilder)
|
ssz.DefineSliceOfDynamicBytesOffsetOnFork(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
|
||||||
|
ssz.DefineBoolPointerOnFork(c, &p.ShouldOverrideBuilder, fromCancun)
|
||||||
|
|
||||||
ssz.DefineDynamicObjectContent(c, &p.Payload)
|
ssz.DefineDynamicObjectContent(c, &p.Payload)
|
||||||
ssz.DefineDynamicObjectContent(c, &p.BlobsBundle)
|
ssz.DefineDynamicObjectContentOnFork(c, &p.BlobsBundleV1, cancunToOsaka)
|
||||||
ssz.DefineSliceOfDynamicBytesContent(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest)
|
ssz.DefineDynamicObjectContentOnFork(c, &p.BlobsBundleV2, fromOsaka)
|
||||||
|
ssz.DefineSliceOfDynamicBytesContentOnFork(c, &p.ExecutionRequests, MaxExecutionRequestsPerPayload, MaxBytesPerExecutionRequest, fromPrague)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForkchoiceUpdateAmsterdam is the request body of POST /amsterdam/forkchoice.
|
// ForkchoiceUpdateAmsterdam is the request body of POST /amsterdam/forkchoice.
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,19 @@ const (
|
||||||
forkAmsterdam = ssz.ForkPectra + 2
|
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
|
// 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
|
// the codec multiplexes on. The bool is false for forks that predate the
|
||||||
// Engine API (and thus have no SSZ wire representation here).
|
// Engine API (and thus have no SSZ wire representation here).
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,13 @@ var (
|
||||||
fromCancun = ssz.ForkFilter{Added: ssz.ForkDencun}
|
fromCancun = ssz.ForkFilter{Added: ssz.ForkDencun}
|
||||||
// beacon-root attribute, introduced in Cancun (Dencun).
|
// beacon-root attribute, introduced in Cancun (Dencun).
|
||||||
// (shares the Cancun boundary but kept named for clarity at call sites)
|
// (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.
|
// bal + slot group, introduced in Amsterdam.
|
||||||
fromAmsterdam = ssz.ForkFilter{Added: forkAmsterdam}
|
fromAmsterdam = ssz.ForkFilter{Added: forkAmsterdam}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package ssz
|
package ssz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -157,3 +158,157 @@ func TestForkchoiceUpdateAmsterdamRoundtrip(t *testing.T) {
|
||||||
}
|
}
|
||||||
encDecOnFork(t, bare, forkAmsterdam, func() *ForkchoiceUpdateAmsterdam { return new(ForkchoiceUpdateAmsterdam) })
|
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])
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func (b *Blob) DefineSSZ(c *ssz.Codec) {
|
||||||
ssz.DefineCheckedStaticBytes(c, &b.Bytes, BytesPerBlob)
|
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.
|
// rationale as Blob.
|
||||||
type BlobCell struct {
|
type BlobCell struct {
|
||||||
Bytes []byte
|
Bytes []byte
|
||||||
|
|
@ -59,7 +59,7 @@ func (b *Bitvector128) DefineSSZ(c *ssz.Codec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionalBlobCell models `Optional[ByteVector[BYTES_PER_CELL]]` =
|
// 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 {
|
type OptionalBlobCell struct {
|
||||||
Cells []*BlobCell
|
Cells []*BlobCell
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,42 +56,83 @@ func (rt *Router) handleGetPayload(w http.ResponseWriter, r *http.Request, fork
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildBuiltPayloadAmsterdam packages an engine.ExecutionPayloadEnvelope into
|
// buildBuiltPayloadAmsterdam packages an engine.ExecutionPayloadEnvelope into
|
||||||
// the SSZ BuiltPayload shape. BlockValue/BlobsBundle/Requests come straight
|
// the SSZ BuiltPayload shape for the URL fork. BlockValue/Requests come straight
|
||||||
// across; the inner payload goes through the SSZ converter.
|
// 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 {
|
func buildBuiltPayloadAmsterdam(env *engine.ExecutionPayloadEnvelope, sf ssz.Fork) *sszt.BuiltPayloadAmsterdam {
|
||||||
out := &sszt.BuiltPayloadAmsterdam{
|
out := &sszt.BuiltPayloadAmsterdam{
|
||||||
Payload: sszt.ExecutionPayloadFromEngine(env.ExecutionPayload, sf),
|
Payload: sszt.ExecutionPayloadFromEngine(env.ExecutionPayload, sf),
|
||||||
BlockValue: new(uint256.Int),
|
BlockValue: new(uint256.Int),
|
||||||
ExecutionRequests: env.Requests,
|
|
||||||
ShouldOverrideBuilder: env.Override,
|
|
||||||
}
|
}
|
||||||
if env.BlockValue != nil {
|
if env.BlockValue != nil {
|
||||||
out.BlockValue.SetFromBig(env.BlockValue)
|
out.BlockValue.SetFromBig(env.BlockValue)
|
||||||
}
|
}
|
||||||
if env.BlobsBundle != nil {
|
// execution_requests and should_override_builder exist from Prague and
|
||||||
out.BlobsBundle = convertBlobsBundle(env.BlobsBundle)
|
// Cancun respectively; the codec gates them, so it is safe to always set
|
||||||
} else {
|
// the values the EL produced — the gated-off forks simply ignore them.
|
||||||
out.BlobsBundle = new(sszt.BlobsBundleV2)
|
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
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertBlobsBundle copies the JSON BlobsBundle into the SSZ V2 layout.
|
// convertBlobsBundleV2 copies the JSON BlobsBundle into the SSZ V2 (cell-proof)
|
||||||
// Inputs are length-validated by the caller's miner pipeline.
|
// layout. Inputs are length-validated by the caller's miner pipeline.
|
||||||
func convertBlobsBundle(b *engine.BlobsBundle) *sszt.BlobsBundleV2 {
|
func convertBlobsBundleV2(b *engine.BlobsBundle) *sszt.BlobsBundleV2 {
|
||||||
out := &sszt.BlobsBundleV2{
|
out := &sszt.BlobsBundleV2{
|
||||||
Commitments: make([][48]byte, len(b.Commitments)),
|
Commitments: make([][48]byte, len(b.Commitments)),
|
||||||
Proofs: make([][48]byte, len(b.Proofs)),
|
Proofs: make([][48]byte, len(b.Proofs)),
|
||||||
Blobs: make([]*sszt.Blob, len(b.Blobs)),
|
Blobs: make([]*sszt.Blob, len(b.Blobs)),
|
||||||
}
|
}
|
||||||
for i, c := range b.Commitments {
|
fillBundle(out.Commitments, out.Proofs, out.Blobs, b)
|
||||||
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...)}
|
|
||||||
}
|
|
||||||
return out
|
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...)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,9 @@ func (rt *Router) handleNewPayload(w http.ResponseWriter, r *http.Request, fork
|
||||||
writeProblem(w, http.StatusUnprocessableEntity, ErrInvalidBody, err.Error())
|
writeProblem(w, http.StatusUnprocessableEntity, ErrInvalidBody, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
root := env.ParentBeaconBlockRoot
|
// ParentBeaconBlockRoot is absent (nil) for Paris/Shanghai and present from
|
||||||
status, err := rt.backend.NewPayload(r.Context(), *data, versionedHashes, &root, env.ExecutionRequests)
|
// 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 {
|
if err != nil {
|
||||||
mapBackendErr(w, err)
|
mapBackendErr(w, err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ func TestRouterNewPayload(t *testing.T) {
|
||||||
ExcessBlobGas: &excess,
|
ExcessBlobGas: &excess,
|
||||||
SlotNumber: &slot,
|
SlotNumber: &slot,
|
||||||
},
|
},
|
||||||
ParentBeaconBlockRoot: common.Hash{0x55},
|
ParentBeaconBlockRoot: &common.Hash{0x55},
|
||||||
}
|
}
|
||||||
resp := sszPost(t, srv, "/amsterdam/payloads", env, amsterdam)
|
resp := sszPost(t, srv, "/amsterdam/payloads", env, amsterdam)
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
|
|
@ -257,7 +257,7 @@ func TestRouterAdvertisedForkRoutable(t *testing.T) {
|
||||||
BlobGasUsed: &blob,
|
BlobGasUsed: &blob,
|
||||||
ExcessBlobGas: &excess,
|
ExcessBlobGas: &excess,
|
||||||
},
|
},
|
||||||
ParentBeaconBlockRoot: common.Hash{0x55},
|
ParentBeaconBlockRoot: &common.Hash{0x55},
|
||||||
}
|
}
|
||||||
resp = sszPost(t, srv, "/osaka/payloads", penv, osaka)
|
resp = sszPost(t, srv, "/osaka/payloads", penv, osaka)
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue