From d69b20bc4ed3aa3fb6197622f68c66835b403a77 Mon Sep 17 00:00:00 2001 From: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com> Date: Sun, 6 Aug 2023 11:54:14 +0200 Subject: [PATCH] Support engine_forkchoiceUpdatedV3 with ParentBeaconBlockRoot (EIP-4788) (#7969) Prerequisites: https://github.com/ledgerwatch/interfaces/pull/187 & https://github.com/ledgerwatch/erigon-lib/pull/1069. Also implement https://github.com/ethereum/execution-apis/pull/426. --- README.md | 2 +- .../execution_client/rpc_helper/methods.go | 1 + cmd/rpcdaemon/README.md | 1 + core/block_builder_parameters.go | 7 ++-- eth/stagedsync/stage_mining_create_block.go | 1 + go.mod | 2 +- go.sum | 4 +- rpc/errors.go | 7 ++++ turbo/engineapi/engine_server.go | 40 +++++++++++++------ turbo/execution/eth1/block_building.go | 12 +++++- 10 files changed, 56 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 334b44740..1687b7fbc 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ Erigon can be used as an Execution Layer (EL) for Consensus Layer clients (CL). If your CL client is on a different device, add `--authrpc.addr 0.0.0.0` ([Engine API] listens on localhost by default) as well as `--authrpc.vhosts ` where `` is your source host or `any`. -[Engine API]: https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md +[Engine API]: https://github.com/ethereum/execution-apis/blob/main/src/engine In order to establish a secure connection between the Consensus Layer and the Execution Layer, a JWT secret key is automatically generated. diff --git a/cl/phase1/execution_client/rpc_helper/methods.go b/cl/phase1/execution_client/rpc_helper/methods.go index e41b3f163..68f039729 100644 --- a/cl/phase1/execution_client/rpc_helper/methods.go +++ b/cl/phase1/execution_client/rpc_helper/methods.go @@ -6,3 +6,4 @@ const EngineNewPayloadV3 = "engine_newPayloadV3" const ForkChoiceUpdatedV1 = "engine_forkchoiceUpdatedV1" const ForkChoiceUpdatedV2 = "engine_forkchoiceUpdatedV2" +const ForkChoiceUpdatedV3 = "engine_forkchoiceUpdatedV3" diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index e6634dffd..116ef65d3 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -276,6 +276,7 @@ The following table shows the current implementation status of Erigon's RPC daem | engine_newPayloadV3 | Yes | | | engine_forkchoiceUpdatedV1 | Yes | | | engine_forkchoiceUpdatedV2 | Yes | | +| engine_forkchoiceUpdatedV3 | Yes | | | engine_getPayloadV1 | Yes | | | engine_getPayloadV2 | Yes | | | engine_getPayloadV3 | Yes | | diff --git a/core/block_builder_parameters.go b/core/block_builder_parameters.go index 4fc477410..936724c26 100644 --- a/core/block_builder_parameters.go +++ b/core/block_builder_parameters.go @@ -7,12 +7,13 @@ import ( ) // Parameters for PoS block building -// See also https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv2 +// See also https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3 type BlockBuilderParameters struct { + PayloadId uint64 ParentHash libcommon.Hash Timestamp uint64 PrevRandao libcommon.Hash SuggestedFeeRecipient libcommon.Address - Withdrawals []*types.Withdrawal - PayloadId uint64 + Withdrawals []*types.Withdrawal // added in Shapella (EIP-4895) + ParentBeaconBlockRoot *libcommon.Hash // added in Dencun (EIP-4788) } diff --git a/eth/stagedsync/stage_mining_create_block.go b/eth/stagedsync/stage_mining_create_block.go index 294bfa2cf..75247d425 100644 --- a/eth/stagedsync/stage_mining_create_block.go +++ b/eth/stagedsync/stage_mining_create_block.go @@ -193,6 +193,7 @@ func SpawnMiningCreateBlockStage(s *StageState, tx kv.RwTx, cfg MiningCreateBloc if cfg.blockBuilderParameters != nil { header.MixDigest = cfg.blockBuilderParameters.PrevRandao + header.ParentBeaconBlockRoot = cfg.blockBuilderParameters.ParentBeaconBlockRoot current.Header = header current.Uncles = nil diff --git a/go.mod b/go.mod index 43ace06e8..f39a02cf4 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ledgerwatch/erigon go 1.19 require ( - github.com/ledgerwatch/erigon-lib v0.0.0-20230806000623-e9a42e443c30 + github.com/ledgerwatch/erigon-lib v0.0.0-20230806094003-563a68124b44 github.com/ledgerwatch/erigon-snapshot v1.2.1-0.20230622075030-1d69651854c2 github.com/ledgerwatch/log/v3 v3.8.0 github.com/ledgerwatch/secp256k1 v1.0.0 diff --git a/go.sum b/go.sum index 1801f442b..b9ee1d66e 100644 --- a/go.sum +++ b/go.sum @@ -497,8 +497,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170224010052-a616ab194758 h1:0D5M2HQSGD3PYPwICLl+/9oulQauOuETfgFvhBDffs0= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/ledgerwatch/erigon-lib v0.0.0-20230806000623-e9a42e443c30 h1:FdZJ/lz6Uv936nQe/n7tTVc/VXcdXgipC5oPi31TEXk= -github.com/ledgerwatch/erigon-lib v0.0.0-20230806000623-e9a42e443c30/go.mod h1:g8v1BUhWTrK1mDMLLzFYMmLfqmfrqQmXyCPxRiBJ7iA= +github.com/ledgerwatch/erigon-lib v0.0.0-20230806094003-563a68124b44 h1:5tmiUuLlj94snkO1Ljq12dmqBDG+ncO2IQNPe9fU0HA= +github.com/ledgerwatch/erigon-lib v0.0.0-20230806094003-563a68124b44/go.mod h1:vMETmlckriMRtrg81+YGcmA4/V3XFmjScMqjCojPr3g= github.com/ledgerwatch/erigon-snapshot v1.2.1-0.20230622075030-1d69651854c2 h1:Ls2itRGHMOr2PbHRDA4g1HH8HQdwfJhRVfMPEaLQe94= github.com/ledgerwatch/erigon-snapshot v1.2.1-0.20230622075030-1d69651854c2/go.mod h1:3AuPxZc85jkehh/HA9h8gabv5MSi3kb/ddtzBsTVJFo= github.com/ledgerwatch/log/v3 v3.8.0 h1:gCpp7uGtIerEz1jKVPeDnbIopFPud9ZnCpBLlLBGqPU= diff --git a/rpc/errors.go b/rpc/errors.go index b6f8f2ff6..83f9c0313 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -74,6 +74,13 @@ func (e *InvalidParamsError) ErrorCode() int { return -32602 } func (e *InvalidParamsError) Error() string { return e.Message } +// mismatch between the Engine API method version and the fork +type UnsupportedForkError struct{ Message string } + +func (e *UnsupportedForkError) ErrorCode() int { return -38005 } + +func (e *UnsupportedForkError) Error() string { return e.Message } + type CustomError struct { Code int Message string diff --git a/turbo/engineapi/engine_server.go b/turbo/engineapi/engine_server.go index 8c73592fd..56747477b 100644 --- a/turbo/engineapi/engine_server.go +++ b/turbo/engineapi/engine_server.go @@ -115,7 +115,7 @@ func (s *EngineServer) checkWithdrawalsPresence(time uint64, withdrawals []*type // EngineNewPayload validates and possibly executes payload func (s *EngineServer) newPayload(ctx context.Context, req *engine_types.ExecutionPayload, - expectedBlobHashes []libcommon.Hash, version clparams.StateVersion, + expectedBlobHashes []libcommon.Hash, parentBeaconBlockRoot *libcommon.Hash, version clparams.StateVersion, ) (*engine_types.PayloadStatus, error) { var bloom types.Bloom copy(bloom[:], req.LogsBloom) @@ -160,16 +160,22 @@ func (s *EngineServer) newPayload(ctx context.Context, req *engine_types.Executi if version >= clparams.DenebVersion { header.BlobGasUsed = (*uint64)(req.BlobGasUsed) header.ExcessBlobGas = (*uint64)(req.ExcessBlobGas) + header.ParentBeaconBlockRoot = parentBeaconBlockRoot } - if !s.config.IsCancun(header.Time) && (header.BlobGasUsed != nil || header.ExcessBlobGas != nil) { - return nil, &rpc.InvalidParamsError{Message: "blobGasUsed/excessBlobGas present before Cancun"} + if (!s.config.IsCancun(header.Time) && version >= clparams.DenebVersion) || + (s.config.IsCancun(header.Time) && version < clparams.DenebVersion) { + return nil, &rpc.UnsupportedForkError{Message: "Unsupported fork"} } if s.config.IsCancun(header.Time) && (header.BlobGasUsed == nil || header.ExcessBlobGas == nil) { return nil, &rpc.InvalidParamsError{Message: "blobGasUsed/excessBlobGas missing"} } + if s.config.IsCancun(header.Time) && header.ParentBeaconBlockRoot == nil { + return nil, &rpc.InvalidParamsError{Message: "parentBeaconBlockRoot missing"} + } + blockHash := req.BlockHash if header.Hash() != blockHash { s.logger.Error("[NewPayload] invalid block hash", "stated", blockHash, "actual", header.Hash()) @@ -428,6 +434,12 @@ func (s *EngineServer) forkchoiceUpdated(ctx context.Context, forkchoiceState *e return nil, fmt.Errorf("execution layer not running as a proposer. enable proposer by taking out the --proposer.disable flag on startup") } + timestamp := uint64(payloadAttributes.Timestamp) + if (!s.config.IsCancun(timestamp) && version >= clparams.DenebVersion) || + (s.config.IsCancun(timestamp) && version < clparams.DenebVersion) { + return nil, &rpc.UnsupportedForkError{Message: "Unsupported fork"} + } + headHeader := s.chainRW.GetHeaderByHash(forkchoiceState.HeadHash) if headHeader.Hash() != forkchoiceState.HeadHash { @@ -443,21 +455,25 @@ func (s *EngineServer) forkchoiceUpdated(ctx context.Context, forkchoiceState *e return &engine_types.ForkChoiceUpdatedResponse{PayloadStatus: status}, nil } - if headHeader.Time >= uint64(payloadAttributes.Timestamp) { + if headHeader.Time >= timestamp { return nil, &engine_helpers.InvalidPayloadAttributesErr } req := &execution.AssembleBlockRequest{ - ParentHash: gointerfaces.ConvertHashToH256(forkchoiceState.HeadHash), - Timestamp: uint64(payloadAttributes.Timestamp), - MixDigest: gointerfaces.ConvertHashToH256(payloadAttributes.PrevRandao), - SuggestedFeeRecipent: gointerfaces.ConvertAddressToH160(payloadAttributes.SuggestedFeeRecipient), + ParentHash: gointerfaces.ConvertHashToH256(forkchoiceState.HeadHash), + Timestamp: timestamp, + PrevRandao: gointerfaces.ConvertHashToH256(payloadAttributes.PrevRandao), + SuggestedFeeRecipient: gointerfaces.ConvertAddressToH160(payloadAttributes.SuggestedFeeRecipient), } if version >= clparams.CapellaVersion { req.Withdrawals = engine_types.ConvertWithdrawalsToRpc(payloadAttributes.Withdrawals) } + if version >= clparams.DenebVersion { + req.ParentBeaconBlockRoot = gointerfaces.ConvertHashToH256(payloadAttributes.ParentBeaconBlockRoot) + } + resp, err := s.executionService.AssembleBlock(ctx, req) if err != nil { return nil, err @@ -564,26 +580,26 @@ func (e *EngineServer) ForkchoiceUpdatedV2(ctx context.Context, forkChoiceState } func (e *EngineServer) ForkchoiceUpdatedV3(ctx context.Context, forkChoiceState *engine_types.ForkChoiceState, payloadAttributes *engine_types.PayloadAttributes) (*engine_types.ForkChoiceUpdatedResponse, error) { - return e.forkchoiceUpdated(ctx, forkChoiceState, payloadAttributes, clparams.CapellaVersion) + return e.forkchoiceUpdated(ctx, forkChoiceState, payloadAttributes, clparams.DenebVersion) } // NewPayloadV1 processes new payloads (blocks) from the beacon chain without withdrawals. // See https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_newpayloadv1 func (e *EngineServer) NewPayloadV1(ctx context.Context, payload *engine_types.ExecutionPayload) (*engine_types.PayloadStatus, error) { - return e.newPayload(ctx, payload, nil, clparams.BellatrixVersion) + return e.newPayload(ctx, payload, nil, nil, clparams.BellatrixVersion) } // NewPayloadV2 processes new payloads (blocks) from the beacon chain with withdrawals. // See https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_newpayloadv2 func (e *EngineServer) NewPayloadV2(ctx context.Context, payload *engine_types.ExecutionPayload) (*engine_types.PayloadStatus, error) { - return e.newPayload(ctx, payload, nil, clparams.CapellaVersion) + return e.newPayload(ctx, payload, nil, nil, clparams.CapellaVersion) } // NewPayloadV3 processes new payloads (blocks) from the beacon chain with withdrawals & blob gas. // See https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_newpayloadv3 func (e *EngineServer) NewPayloadV3(ctx context.Context, payload *engine_types.ExecutionPayload, expectedBlobHashes []libcommon.Hash, parentBeaconBlockRoot *libcommon.Hash) (*engine_types.PayloadStatus, error) { - return e.newPayload(ctx, payload, expectedBlobHashes, clparams.DenebVersion) + return e.newPayload(ctx, payload, expectedBlobHashes, parentBeaconBlockRoot, clparams.DenebVersion) } // Receives consensus layer's transition configuration and checks if the execution layer has the correct configuration. diff --git a/turbo/execution/eth1/block_building.go b/turbo/execution/eth1/block_building.go index f710dc9c9..cbd161ec0 100644 --- a/turbo/execution/eth1/block_building.go +++ b/turbo/execution/eth1/block_building.go @@ -6,9 +6,12 @@ import ( "reflect" "github.com/holiman/uint256" + + libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/gointerfaces" "github.com/ledgerwatch/erigon-lib/gointerfaces/execution" types2 "github.com/ledgerwatch/erigon-lib/gointerfaces/types" + "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/types" @@ -49,8 +52,8 @@ func (e *EthereumExecutionModule) AssembleBlock(ctx context.Context, req *execut param := core.BlockBuilderParameters{ ParentHash: gointerfaces.ConvertH256ToHash(req.ParentHash), Timestamp: req.Timestamp, - PrevRandao: gointerfaces.ConvertH256ToHash(req.MixDigest), - SuggestedFeeRecipient: gointerfaces.ConvertH160toAddress(req.SuggestedFeeRecipent), + PrevRandao: gointerfaces.ConvertH256ToHash(req.PrevRandao), + SuggestedFeeRecipient: gointerfaces.ConvertH160toAddress(req.SuggestedFeeRecipient), Withdrawals: eth1_utils.ConvertWithdrawalsFromRpc(req.Withdrawals), } @@ -58,6 +61,11 @@ func (e *EthereumExecutionModule) AssembleBlock(ctx context.Context, req *execut return nil, err } + if req.ParentBeaconBlockRoot != nil { + pbbr := libcommon.Hash(gointerfaces.ConvertH256ToHash(req.ParentBeaconBlockRoot)) + param.ParentBeaconBlockRoot = &pbbr + } + // First check if we're already building a block with the requested parameters if reflect.DeepEqual(e.lastParameters, ¶m) { e.logger.Info("[ForkChoiceUpdated] duplicate build request")