diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go index deada06166c5..cf7bd9ee3fa5 100644 --- a/beacon/engine/gen_epe.go +++ b/beacon/engine/gen_epe.go @@ -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"` diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 76bfd22a2367..ddb276ab0914 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -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[:]) } } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index b222470228a2..17299d92963d 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -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 { diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 703f1b0881be..0df612a69550 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -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 {