erigon-pulse/turbo/engineapi/engine_server_experimental.go

712 lines
26 KiB
Go

package engineapi
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"math/big"
"sync"
"time"
"github.com/ledgerwatch/erigon-lib/gointerfaces"
libstate "github.com/ledgerwatch/erigon-lib/state"
"github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/common/hexutility"
"github.com/ledgerwatch/erigon-lib/gointerfaces/execution"
"github.com/ledgerwatch/erigon-lib/gointerfaces/txpool"
"github.com/ledgerwatch/erigon-lib/kv"
"github.com/ledgerwatch/erigon-lib/kv/kvcache"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon/cl/clparams"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli"
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/cli/httpcfg"
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/consensus"
"github.com/ledgerwatch/erigon/consensus/merge"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/engineapi/engine_block_downloader"
"github.com/ledgerwatch/erigon/turbo/engineapi/engine_helpers"
"github.com/ledgerwatch/erigon/turbo/engineapi/engine_types"
"github.com/ledgerwatch/erigon/turbo/jsonrpc"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
"github.com/ledgerwatch/erigon/turbo/services"
"github.com/ledgerwatch/erigon/turbo/stages/headerdownload"
)
type EngineServerExperimental struct {
hd *headerdownload.HeaderDownload
blockDownloader *engine_block_downloader.EngineBlockDownloader
config *chain.Config
// Block proposing for proof-of-stake
proposing bool
test bool
executionService execution.ExecutionClient
ctx context.Context
lock sync.Mutex
logger log.Logger
db kv.RoDB
blockReader services.FullBlockReader
}
func NewEngineServerExperimental(ctx context.Context, logger log.Logger, config *chain.Config, executionService execution.ExecutionClient,
db kv.RoDB, blockReader services.FullBlockReader, hd *headerdownload.HeaderDownload,
blockDownloader *engine_block_downloader.EngineBlockDownloader, test bool, proposing bool) *EngineServerExperimental {
return &EngineServerExperimental{
ctx: ctx,
logger: logger,
config: config,
executionService: executionService,
blockDownloader: blockDownloader,
db: db,
blockReader: blockReader,
proposing: proposing,
hd: hd,
}
}
func (e *EngineServerExperimental) Start(httpConfig httpcfg.HttpCfg,
filters *rpchelper.Filters, stateCache kvcache.Cache, agg *libstate.AggregatorV3, engineReader consensus.EngineReader,
eth rpchelper.ApiBackend, txPool txpool.TxpoolClient, mining txpool.MiningClient) {
base := jsonrpc.NewBaseApi(filters, stateCache, e.blockReader, agg, httpConfig.WithDatadir, httpConfig.EvmCallTimeout, engineReader, httpConfig.Dirs)
ethImpl := jsonrpc.NewEthAPI(base, e.db, eth, txPool, mining, httpConfig.Gascap, httpConfig.ReturnDataLimit, e.logger)
// engineImpl := NewEngineAPI(base, db, engineBackend)
apiList := []rpc.API{
{
Namespace: "eth",
Public: true,
Service: jsonrpc.EthAPI(ethImpl),
Version: "1.0",
}, {
Namespace: "engine",
Public: true,
Service: EngineAPI(e),
Version: "1.0",
}}
if err := cli.StartRpcServerWithJwtAuthentication(e.ctx, httpConfig, apiList, e.logger); err != nil {
e.logger.Error(err.Error())
}
}
func (s *EngineServerExperimental) stageLoopIsBusy() bool {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
wait, ok := s.hd.BeaconRequestList.WaitForWaiting(ctx)
if !ok {
select {
case <-wait:
return false
case <-ctx.Done():
return true
}
}
return false
}
func (s *EngineServerExperimental) checkWithdrawalsPresence(time uint64, withdrawals []*types.Withdrawal) error {
if !s.config.IsShanghai(time) && withdrawals != nil {
return &rpc.InvalidParamsError{Message: "withdrawals before shanghai"}
}
if s.config.IsShanghai(time) && withdrawals == nil {
return &rpc.InvalidParamsError{Message: "missing withdrawals list"}
}
return nil
}
// EngineNewPayload validates and possibly executes payload
func (s *EngineServerExperimental) newPayload(ctx context.Context, req *engine_types.ExecutionPayload, version clparams.StateVersion) (*engine_types.PayloadStatus, error) {
var bloom types.Bloom
copy(bloom[:], req.LogsBloom)
txs := [][]byte{}
for _, transaction := range req.Transactions {
txs = append(txs, transaction)
}
header := types.Header{
ParentHash: req.ParentHash,
Coinbase: req.FeeRecipient,
Root: req.StateRoot,
Bloom: bloom,
BaseFee: (*big.Int)(req.BaseFeePerGas),
Extra: req.ExtraData,
Number: big.NewInt(int64(req.BlockNumber)),
GasUsed: uint64(req.GasUsed),
GasLimit: uint64(req.GasLimit),
Time: uint64(req.Timestamp),
MixDigest: req.PrevRandao,
UncleHash: types.EmptyUncleHash,
Difficulty: merge.ProofOfStakeDifficulty,
Nonce: merge.ProofOfStakeNonce,
ReceiptHash: req.ReceiptsRoot,
TxHash: types.DeriveSha(types.BinaryTransactions(txs)),
}
var withdrawals []*types.Withdrawal
if version >= clparams.CapellaVersion {
withdrawals = req.Withdrawals
}
if withdrawals != nil {
wh := types.DeriveSha(types.Withdrawals(withdrawals))
header.WithdrawalsHash = &wh
}
if err := s.checkWithdrawalsPresence(header.Time, withdrawals); err != nil {
return nil, err
}
if version >= clparams.DenebVersion {
header.BlobGasUsed = (*uint64)(req.BlobGasUsed)
header.ExcessBlobGas = (*uint64)(req.ExcessBlobGas)
}
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) && (header.BlobGasUsed == nil || header.ExcessBlobGas == nil) {
return nil, &rpc.InvalidParamsError{Message: "blobGasUsed/excessBlobGas missing"}
}
blockHash := req.BlockHash
if header.Hash() != blockHash {
s.logger.Error("[NewPayload] invalid block hash", "stated", blockHash, "actual", header.Hash())
return &engine_types.PayloadStatus{
Status: engine_types.InvalidStatus,
ValidationError: engine_types.NewStringifiedErrorFromString("invalid block hash"),
}, nil
}
for _, txn := range req.Transactions {
if types.TypedTransactionMarshalledAsRlpString(txn) {
s.logger.Warn("[NewPayload] typed txn marshalled as RLP string", "txn", common.Bytes2Hex(txn))
return &engine_types.PayloadStatus{
Status: engine_types.InvalidStatus,
ValidationError: engine_types.NewStringifiedErrorFromString("typed txn marshalled as RLP string"),
}, nil
}
}
transactions, err := types.DecodeTransactions(txs)
if err != nil {
s.logger.Warn("[NewPayload] failed to decode transactions", "err", err)
return &engine_types.PayloadStatus{
Status: engine_types.InvalidStatus,
ValidationError: engine_types.NewStringifiedError(err),
}, nil
}
block := types.NewBlockFromStorage(blockHash, &header, transactions, nil /* uncles */, withdrawals)
possibleStatus, err := s.getQuickPayloadStatusIfPossible(blockHash, uint64(req.BlockNumber), header.ParentHash, nil, true)
if err != nil {
return nil, err
}
if possibleStatus != nil {
return possibleStatus, nil
}
s.lock.Lock()
defer s.lock.Unlock()
s.logger.Debug("[NewPayload] sending block", "height", header.Number, "hash", blockHash)
s.hd.BeaconRequestList.AddPayloadRequest(block)
payloadStatus := <-s.hd.PayloadStatusCh
s.logger.Debug("[NewPayload] got reply", "payloadStatus", payloadStatus)
if payloadStatus.CriticalError != nil {
return nil, payloadStatus.CriticalError
}
return &payloadStatus, nil
}
// Check if we can quickly determine the status of a newPayload or forkchoiceUpdated.
func (s *EngineServerExperimental) getQuickPayloadStatusIfPossible(blockHash libcommon.Hash, blockNumber uint64, parentHash libcommon.Hash, forkchoiceMessage *engine_types.ForkChoiceState, newPayload bool) (*engine_types.PayloadStatus, error) {
// Determine which prefix to use for logs
var prefix string
if newPayload {
prefix = "NewPayload"
} else {
prefix = "ForkChoiceUpdated"
}
if s.config.TerminalTotalDifficulty == nil {
s.logger.Error(fmt.Sprintf("[%s] not a proof-of-stake chain", prefix))
return nil, fmt.Errorf("not a proof-of-stake chain")
}
if s.hd == nil {
return nil, fmt.Errorf("headerdownload is nil")
}
tx, err := s.db.BeginRo(s.ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
// Some Consensus layer clients sometimes sends us repeated FCUs and make Erigon print a gazillion logs.
// E.G teku sometimes will end up spamming fcu on the terminal block if it has not synced to that point.
if forkchoiceMessage != nil &&
forkchoiceMessage.FinalizedBlockHash == rawdb.ReadForkchoiceFinalized(tx) &&
forkchoiceMessage.HeadHash == rawdb.ReadForkchoiceHead(tx) &&
forkchoiceMessage.SafeBlockHash == rawdb.ReadForkchoiceSafe(tx) {
return &engine_types.PayloadStatus{Status: engine_types.ValidStatus, LatestValidHash: &blockHash}, nil
}
header, err := rawdb.ReadHeaderByHash(tx, blockHash)
if err != nil {
return nil, err
}
// Retrieve parent and total difficulty.
var parent *types.Header
var td *big.Int
if newPayload {
parent, err = rawdb.ReadHeaderByHash(tx, parentHash)
if err != nil {
return nil, err
}
td, err = rawdb.ReadTdByHash(tx, parentHash)
} else {
td, err = rawdb.ReadTdByHash(tx, blockHash)
}
if err != nil {
return nil, err
}
if td != nil && td.Cmp(s.config.TerminalTotalDifficulty) < 0 {
s.logger.Warn(fmt.Sprintf("[%s] Beacon Chain request before TTD", prefix), "hash", blockHash)
return &engine_types.PayloadStatus{Status: engine_types.InvalidStatus, LatestValidHash: &libcommon.Hash{}}, nil
}
if !s.hd.POSSync() {
s.logger.Info(fmt.Sprintf("[%s] Still in PoW sync", prefix), "hash", blockHash)
return &engine_types.PayloadStatus{Status: engine_types.SyncingStatus}, nil
}
var canonicalHash libcommon.Hash
if header != nil {
canonicalHash, err = s.blockReader.CanonicalHash(context.Background(), tx, header.Number.Uint64())
}
if err != nil {
return nil, err
}
if newPayload && parent != nil && blockNumber != parent.Number.Uint64()+1 {
s.logger.Warn(fmt.Sprintf("[%s] Invalid block number", prefix), "headerNumber", blockNumber, "parentNumber", parent.Number.Uint64())
s.hd.ReportBadHeaderPoS(blockHash, parent.Hash())
parentHash := parent.Hash()
return &engine_types.PayloadStatus{
Status: engine_types.InvalidStatus,
LatestValidHash: &parentHash,
ValidationError: engine_types.NewStringifiedErrorFromString("invalid block number"),
}, nil
}
// Check if we already determined if the hash is attributed to a previously received invalid header.
bad, lastValidHash := s.hd.IsBadHeaderPoS(blockHash)
if bad {
s.logger.Warn(fmt.Sprintf("[%s] Previously known bad block", prefix), "hash", blockHash)
} else if newPayload {
bad, lastValidHash = s.hd.IsBadHeaderPoS(parentHash)
if bad {
s.logger.Warn(fmt.Sprintf("[%s] Previously known bad block", prefix), "hash", blockHash, "parentHash", parentHash)
}
}
if bad {
s.hd.ReportBadHeaderPoS(blockHash, lastValidHash)
return &engine_types.PayloadStatus{Status: engine_types.InvalidStatus, LatestValidHash: &lastValidHash}, nil
}
// If header is already validated or has a missing parent, you can either return VALID or SYNCING.
if newPayload {
if header != nil && canonicalHash == blockHash {
return &engine_types.PayloadStatus{Status: engine_types.ValidStatus, LatestValidHash: &blockHash}, nil
}
if parent == nil && s.hd.PosStatus() != headerdownload.Idle {
s.logger.Debug(fmt.Sprintf("[%s] Downloading some other PoS blocks", prefix), "hash", blockHash)
return &engine_types.PayloadStatus{Status: engine_types.SyncingStatus}, nil
}
} else {
if header == nil && s.hd.PosStatus() != headerdownload.Idle {
s.logger.Debug(fmt.Sprintf("[%s] Downloading some other PoS stuff", prefix), "hash", blockHash)
return &engine_types.PayloadStatus{Status: engine_types.SyncingStatus}, nil
}
// Following code ensures we skip the fork choice state update if if forkchoiceState.headBlockHash references an ancestor of the head of canonical chain
headHash := rawdb.ReadHeadBlockHash(tx)
if err != nil {
return nil, err
}
// We add the extra restriction blockHash != headHash for the FCU case of canonicalHash == blockHash
// because otherwise (when FCU points to the head) we want go to stage headers
// so that it calls writeForkChoiceHashes.
if blockHash != headHash && canonicalHash == blockHash {
return &engine_types.PayloadStatus{Status: engine_types.ValidStatus, LatestValidHash: &blockHash}, nil
}
}
// If another payload is already commissioned then we just reply with syncing
if s.stageLoopIsBusy() {
s.logger.Debug(fmt.Sprintf("[%s] stage loop is busy", prefix))
return &engine_types.PayloadStatus{Status: engine_types.SyncingStatus}, nil
}
return nil, nil
}
// EngineGetPayload retrieves previously assembled payload (Validators only)
func (s *EngineServerExperimental) getPayload(ctx context.Context, payloadId uint64) (*engine_types.GetPayloadResponse, error) {
if !s.proposing {
return nil, fmt.Errorf("execution layer not running as a proposer. enable proposer by taking out the --proposer.disable flag on startup")
}
if s.config.TerminalTotalDifficulty == nil {
return nil, fmt.Errorf("not a proof-of-stake chain")
}
s.logger.Debug("[GetPayload] acquiring lock")
s.lock.Lock()
defer s.lock.Unlock()
s.logger.Debug("[GetPayload] lock acquired")
resp, err := s.executionService.GetAssembledBlock(ctx, &execution.GetAssembledBlockRequest{
Id: payloadId,
})
if err != nil {
return nil, err
}
if resp.Busy {
log.Warn("Cannot build payload, execution is busy", "payloadId", payloadId)
return nil, &engine_helpers.UnknownPayloadErr
}
// If the service is busy or there is no data for the given id then respond accordingly.
if resp.Data == nil {
log.Warn("Payload not stored", "payloadId", payloadId)
return nil, &engine_helpers.UnknownPayloadErr
}
data := resp.Data
return &engine_types.GetPayloadResponse{
ExecutionPayload: engine_types.ConvertPayloadFromRpc(data.ExecutionPayload),
BlockValue: (*hexutil.Big)(gointerfaces.ConvertH256ToUint256Int(data.BlockValue).ToBig()),
BlobsBundle: engine_types.ConvertBlobsFromRpc(data.BlobsBundle),
}, nil
}
// engineForkChoiceUpdated either states new block head or request the assembling of a new block
func (s *EngineServerExperimental) forkchoiceUpdated(ctx context.Context, forkchoiceState *engine_types.ForkChoiceState, payloadAttributes *engine_types.PayloadAttributes, version clparams.StateVersion,
) (*engine_types.ForkChoiceUpdatedResponse, error) {
forkChoice := &engine_types.ForkChoiceState{
HeadHash: forkchoiceState.HeadHash,
SafeBlockHash: forkchoiceState.SafeBlockHash,
FinalizedBlockHash: forkchoiceState.FinalizedBlockHash,
}
status, err := s.getQuickPayloadStatusIfPossible(forkChoice.HeadHash, 0, libcommon.Hash{}, forkChoice, false)
if err != nil {
return nil, err
}
s.lock.Lock()
defer s.lock.Unlock()
if status == nil {
s.logger.Debug("[ForkChoiceUpdated] sending forkChoiceMessage", "head", forkChoice.HeadHash)
s.hd.BeaconRequestList.AddForkChoiceRequest(forkChoice)
statusDeref := <-s.hd.PayloadStatusCh
status = &statusDeref
s.logger.Debug("[ForkChoiceUpdated] got reply", "payloadStatus", status)
if status.CriticalError != nil {
return nil, status.CriticalError
}
}
// No need for payload building
if payloadAttributes == nil || status.Status != engine_types.ValidStatus {
return &engine_types.ForkChoiceUpdatedResponse{PayloadStatus: status}, nil
}
if !s.proposing {
return nil, fmt.Errorf("execution layer not running as a proposer. enable proposer by taking out the --proposer.disable flag on startup")
}
tx2, err := s.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx2.Rollback()
headHash := rawdb.ReadHeadBlockHash(tx2)
headNumber := rawdb.ReadHeaderNumber(tx2, headHash)
headHeader := rawdb.ReadHeader(tx2, headHash, *headNumber)
tx2.Rollback()
if headHeader.Hash() != forkChoice.HeadHash {
// Per Item 2 of https://github.com/ethereum/execution-apis/blob/v1.0.0-alpha.9/src/engine/specification.md#specification-1:
// Client software MAY skip an update of the forkchoice state and
// MUST NOT begin a payload build process if forkchoiceState.headBlockHash doesn't reference a leaf of the block tree.
// That is, the block referenced by forkchoiceState.headBlockHash is neither the head of the canonical chain nor a block at the tip of any other chain.
// In the case of such an event, client software MUST return
// {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, validationError: null}, payloadId: null}.
s.logger.Warn("Skipping payload building because forkchoiceState.headBlockHash is not the head of the canonical chain",
"forkChoice.HeadBlockHash", forkChoice.HeadHash, "headHeader.Hash", headHeader.Hash())
return &engine_types.ForkChoiceUpdatedResponse{PayloadStatus: status}, nil
}
if headHeader.Time >= uint64(payloadAttributes.Timestamp) {
return nil, &engine_helpers.InvalidPayloadAttributesErr
}
req := &execution.AssembleBlockRequest{
ParentHash: gointerfaces.ConvertHashToH256(forkChoice.HeadHash),
Timestamp: uint64(payloadAttributes.Timestamp),
MixDigest: gointerfaces.ConvertHashToH256(payloadAttributes.PrevRandao),
SuggestedFeeRecipent: gointerfaces.ConvertAddressToH160(payloadAttributes.SuggestedFeeRecipient),
}
if version >= clparams.CapellaVersion {
req.Withdrawals = engine_types.ConvertWithdrawalsToRpc(payloadAttributes.Withdrawals)
}
resp, err := s.executionService.AssembleBlock(ctx, req)
if err != nil {
return nil, err
}
if resp.Busy {
return nil, errors.New("[ForkChoiceUpdated]: execution service is busy, cannot assemble blocks")
}
return &engine_types.ForkChoiceUpdatedResponse{
PayloadStatus: &engine_types.PayloadStatus{
Status: engine_types.ValidStatus,
LatestValidHash: &headHash,
},
PayloadId: engine_types.ConvertPayloadId(resp.Id),
}, nil
}
func (s *EngineServerExperimental) getPayloadBodiesByHash(ctx context.Context, request []libcommon.Hash, _ clparams.StateVersion) ([]*engine_types.ExecutionPayloadBodyV1, error) {
tx, err := s.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
bodies := make([]*engine_types.ExecutionPayloadBodyV1, len(request))
for hashIdx, hash := range request {
block, err := s.blockReader.BlockByHash(ctx, tx, hash)
if err != nil {
return nil, err
}
body, err := extractPayloadBodyFromBlock(block)
if err != nil {
return nil, err
}
bodies[hashIdx] = body
}
return bodies, nil
}
func extractPayloadBodyFromBlock(block *types.Block) (*engine_types.ExecutionPayloadBodyV1, error) {
if block == nil {
return nil, nil
}
txs := block.Transactions()
bdTxs := make([]hexutility.Bytes, len(txs))
for idx, tx := range txs {
var buf bytes.Buffer
if err := tx.MarshalBinary(&buf); err != nil {
return nil, err
} else {
bdTxs[idx] = buf.Bytes()
}
}
return &engine_types.ExecutionPayloadBodyV1{Transactions: bdTxs, Withdrawals: block.Withdrawals()}, nil
}
func (s *EngineServerExperimental) getPayloadBodiesByRange(ctx context.Context, start, count uint64, _ clparams.StateVersion) ([]*engine_types.ExecutionPayloadBodyV1, error) {
tx, err := s.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()
bodies := make([]*engine_types.ExecutionPayloadBodyV1, 0, count)
for i := uint64(0); i < count; i++ {
hash, err := rawdb.ReadCanonicalHash(tx, start+i)
if err != nil {
return nil, err
}
if hash == (libcommon.Hash{}) {
// break early if beyond the last known canonical header
break
}
block, _, err := s.blockReader.BlockWithSenders(ctx, tx, hash, start+i)
if err != nil {
return nil, err
}
body, err := extractPayloadBodyFromBlock(block)
if err != nil {
return nil, err
}
bodies = append(bodies, body)
}
return bodies, nil
}
func (e *EngineServerExperimental) GetPayloadV1(ctx context.Context, payloadId hexutility.Bytes) (*engine_types.ExecutionPayload, error) {
decodedPayloadId := binary.BigEndian.Uint64(payloadId)
log.Info("Received GetPayloadV1", "payloadId", decodedPayloadId)
response, err := e.getPayload(ctx, decodedPayloadId)
if err != nil {
return nil, err
}
return response.ExecutionPayload, nil
}
func (e *EngineServerExperimental) GetPayloadV2(ctx context.Context, payloadID hexutility.Bytes) (*engine_types.GetPayloadResponse, error) {
decodedPayloadId := binary.BigEndian.Uint64(payloadID)
log.Info("Received GetPayloadV2", "payloadId", decodedPayloadId)
return e.getPayload(ctx, decodedPayloadId)
}
func (e *EngineServerExperimental) GetPayloadV3(ctx context.Context, payloadID hexutility.Bytes) (*engine_types.GetPayloadResponse, error) {
decodedPayloadId := binary.BigEndian.Uint64(payloadID)
log.Info("Received GetPayloadV3", "payloadId", decodedPayloadId)
return e.getPayload(ctx, decodedPayloadId)
}
func (e *EngineServerExperimental) ForkchoiceUpdatedV1(ctx context.Context, forkChoiceState *engine_types.ForkChoiceState, payloadAttributes *engine_types.PayloadAttributes) (*engine_types.ForkChoiceUpdatedResponse, error) {
return e.forkchoiceUpdated(ctx, forkChoiceState, payloadAttributes, clparams.BellatrixVersion)
}
func (e *EngineServerExperimental) ForkchoiceUpdatedV2(ctx context.Context, forkChoiceState *engine_types.ForkChoiceState, payloadAttributes *engine_types.PayloadAttributes) (*engine_types.ForkChoiceUpdatedResponse, error) {
return e.forkchoiceUpdated(ctx, forkChoiceState, payloadAttributes, clparams.CapellaVersion)
}
// 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 *EngineServerExperimental) NewPayloadV1(ctx context.Context, payload *engine_types.ExecutionPayload) (*engine_types.PayloadStatus, error) {
return e.newPayload(ctx, payload, 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 *EngineServerExperimental) NewPayloadV2(ctx context.Context, payload *engine_types.ExecutionPayload) (*engine_types.PayloadStatus, error) {
return e.newPayload(ctx, payload, 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/specification.md#engine_newpayloadv3
func (e *EngineServerExperimental) NewPayloadV3(ctx context.Context, payload *engine_types.ExecutionPayload, versionedHashes []libcommon.Hash) (*engine_types.PayloadStatus, error) {
return e.newPayload(ctx, payload, clparams.DenebVersion)
}
// Receives consensus layer's transition configuration and checks if the execution layer has the correct configuration.
// Can also be used to ping the execution layer (heartbeats).
// See https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.1/src/engine/specification.md#engine_exchangetransitionconfigurationv1
func (e *EngineServerExperimental) ExchangeTransitionConfigurationV1(ctx context.Context, beaconConfig *engine_types.TransitionConfiguration) (*engine_types.TransitionConfiguration, error) {
terminalTotalDifficulty := e.config.TerminalTotalDifficulty
if terminalTotalDifficulty == nil {
return nil, fmt.Errorf("the execution layer doesn't have a terminal total difficulty. expected: %v", beaconConfig.TerminalTotalDifficulty)
}
if terminalTotalDifficulty.Cmp((*big.Int)(beaconConfig.TerminalTotalDifficulty)) != 0 {
return nil, fmt.Errorf("the execution layer has a wrong terminal total difficulty. expected %v, but instead got: %d", beaconConfig.TerminalTotalDifficulty, terminalTotalDifficulty)
}
return &engine_types.TransitionConfiguration{
TerminalTotalDifficulty: (*hexutil.Big)(terminalTotalDifficulty),
TerminalBlockHash: libcommon.Hash{},
TerminalBlockNumber: (*hexutil.Big)(libcommon.Big0),
}, nil
}
func (e *EngineServerExperimental) GetPayloadBodiesByHashV1(ctx context.Context, hashes []libcommon.Hash) ([]*engine_types.ExecutionPayloadBodyV1, error) {
if len(hashes) > 1024 {
return nil, &engine_helpers.TooLargeRequestErr
}
return e.getPayloadBodiesByHash(ctx, hashes, clparams.DenebVersion)
}
func (e *EngineServerExperimental) GetPayloadBodiesByRangeV1(ctx context.Context, start, count hexutil.Uint64) ([]*engine_types.ExecutionPayloadBodyV1, error) {
if start == 0 || count == 0 {
return nil, &rpc.InvalidParamsError{Message: fmt.Sprintf("invalid start or count, start: %v count: %v", start, count)}
}
if count > 1024 {
return nil, &engine_helpers.TooLargeRequestErr
}
return e.getPayloadBodiesByRange(ctx, uint64(start), uint64(count), clparams.CapellaVersion)
}
var ourCapabilities = []string{
"engine_forkchoiceUpdatedV1",
"engine_forkchoiceUpdatedV2",
"engine_newPayloadV1",
"engine_newPayloadV2",
// "engine_newPayloadV3",
"engine_getPayloadV1",
"engine_getPayloadV2",
// "engine_getPayloadV3",
"engine_exchangeTransitionConfigurationV1",
"engine_getPayloadBodiesByHashV1",
"engine_getPayloadBodiesByRangeV1",
}
func (e *EngineServerExperimental) ExchangeCapabilities(fromCl []string) []string {
missingOurs := compareCapabilities(fromCl, ourCapabilities)
missingCl := compareCapabilities(ourCapabilities, fromCl)
if len(missingCl) > 0 || len(missingOurs) > 0 {
log.Debug("ExchangeCapabilities mismatches", "cl_unsupported", missingCl, "erigon_unsupported", missingOurs)
}
return ourCapabilities
}
func compareCapabilities(from []string, to []string) []string {
result := make([]string, 0)
for _, f := range from {
found := false
for _, t := range to {
if f == t {
found = true
break
}
}
if !found {
result = append(result, f)
}
}
return result
}