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.
This commit is contained in:
Andrew Ashikhmin 2023-08-06 11:54:14 +02:00 committed by GitHub
parent b4057da8e6
commit d69b20bc4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 56 additions and 21 deletions

View File

@ -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 <CL host>` where `<CL host>` 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.

View File

@ -6,3 +6,4 @@ const EngineNewPayloadV3 = "engine_newPayloadV3"
const ForkChoiceUpdatedV1 = "engine_forkchoiceUpdatedV1"
const ForkChoiceUpdatedV2 = "engine_forkchoiceUpdatedV2"
const ForkChoiceUpdatedV3 = "engine_forkchoiceUpdatedV3"

View File

@ -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 | |

View File

@ -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)
}

View File

@ -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

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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),
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.

View File

@ -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, &param) {
e.logger.Info("[ForkChoiceUpdated] duplicate build request")