eth/catalyst, beacon/engine: enable BPO and Osaka on stateless APIs (#32636)

Addresses https://github.com/ethereum/go-ethereum/issues/32630

This pull request enables the stateless engine APIs for Osaka and the
following BPOs. Apart from that, a few more descriptions have been added
in the engine APIs, making it easier to follow the spec change.
This commit is contained in:
rjl493456442 2025-09-19 06:16:01 +08:00 committed by GitHub
parent ab95477a65
commit 2a82964727
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 60 additions and 19 deletions

View file

@ -17,7 +17,7 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
BlobsBundle *BlobsBundle `json:"blobsBundle"`
Requests []hexutil.Bytes `json:"executionRequests"`
Override bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness,omitempty"`
@ -42,7 +42,7 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
BlobsBundle *BlobsBundle `json:"blobsBundle"`
Requests []hexutil.Bytes `json:"executionRequests"`
Override *bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness,omitempty"`

View file

@ -33,8 +33,22 @@ import (
type PayloadVersion byte
var (
// PayloadV1 is the identifier of ExecutionPayloadV1 introduced in paris fork.
// https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#executionpayloadv1
PayloadV1 PayloadVersion = 0x1
// PayloadV2 is the identifier of ExecutionPayloadV2 introduced in shanghai fork.
//
// https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#executionpayloadv2
// ExecutionPayloadV2 has the syntax of ExecutionPayloadV1 and appends a
// single field: withdrawals.
PayloadV2 PayloadVersion = 0x2
// PayloadV3 is the identifier of ExecutionPayloadV3 introduced in cancun fork.
//
// https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#executionpayloadv3
// ExecutionPayloadV3 has the syntax of ExecutionPayloadV2 and appends the new
// fields: blobGasUsed and excessBlobGas.
PayloadV3 PayloadVersion = 0x3
)
@ -106,13 +120,18 @@ type StatelessPayloadStatusV1 struct {
type ExecutionPayloadEnvelope struct {
ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"`
BlockValue *big.Int `json:"blockValue" gencodec:"required"`
BlobsBundle *BlobsBundleV1 `json:"blobsBundle"`
BlobsBundle *BlobsBundle `json:"blobsBundle"`
Requests [][]byte `json:"executionRequests"`
Override bool `json:"shouldOverrideBuilder"`
Witness *hexutil.Bytes `json:"witness,omitempty"`
}
type BlobsBundleV1 struct {
// BlobsBundle includes the marshalled sidecar data. Note this structure is
// shared by BlobsBundleV1 and BlobsBundleV2 for the sake of simplicity.
//
// - BlobsBundleV1: proofs contain exactly len(blobs) kzg proofs.
// - BlobsBundleV2: proofs contain exactly CELLS_PER_EXT_BLOB * len(blobs) cell proofs.
type BlobsBundle struct {
Commitments []hexutil.Bytes `json:"commitments"`
Proofs []hexutil.Bytes `json:"proofs"`
Blobs []hexutil.Bytes `json:"blobs"`
@ -125,7 +144,7 @@ type BlobAndProofV1 struct {
type BlobAndProofV2 struct {
Blob hexutil.Bytes `json:"blob"`
CellProofs []hexutil.Bytes `json:"proofs"`
CellProofs []hexutil.Bytes `json:"proofs"` // proofs MUST contain exactly CELLS_PER_EXT_BLOB cell proofs.
}
// JSON type overrides for ExecutionPayloadEnvelope.
@ -327,18 +346,27 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
}
// Add blobs.
bundle := BlobsBundleV1{
bundle := BlobsBundle{
Commitments: make([]hexutil.Bytes, 0),
Blobs: make([]hexutil.Bytes, 0),
Proofs: make([]hexutil.Bytes, 0),
}
for _, sidecar := range sidecars {
for j := range sidecar.Blobs {
bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:]))
bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:]))
bundle.Blobs = append(bundle.Blobs, sidecar.Blobs[j][:])
bundle.Commitments = append(bundle.Commitments, sidecar.Commitments[j][:])
}
// - Before the Osaka fork, only version-0 blob transactions should be packed,
// with the proof length equal to len(blobs).
//
// - After the Osaka fork, only version-1 blob transactions should be packed,
// with the proof length equal to CELLS_PER_EXT_BLOB * len(blobs).
//
// Ideally, length validation should be performed based on the bundle version.
// In practice, this is unnecessary because blob transaction filtering is
// already done during payload construction.
for _, proof := range sidecar.Proofs {
bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(proof[:]))
bundle.Proofs = append(bundle.Proofs, proof[:])
}
}

View file

@ -418,13 +418,21 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.Execu
// GetPayloadV2 returns a cached payload by id.
func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
// executionPayload: ExecutionPayloadV1 | ExecutionPayloadV2 where:
//
// - ExecutionPayloadV1 MUST be returned if the payload timestamp is lower
// than the Shanghai timestamp
//
// - ExecutionPayloadV2 MUST be returned if the payload timestamp is greater
// or equal to the Shanghai timestamp
if !payloadID.Is(engine.PayloadV1, engine.PayloadV2) {
return nil, engine.UnsupportedFork
}
return api.getPayload(payloadID, false)
}
// GetPayloadV3 returns a cached payload by id.
// GetPayloadV3 returns a cached payload by id. This endpoint should only
// be used for the Cancun fork.
func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
@ -432,7 +440,8 @@ func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false)
}
// GetPayloadV4 returns a cached payload by id.
// GetPayloadV4 returns a cached payload by id. This endpoint should only
// be used for the Prague fork.
func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
@ -440,7 +449,11 @@ func (api *ConsensusAPI) GetPayloadV4(payloadID engine.PayloadID) (*engine.Execu
return api.getPayload(payloadID, false)
}
// GetPayloadV5 returns a cached payload by id.
// GetPayloadV5 returns a cached payload by id. This endpoint should only
// be used after the Osaka fork.
//
// This method follows the same specification as engine_getPayloadV4 with
// changes of returning BlobsBundleV2 with BlobSidecar version 1.
func (api *ConsensusAPI) GetPayloadV5(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
if !payloadID.Is(engine.PayloadV3) {
return nil, engine.UnsupportedFork
@ -637,7 +650,7 @@ func (api *ConsensusAPI) NewPayloadV4(params engine.ExecutableData, versionedHas
case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for Prague payloads")
return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {

View file

@ -73,8 +73,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedWithWitnessV3(update engine.Forkchoice
return engine.STATUS_INVALID, attributesErr("missing withdrawals")
case params.BeaconRoot == nil:
return engine.STATUS_INVALID, attributesErr("missing beacon root")
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun or prague payloads")
case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads")
}
}
// TODO(matt): the spec requires that fcu is applied when called on a valid
@ -151,8 +151,8 @@ func (api *ConsensusAPI) NewPayloadWithWitnessV4(params engine.ExecutableData, v
return invalidStatus, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return invalidStatus, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague):
return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague payloads")
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
return invalidStatus, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {
@ -228,8 +228,8 @@ func (api *ConsensusAPI) ExecuteStatelessPayloadV4(params engine.ExecutableData,
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil beaconRoot post-cancun")
case executionRequests == nil:
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, paramsErr("nil executionRequests post-prague")
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV4 must only be called for prague payloads")
case !api.checkFork(params.Timestamp, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5):
return engine.StatelessPayloadStatusV1{Status: engine.INVALID}, unsupportedForkErr("newPayloadV4 must only be called for prague/osaka payloads")
}
requests := convertRequests(executionRequests)
if err := validateRequests(requests); err != nil {