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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
if env.BlockValue != nil {
|
||||
out.BlockValue.SetFromBig(env.BlockValue)
|
||||
}
|
||||
// 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.BlobsBundle = convertBlobsBundle(env.BlobsBundle)
|
||||
out.BlobsBundleV2 = convertBlobsBundleV2(env.BlobsBundle)
|
||||
} else {
|
||||
out.BlobsBundle = new(sszt.BlobsBundleV2)
|
||||
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...)}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue