core: eth: implement Kiln-v2 spec (#24506)

* core/beacon: eth/catalyst: updated engine api to new version

* core: implement exchangeTransitionConfig

* core/beacon: prevRandao instead of Random

* eth/catalyst: Fix ExchangeTransitionConfig, add test

* eth/catalyst: stop external miners on TTD reached

* node: implement --authrpc.vhosts flag

* core: allow for config override on non-mainnet networks

* eth/catalyst: fix peters comments

* eth/catalyst: make stop remote sealer more explicit

* eth/catalyst: add log output

* cmd/utils: rename authrpc.host to authrpc.addr

* eth/catalyst: disable the disabling of the miner

* eth: core: remove notion of terminal pow block

* eth: les: more of peters nitpicks
This commit is contained in:
Marius van der Wijden 2022-03-17 16:20:03 +01:00 committed by GitHub
parent 830231c1c4
commit 4f4622bc8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 307 additions and 168 deletions

@ -166,8 +166,9 @@ var (
utils.HTTPListenAddrFlag, utils.HTTPListenAddrFlag,
utils.HTTPPortFlag, utils.HTTPPortFlag,
utils.HTTPCORSDomainFlag, utils.HTTPCORSDomainFlag,
utils.AuthHostFlag, utils.AuthListenFlag,
utils.AuthPortFlag, utils.AuthPortFlag,
utils.AuthVirtualHostsFlag,
utils.JWTSecretFlag, utils.JWTSecretFlag,
utils.HTTPVirtualHostsFlag, utils.HTTPVirtualHostsFlag,
utils.GraphQLEnabledFlag, utils.GraphQLEnabledFlag,

@ -150,8 +150,9 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.WSPathPrefixFlag, utils.WSPathPrefixFlag,
utils.WSAllowedOriginsFlag, utils.WSAllowedOriginsFlag,
utils.JWTSecretFlag, utils.JWTSecretFlag,
utils.AuthHostFlag, utils.AuthListenFlag,
utils.AuthPortFlag, utils.AuthPortFlag,
utils.AuthVirtualHostsFlag,
utils.GraphQLEnabledFlag, utils.GraphQLEnabledFlag,
utils.GraphQLCORSDomainFlag, utils.GraphQLCORSDomainFlag,
utils.GraphQLVirtualHostsFlag, utils.GraphQLVirtualHostsFlag,

@ -527,16 +527,21 @@ var (
Value: ethconfig.Defaults.RPCTxFeeCap, Value: ethconfig.Defaults.RPCTxFeeCap,
} }
// Authenticated RPC HTTP settings // Authenticated RPC HTTP settings
AuthHostFlag = cli.StringFlag{ AuthListenFlag = cli.StringFlag{
Name: "authrpc.host", Name: "authrpc.addr",
Usage: "Listening address for authenticated APIs", Usage: "Listening address for authenticated APIs",
Value: node.DefaultConfig.AuthHost, Value: node.DefaultConfig.AuthAddr,
} }
AuthPortFlag = cli.IntFlag{ AuthPortFlag = cli.IntFlag{
Name: "authrpc.port", Name: "authrpc.port",
Usage: "Listening port for authenticated APIs", Usage: "Listening port for authenticated APIs",
Value: node.DefaultConfig.AuthPort, Value: node.DefaultConfig.AuthPort,
} }
AuthVirtualHostsFlag = cli.StringFlag{
Name: "authrpc.vhosts",
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
Value: strings.Join(node.DefaultConfig.AuthVirtualHosts, ","),
}
JWTSecretFlag = cli.StringFlag{ JWTSecretFlag = cli.StringFlag{
Name: "authrpc.jwtsecret", Name: "authrpc.jwtsecret",
Usage: "JWT secret (or path to a jwt secret) to use for authenticated RPC endpoints", Usage: "JWT secret (or path to a jwt secret) to use for authenticated RPC endpoints",
@ -974,13 +979,18 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
cfg.HTTPPort = ctx.GlobalInt(HTTPPortFlag.Name) cfg.HTTPPort = ctx.GlobalInt(HTTPPortFlag.Name)
} }
if ctx.GlobalIsSet(AuthHostFlag.Name) { if ctx.GlobalIsSet(AuthListenFlag.Name) {
cfg.AuthHost = ctx.GlobalString(AuthHostFlag.Name) cfg.AuthAddr = ctx.GlobalString(AuthListenFlag.Name)
} }
if ctx.GlobalIsSet(AuthPortFlag.Name) { if ctx.GlobalIsSet(AuthPortFlag.Name) {
cfg.AuthPort = ctx.GlobalInt(AuthPortFlag.Name) cfg.AuthPort = ctx.GlobalInt(AuthPortFlag.Name)
} }
if ctx.GlobalIsSet(AuthVirtualHostsFlag.Name) {
cfg.AuthVirtualHosts = SplitAndTrim(ctx.GlobalString(AuthVirtualHostsFlag.Name))
}
if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) { if ctx.GlobalIsSet(HTTPCORSDomainFlag.Name) {
cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name)) cfg.HTTPCors = SplitAndTrim(ctx.GlobalString(HTTPCORSDomainFlag.Name))
} }

@ -549,6 +549,11 @@ func NewShared() *Ethash {
// Close closes the exit channel to notify all backend threads exiting. // Close closes the exit channel to notify all backend threads exiting.
func (ethash *Ethash) Close() error { func (ethash *Ethash) Close() error {
return ethash.StopRemoteSealer()
}
// StopRemoteSealer stops the remote sealer
func (ethash *Ethash) StopRemoteSealer() error {
ethash.closeOnce.Do(func() { ethash.closeOnce.Do(func() {
// Short circuit if the exit channel is not allocated. // Short circuit if the exit channel is not allocated.
if ethash.remote == nil { if ethash.remote == nil {

@ -38,7 +38,13 @@ var (
// - newPayloadV1: if the payload was accepted, but not processed (side chain) // - newPayloadV1: if the payload was accepted, but not processed (side chain)
ACCEPTED = "ACCEPTED" ACCEPTED = "ACCEPTED"
INVALIDBLOCKHASH = "INVALID_BLOCK_HASH"
INVALIDTERMINALBLOCK = "INVALID_TERMINAL_BLOCK"
GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"} GenericServerError = rpc.CustomError{Code: -32000, ValidationError: "Server error"}
UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"} UnknownPayload = rpc.CustomError{Code: -32001, ValidationError: "Unknown payload"}
InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"} InvalidTB = rpc.CustomError{Code: -32002, ValidationError: "Invalid terminal block"}
STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil}
STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil}
) )

@ -16,7 +16,7 @@ var _ = (*payloadAttributesMarshaling)(nil)
func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) { func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) {
type PayloadAttributesV1 struct { type PayloadAttributesV1 struct {
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"random" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
} }
var enc PayloadAttributesV1 var enc PayloadAttributesV1
@ -30,7 +30,7 @@ func (p PayloadAttributesV1) MarshalJSON() ([]byte, error) {
func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error { func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error {
type PayloadAttributesV1 struct { type PayloadAttributesV1 struct {
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
Random *common.Hash `json:"random" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
} }
var dec PayloadAttributesV1 var dec PayloadAttributesV1
@ -42,7 +42,7 @@ func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error {
} }
p.Timestamp = uint64(*dec.Timestamp) p.Timestamp = uint64(*dec.Timestamp)
if dec.Random == nil { if dec.Random == nil {
return errors.New("missing required field 'random' for PayloadAttributesV1") return errors.New("missing required field 'prevRandao' for PayloadAttributesV1")
} }
p.Random = *dec.Random p.Random = *dec.Random
if dec.SuggestedFeeRecipient == nil { if dec.SuggestedFeeRecipient == nil {

@ -19,9 +19,9 @@ func (e ExecutableDataV1) MarshalJSON() ([]byte, error) {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"random" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
@ -60,9 +60,9 @@ func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"random" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
@ -97,7 +97,7 @@ func (e *ExecutableDataV1) UnmarshalJSON(input []byte) error {
} }
e.LogsBloom = *dec.LogsBloom e.LogsBloom = *dec.LogsBloom
if dec.Random == nil { if dec.Random == nil {
return errors.New("missing required field 'random' for ExecutableDataV1") return errors.New("missing required field 'prevRandao' for ExecutableDataV1")
} }
e.Random = *dec.Random e.Random = *dec.Random
if dec.Number == nil { if dec.Number == nil {

@ -31,7 +31,7 @@ import (
// PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74 // PayloadAttributesV1 structure described at https://github.com/ethereum/execution-apis/pull/74
type PayloadAttributesV1 struct { type PayloadAttributesV1 struct {
Timestamp uint64 `json:"timestamp" gencodec:"required"` Timestamp uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"random" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
} }
@ -47,9 +47,9 @@ type ExecutableDataV1 struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom []byte `json:"logsBloom" gencodec:"required"` LogsBloom []byte `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"random" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number uint64 `json:"blockNumber" gencodec:"required"` Number uint64 `json:"blockNumber" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"`
@ -72,14 +72,16 @@ type executableDataMarshaling struct {
Transactions []hexutil.Bytes Transactions []hexutil.Bytes
} }
type ExecutePayloadResponse struct { type PayloadStatusV1 struct {
Status string `json:"status"` Status string `json:"status"`
LatestValidHash common.Hash `json:"latestValidHash"` LatestValidHash *common.Hash `json:"latestValidHash"`
ValidationError *string `json:"validationError"`
} }
type ConsensusValidatedParams struct { type TransitionConfigurationV1 struct {
BlockHash common.Hash `json:"blockHash"` TerminalTotalDifficulty *hexutil.Big `json:"terminalTotalDifficulty"`
Status string `json:"status"` TerminalBlockHash common.Hash `json:"terminalBlockHash"`
TerminalBlockNumber hexutil.Uint64 `json:"terminalBlockNumber"`
} }
// PayloadID is an identifier of the payload build process // PayloadID is an identifier of the payload build process
@ -102,8 +104,8 @@ func (b *PayloadID) UnmarshalText(input []byte) error {
} }
type ForkChoiceResponse struct { type ForkChoiceResponse struct {
Status string `json:"status"` PayloadStatus PayloadStatusV1 `json:"payloadStatus"`
PayloadID *PayloadID `json:"payloadId"` PayloadID *PayloadID `json:"payloadId"`
} }
type ForkchoiceStateV1 struct { type ForkchoiceStateV1 struct {

@ -207,9 +207,6 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
if overrideArrowGlacier != nil { if overrideArrowGlacier != nil {
newcfg.ArrowGlacierBlock = overrideArrowGlacier newcfg.ArrowGlacierBlock = overrideArrowGlacier
} }
if overrideTerminalTotalDifficulty != nil {
newcfg.TerminalTotalDifficulty = overrideTerminalTotalDifficulty
}
if err := newcfg.CheckConfigForkOrder(); err != nil { if err := newcfg.CheckConfigForkOrder(); err != nil {
return newcfg, common.Hash{}, err return newcfg, common.Hash{}, err
} }
@ -219,6 +216,10 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
rawdb.WriteChainConfig(db, stored, newcfg) rawdb.WriteChainConfig(db, stored, newcfg)
return newcfg, stored, nil return newcfg, stored, nil
} }
if overrideTerminalTotalDifficulty != nil {
storedcfg.TerminalTotalDifficulty = overrideTerminalTotalDifficulty
}
// Special case: don't change the existing config of a non-mainnet chain if no new // Special case: don't change the existing config of a non-mainnet chain if no new
// config is supplied. These chains would get AllProtocolChanges (and a compat error) // config is supplied. These chains would get AllProtocolChanges (and a compat error)
// if we just continued here. // if we just continued here.

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -86,10 +87,10 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI {
// If there are payloadAttributes: // If there are payloadAttributes:
// we try to assemble a block with the payloadAttributes and return its payloadID // we try to assemble a block with the payloadAttributes and return its payloadID
func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
log.Trace("Engine API request received", "method", "ForkChoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash)
if update.HeadBlockHash == (common.Hash{}) { if update.HeadBlockHash == (common.Hash{}) {
log.Warn("Forkchoice requested update to zero hash") log.Warn("Forkchoice requested update to zero hash")
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, nil // TODO(karalabe): Why does someone send us this? return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
} }
// Check whether we have the block yet in our database or not. If not, we'll // Check whether we have the block yet in our database or not. If not, we'll
// need to either trigger a sync, or to reject this forkchoice update for a // need to either trigger a sync, or to reject this forkchoice update for a
@ -102,8 +103,8 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
// that should be fixed, not papered over. // that should be fixed, not papered over.
header := api.remoteBlocks.get(update.HeadBlockHash) header := api.remoteBlocks.get(update.HeadBlockHash)
if header == nil { if header == nil {
log.Warn("Forkcoice requested unknown head", "hash", update.HeadBlockHash) log.Warn("Forkchoice requested unknown head", "hash", update.HeadBlockHash)
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, errors.New("head hash never advertised") return beacon.STATUS_SYNCING, nil
} }
// Header advertised via a past newPayload request. Start syncing to it. // Header advertised via a past newPayload request. Start syncing to it.
// Before we do however, make sure any legacy sync in switched off so we // Before we do however, make sure any legacy sync in switched off so we
@ -114,13 +115,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
} }
log.Info("Forkchoice requested sync to new head", "number", header.Number, "hash", header.Hash()) log.Info("Forkchoice requested sync to new head", "number", header.Number, "hash", header.Hash())
if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header); err != nil { if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), header); err != nil {
return beacon.ForkChoiceResponse{Status: beacon.SYNCING}, err return beacon.STATUS_SYNCING, err
} }
return beacon.ForkChoiceResponse{Status: beacon.SYNCING}, nil return beacon.STATUS_SYNCING, nil
} }
// Block is known locally, just sanity check that the beacon client does not // Block is known locally, just sanity check that the beacon client does not
// attempt to push as back to before the merge. // attempt to push us back to before the merge.
if block.Difficulty().BitLen() > 0 { if block.Difficulty().BitLen() > 0 || block.NumberU64() == 0 {
var ( var (
td = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64()) td = api.eth.BlockChain().GetTd(update.HeadBlockHash, block.NumberU64())
ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1) ptd = api.eth.BlockChain().GetTd(block.ParentHash(), block.NumberU64()-1)
@ -128,21 +129,23 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
) )
if td == nil || (block.NumberU64() > 0 && ptd == nil) { if td == nil || (block.NumberU64() > 0 && ptd == nil) {
log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd) log.Error("TDs unavailable for TTD check", "number", block.NumberU64(), "hash", update.HeadBlockHash, "td", td, "parent", block.ParentHash(), "ptd", ptd)
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, errors.New("TDs unavailable for TDD check") return beacon.STATUS_INVALID, errors.New("TDs unavailable for TDD check")
} }
if td.Cmp(ttd) < 0 || (block.NumberU64() > 0 && ptd.Cmp(ttd) >= 0) { if td.Cmp(ttd) < 0 || (block.NumberU64() > 0 && ptd.Cmp(ttd) > 0) {
log.Error("Refusing beacon update to pre-merge", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) log.Error("Refusing beacon update to pre-merge", "number", block.NumberU64(), "hash", update.HeadBlockHash, "diff", block.Difficulty(), "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, errors.New("refusing reorg to pre-merge") return beacon.ForkChoiceResponse{PayloadStatus: beacon.PayloadStatusV1{Status: beacon.INVALIDTERMINALBLOCK}, PayloadID: nil}, nil
} }
} }
// If the head block is already in our canonical chain, the beacon client is
// probably resyncing. Ignore the update. if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) == update.HeadBlockHash { // Block is not canonical, set head.
log.Warn("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().NumberU64()) if err := api.eth.BlockChain().SetChainHead(block); err != nil {
return beacon.ForkChoiceResponse{Status: beacon.VALID}, nil return beacon.STATUS_INVALID, err
} }
if err := api.eth.BlockChain().SetChainHead(block); err != nil { } else {
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, err // If the head block is already in our canonical chain, the beacon client is
// probably resyncing. Ignore the update.
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().NumberU64())
} }
api.eth.SetSynced() api.eth.SetSynced()
@ -152,6 +155,27 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
if merger := api.eth.Merger(); !merger.PoSFinalized() { if merger := api.eth.Merger(); !merger.PoSFinalized() {
merger.FinalizePoS() merger.FinalizePoS()
} }
// TODO (MariusVanDerWijden): If the finalized block is not in our canonical tree, somethings wrong
finalBlock := api.eth.BlockChain().GetBlockByHash(update.FinalizedBlockHash)
if finalBlock == nil {
log.Warn("Final block not available in database", "hash", update.FinalizedBlockHash)
return beacon.STATUS_INVALID, errors.New("final block not available")
} else if rawdb.ReadCanonicalHash(api.eth.ChainDb(), finalBlock.NumberU64()) != update.FinalizedBlockHash {
log.Warn("Final block not in canonical chain", "number", block.NumberU64(), "hash", update.HeadBlockHash)
return beacon.STATUS_INVALID, errors.New("final block not canonical")
}
}
// TODO (MariusVanDerWijden): Check if the safe block hash is in our canonical tree, if not somethings wrong
if update.SafeBlockHash != (common.Hash{}) {
safeBlock := api.eth.BlockChain().GetBlockByHash(update.SafeBlockHash)
if safeBlock == nil {
log.Warn("Safe block not available in database")
return beacon.STATUS_INVALID, errors.New("safe head not available")
}
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), safeBlock.NumberU64()) != update.SafeBlockHash {
log.Warn("Safe block not in canonical chain")
return beacon.STATUS_INVALID, errors.New("safe head not canonical")
}
} }
// If payload generation was requested, create a new block to be potentially // If payload generation was requested, create a new block to be potentially
// sealed by the beacon client. The payload will be requested later, and we // sealed by the beacon client. The payload will be requested later, and we
@ -163,15 +187,50 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV1(update beacon.ForkchoiceStateV1, pa
data, err := api.assembleBlock(update.HeadBlockHash, payloadAttributes) data, err := api.assembleBlock(update.HeadBlockHash, payloadAttributes)
if err != nil { if err != nil {
log.Error("Failed to create sealing payload", "err", err) log.Error("Failed to create sealing payload", "err", err)
return beacon.ForkChoiceResponse{Status: beacon.VALID}, err // Valid as setHead was accepted return api.validForkChoiceResponse(nil), err // valid setHead, invalid payload
} }
id := computePayloadId(update.HeadBlockHash, payloadAttributes) id := computePayloadId(update.HeadBlockHash, payloadAttributes)
api.localBlocks.put(id, data) api.localBlocks.put(id, data)
log.Info("Created payload for sealing", "id", id, "elapsed", time.Since(start)) log.Info("Created payload for sealing", "id", id, "elapsed", time.Since(start))
return beacon.ForkChoiceResponse{Status: beacon.VALID, PayloadID: &id}, nil return api.validForkChoiceResponse(&id), nil
} }
return beacon.ForkChoiceResponse{Status: beacon.VALID}, nil return api.validForkChoiceResponse(nil), nil
}
// validForkChoiceResponse returns the ForkChoiceResponse{VALID}
// with the latest valid hash and an optional payloadID.
func (api *ConsensusAPI) validForkChoiceResponse(id *beacon.PayloadID) beacon.ForkChoiceResponse {
currentHash := api.eth.BlockChain().CurrentBlock().Hash()
return beacon.ForkChoiceResponse{
PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &currentHash},
PayloadID: id,
}
}
// ExchangeTransitionConfigurationV1 checks the given configuration against
// the configuration of the node.
func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config beacon.TransitionConfigurationV1) (*beacon.TransitionConfigurationV1, error) {
if config.TerminalTotalDifficulty == nil {
return nil, errors.New("invalid terminal total difficulty")
}
ttd := api.eth.BlockChain().Config().TerminalTotalDifficulty
if ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 {
log.Warn("Invalid TTD configured", "geth", config.TerminalTotalDifficulty, "beacon", ttd)
return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", config.TerminalTotalDifficulty, ttd)
}
if config.TerminalBlockHash != (common.Hash{}) {
if hash := api.eth.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash {
return &beacon.TransitionConfigurationV1{
TerminalTotalDifficulty: (*hexutil.Big)(ttd),
TerminalBlockHash: config.TerminalBlockHash,
TerminalBlockNumber: config.TerminalBlockNumber,
}, nil
}
return nil, fmt.Errorf("invalid terminal block hash")
}
return &beacon.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil
} }
// GetPayloadV1 returns a cached payload by id. // GetPayloadV1 returns a cached payload by id.
@ -184,18 +243,20 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu
return data, nil return data, nil
} }
// ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // NewPayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { func (api *ConsensusAPI) NewPayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) {
log.Trace("Engine API request received", "method", "ExecutePayload", "number", params.Number, "hash", params.BlockHash) log.Trace("Engine API request received", "method", "ExecutePayload", "number", params.Number, "hash", params.BlockHash)
block, err := beacon.ExecutableDataToBlock(params) block, err := beacon.ExecutableDataToBlock(params)
if err != nil { if err != nil {
return api.invalid(), err log.Debug("Invalid NewPayload params", "params", params, "error", err)
return beacon.PayloadStatusV1{Status: beacon.INVALIDBLOCKHASH}, nil
} }
// If we already have the block locally, ignore the entire execution and just // If we already have the block locally, ignore the entire execution and just
// return a fake success. // return a fake success.
if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil { if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil {
log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)))
return beacon.ExecutePayloadResponse{Status: beacon.VALID, LatestValidHash: block.Hash()}, nil hash := block.Hash()
return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
} }
// If the parent is missing, we - in theory - could trigger a sync, but that // If the parent is missing, we - in theory - could trigger a sync, but that
// would also entail a reorg. That is problematic if multiple sibling blocks // would also entail a reorg. That is problematic if multiple sibling blocks
@ -214,14 +275,14 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco
// some strain from the forkchoice update. // some strain from the forkchoice update.
if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil { if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil {
log.Debug("Payload accepted for sync extension", "number", params.Number, "hash", params.BlockHash) log.Debug("Payload accepted for sync extension", "number", params.Number, "hash", params.BlockHash)
return beacon.ExecutePayloadResponse{Status: beacon.SYNCING, LatestValidHash: api.eth.BlockChain().CurrentBlock().Hash()}, nil return beacon.PayloadStatusV1{Status: beacon.SYNCING}, nil
} }
// Either no beacon sync was started yet, or it rejected the delivered // Either no beacon sync was started yet, or it rejected the delivered
// payload as non-integratable on top of the existing sync. We'll just // payload as non-integratable on top of the existing sync. We'll just
// have to rely on the beacon client to forcefully update the head with // have to rely on the beacon client to forcefully update the head with
// a forkchoice update request. // a forkchoice update request.
log.Warn("Ignoring payload with missing parent", "number", params.Number, "hash", params.BlockHash, "parent", params.ParentHash) log.Warn("Ignoring payload with missing parent", "number", params.Number, "hash", params.BlockHash, "parent", params.ParentHash)
return beacon.ExecutePayloadResponse{Status: beacon.SYNCING, LatestValidHash: common.Hash{}}, nil // TODO(karalabe): Switch to ACCEPTED return beacon.PayloadStatusV1{Status: beacon.ACCEPTED}, nil
} }
// We have an existing parent, do some sanity checks to avoid the beacon client // We have an existing parent, do some sanity checks to avoid the beacon client
// triggering too early // triggering too early
@ -231,11 +292,16 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco
) )
if td.Cmp(ttd) < 0 { if td.Cmp(ttd) < 0 {
log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", td, "ttd", ttd) log.Warn("Ignoring pre-merge payload", "number", params.Number, "hash", params.BlockHash, "td", td, "ttd", ttd)
return api.invalid(), fmt.Errorf("cannot execute payload on top of pre-merge blocks: td %v, ttd %v", td, ttd) return beacon.PayloadStatusV1{Status: beacon.INVALIDTERMINALBLOCK}, nil
}
if block.Time() <= parent.Time() {
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
return api.invalid(errors.New("invalid timestamp")), nil
} }
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number) log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number)
if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil { if err := api.eth.BlockChain().InsertBlockWithoutSetHead(block); err != nil {
return api.invalid(), err log.Warn("NewPayloadV1: inserting block failed", "error", err)
return api.invalid(err), nil
} }
// We've accepted a valid payload from the beacon client. Mark the local // We've accepted a valid payload from the beacon client. Mark the local
// chain transitions to notify other subsystems (e.g. downloader) of the // chain transitions to notify other subsystems (e.g. downloader) of the
@ -244,7 +310,8 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco
merger.ReachTTD() merger.ReachTTD()
api.eth.Downloader().Cancel() api.eth.Downloader().Cancel()
} }
return beacon.ExecutePayloadResponse{Status: beacon.VALID, LatestValidHash: block.Hash()}, nil hash := block.Hash()
return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
} }
// computePayloadId computes a pseudo-random payloadid, based on the parameters. // computePayloadId computes a pseudo-random payloadid, based on the parameters.
@ -261,8 +328,10 @@ func computePayloadId(headBlockHash common.Hash, params *beacon.PayloadAttribute
} }
// invalid returns a response "INVALID" with the latest valid hash set to the current head. // invalid returns a response "INVALID" with the latest valid hash set to the current head.
func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { func (api *ConsensusAPI) invalid(err error) beacon.PayloadStatusV1 {
return beacon.ExecutePayloadResponse{Status: beacon.INVALID, LatestValidHash: api.eth.BlockChain().CurrentHeader().Hash()} currentHash := api.eth.BlockChain().CurrentHeader().Hash()
errorMsg := err.Error()
return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash, ValidationError: &errorMsg}
} }
// assembleBlock creates a new block and returns the "execution // assembleBlock creates a new block and returns the "execution
@ -283,20 +352,3 @@ func (api *ConsensusAPI) insertTransactions(txs types.Transactions) error {
} }
return nil return nil
} }
func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {
// shortcut if we entered PoS already
if api.eth.Merger().PoSFinalized() {
return nil
}
// make sure the parent has enough terminal total difficulty
newHeadBlock := api.eth.BlockChain().GetBlockByHash(head)
if newHeadBlock == nil {
return &beacon.GenericServerError
}
td := api.eth.BlockChain().GetTd(newHeadBlock.Hash(), newHeadBlock.NumberU64())
if td != nil && td.Cmp(api.eth.BlockChain().Config().TerminalTotalDifficulty) < 0 {
return &beacon.InvalidTB
}
return nil
}

@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/beacon"
@ -49,11 +50,12 @@ func generatePreMergeChain(n int) (*core.Genesis, []*types.Block) {
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
config := params.AllEthashProtocolChanges config := params.AllEthashProtocolChanges
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: config, Config: config,
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
ExtraData: []byte("test genesis"), ExtraData: []byte("test genesis"),
Timestamp: 9000, Timestamp: 9000,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: big.NewInt(0),
} }
testNonce := uint64(0) testNonce := uint64(0)
generate := func(i int, g *core.BlockGen) { generate := func(i int, g *core.BlockGen) {
@ -130,8 +132,10 @@ func TestSetHeadBeforeTotalDifficulty(t *testing.T) {
SafeBlockHash: common.Hash{}, SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{}, FinalizedBlockHash: common.Hash{},
} }
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil { if resp, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Errorf("fork choice updated before total terminal difficulty should fail") t.Errorf("fork choice updated should not error: %v", err)
} else if resp.PayloadStatus.Status != beacon.INVALIDTERMINALBLOCK {
t.Errorf("fork choice updated before total terminal difficulty should be INVALID")
} }
} }
@ -277,7 +281,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
newResp, err := api.ExecutePayloadV1(*execData) newResp, err := api.NewPayloadV1(*execData)
if err != nil || newResp.Status != "VALID" { if err != nil || newResp.Status != "VALID" {
t.Fatalf("Failed to insert block: %v", err) t.Fatalf("Failed to insert block: %v", err)
} }
@ -317,7 +321,7 @@ func TestEth2NewBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Failed to convert executable data to block %v", err) t.Fatalf("Failed to convert executable data to block %v", err)
} }
newResp, err := api.ExecutePayloadV1(*execData) newResp, err := api.NewPayloadV1(*execData)
if err != nil || newResp.Status != "VALID" { if err != nil || newResp.Status != "VALID" {
t.Fatalf("Failed to insert block: %v", err) t.Fatalf("Failed to insert block: %v", err)
} }
@ -414,66 +418,108 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block)
} }
func TestFullAPI(t *testing.T) { func TestFullAPI(t *testing.T) {
// TODO (MariusVanDerWijden) TestFullAPI is currently broken, because it tries to reorg genesis, preMergeBlocks := generatePreMergeChain(10)
// before the totalTerminalDifficulty threshold, fixed in upcoming merge-kiln-v2 pr n, ethservice := startEthService(t, genesis, preMergeBlocks)
/* ethservice.Merger().ReachTTD()
genesis, preMergeBlocks := generatePreMergeChain(10) defer n.Close()
n, ethservice := startEthService(t, genesis, preMergeBlocks) var (
ethservice.Merger().ReachTTD() api = NewConsensusAPI(ethservice)
defer n.Close() parent = ethservice.BlockChain().CurrentBlock()
var ( // This EVM code generates a log when the contract is created.
api = NewConsensusAPI(ethservice) logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
parent = ethservice.BlockChain().CurrentBlock() )
// This EVM code generates a log when the contract is created. for i := 0; i < 10; i++ {
logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") statedb, _ := ethservice.BlockChain().StateAt(parent.Root())
) nonce := statedb.GetNonce(testAddr)
for i := 0; i < 10; i++ { tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
statedb, _ := ethservice.BlockChain().StateAt(parent.Root()) ethservice.TxPool().AddLocal(tx)
nonce := statedb.GetNonce(testAddr)
tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey)
ethservice.TxPool().AddLocal(tx)
params := beacon.PayloadAttributesV1{ params := beacon.PayloadAttributesV1{
Timestamp: parent.Time() + 1, Timestamp: parent.Time() + 1,
Random: crypto.Keccak256Hash([]byte{byte(i)}), Random: crypto.Keccak256Hash([]byte{byte(i)}),
SuggestedFeeRecipient: parent.Coinbase(), SuggestedFeeRecipient: parent.Coinbase(),
}
fcState := beacon.ForkchoiceStateV1{
HeadBlockHash: parent.Hash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
resp, err := api.ForkchoiceUpdatedV1(fcState, &params)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
if resp.Status != beacon.VALID {
t.Fatalf("error preparing payload, invalid status: %v", resp.Status)
}
payloadID := computePayloadId(parent.Hash(), &params)
payload, err := api.GetPayloadV1(payloadID)
if err != nil {
t.Fatalf("can't get payload: %v", err)
}
execResp, err := api.ExecutePayloadV1(*payload)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
if execResp.Status != beacon.VALID {
t.Fatalf("invalid status: %v", execResp.Status)
}
fcState = beacon.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: payload.ParentHash,
FinalizedBlockHash: payload.ParentHash,
}
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err)
}
if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {
t.Fatalf("Chain head should be updated")
}
parent = ethservice.BlockChain().CurrentBlock()
} }
*/
fcState := beacon.ForkchoiceStateV1{
HeadBlockHash: parent.Hash(),
SafeBlockHash: common.Hash{},
FinalizedBlockHash: common.Hash{},
}
resp, err := api.ForkchoiceUpdatedV1(fcState, &params)
if err != nil {
t.Fatalf("error preparing payload, err=%v", err)
}
if resp.PayloadStatus.Status != beacon.VALID {
t.Fatalf("error preparing payload, invalid status: %v", resp.PayloadStatus.Status)
}
payload, err := api.GetPayloadV1(*resp.PayloadID)
if err != nil {
t.Fatalf("can't get payload: %v", err)
}
execResp, err := api.NewPayloadV1(*payload)
if err != nil {
t.Fatalf("can't execute payload: %v", err)
}
if execResp.Status != beacon.VALID {
t.Fatalf("invalid status: %v", execResp.Status)
}
fcState = beacon.ForkchoiceStateV1{
HeadBlockHash: payload.BlockHash,
SafeBlockHash: payload.ParentHash,
FinalizedBlockHash: payload.ParentHash,
}
if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil {
t.Fatalf("Failed to insert block: %v", err)
}
if ethservice.BlockChain().CurrentBlock().NumberU64() != payload.Number {
t.Fatalf("Chain head should be updated")
}
parent = ethservice.BlockChain().CurrentBlock()
}
}
func TestExchangeTransitionConfig(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(10)
n, ethservice := startEthService(t, genesis, preMergeBlocks)
ethservice.Merger().ReachTTD()
defer n.Close()
var (
api = NewConsensusAPI(ethservice)
)
// invalid ttd
config := beacon.TransitionConfigurationV1{
TerminalTotalDifficulty: (*hexutil.Big)(big.NewInt(0)),
TerminalBlockHash: common.Hash{},
TerminalBlockNumber: 0,
}
if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil {
t.Fatal("expected error on invalid config, invalid ttd")
}
// invalid terminal block hash
config = beacon.TransitionConfigurationV1{
TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
TerminalBlockHash: common.Hash{1},
TerminalBlockNumber: 0,
}
if _, err := api.ExchangeTransitionConfigurationV1(config); err == nil {
t.Fatal("expected error on invalid config, invalid hash")
}
// valid config
config = beacon.TransitionConfigurationV1{
TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
TerminalBlockHash: common.Hash{},
TerminalBlockNumber: 0,
}
if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil {
t.Fatalf("expected no error on valid config, got %v", err)
}
// valid config
config = beacon.TransitionConfigurationV1{
TerminalTotalDifficulty: (*hexutil.Big)(genesis.Config.TerminalTotalDifficulty),
TerminalBlockHash: preMergeBlocks[5].Hash(),
TerminalBlockNumber: 6,
}
if _, err := api.ExchangeTransitionConfigurationV1(config); err != nil {
t.Fatalf("expected no error on valid config, got %v", err)
}
} }

@ -189,7 +189,7 @@ func (cs *chainSyncer) nextSyncOp() *chainSyncOp {
// We seem to be in sync according to the legacy rules. In the merge // We seem to be in sync according to the legacy rules. In the merge
// world, it can also mean we're stuck on the merge block, waiting for // world, it can also mean we're stuck on the merge block, waiting for
// a beacon client. In the latter case, notify the user. // a beacon client. In the latter case, notify the user.
if cs.handler.chain.Config().TerminalTotalDifficulty != nil && time.Since(cs.warned) > 10*time.Second { if ttd := cs.handler.chain.Config().TerminalTotalDifficulty; ttd != nil && ourTD.Cmp(ttd) >= 0 && time.Since(cs.warned) > 10*time.Second {
log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...") log.Warn("Local chain is post-merge, waiting for beacon client sync switch-over...")
cs.warned = time.Now() cs.warned = time.Now()
} }

@ -69,30 +69,31 @@ func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI {
// we return an error since block creation is not supported in les mode // we return an error since block creation is not supported in les mode
func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) { func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads beacon.ForkchoiceStateV1, payloadAttributes *beacon.PayloadAttributesV1) (beacon.ForkChoiceResponse, error) {
if heads.HeadBlockHash == (common.Hash{}) { if heads.HeadBlockHash == (common.Hash{}) {
return beacon.ForkChoiceResponse{Status: beacon.VALID}, nil log.Warn("Forkchoice requested update to zero hash")
return beacon.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this?
} }
if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil {
if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil { if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil {
// TODO (MariusVanDerWijden) trigger sync // TODO (MariusVanDerWijden) trigger sync
return beacon.ForkChoiceResponse{Status: beacon.SYNCING}, nil return beacon.STATUS_SYNCING, nil
} }
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, err return beacon.STATUS_INVALID, err
} }
// If the finalized block is set, check if it is in our blockchain // If the finalized block is set, check if it is in our blockchain
if heads.FinalizedBlockHash != (common.Hash{}) { if heads.FinalizedBlockHash != (common.Hash{}) {
if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil { if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil {
// TODO (MariusVanDerWijden) trigger sync // TODO (MariusVanDerWijden) trigger sync
return beacon.ForkChoiceResponse{Status: beacon.SYNCING}, nil return beacon.STATUS_SYNCING, nil
} }
} }
// SetHead // SetHead
if err := api.setHead(heads.HeadBlockHash); err != nil { if err := api.setHead(heads.HeadBlockHash); err != nil {
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, err return beacon.STATUS_INVALID, err
} }
if payloadAttributes != nil { if payloadAttributes != nil {
return beacon.ForkChoiceResponse{Status: beacon.INVALID}, errors.New("not supported") return beacon.STATUS_INVALID, errors.New("not supported")
} }
return beacon.ForkChoiceResponse{Status: beacon.VALID}, nil return api.validForkChoiceResponse(), nil
} }
// GetPayloadV1 returns a cached payload by id. It's not supported in les mode. // GetPayloadV1 returns a cached payload by id. It's not supported in les mode.
@ -101,7 +102,7 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID beacon.PayloadID) (*beacon.Execu
} }
// ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. // ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.ExecutePayloadResponse, error) { func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beacon.PayloadStatusV1, error) {
block, err := beacon.ExecutableDataToBlock(params) block, err := beacon.ExecutableDataToBlock(params)
if err != nil { if err != nil {
return api.invalid(), err return api.invalid(), err
@ -114,7 +115,7 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco
} }
*/ */
// TODO (MariusVanDerWijden) we should return nil here not empty hash // TODO (MariusVanDerWijden) we should return nil here not empty hash
return beacon.ExecutePayloadResponse{Status: beacon.SYNCING, LatestValidHash: common.Hash{}}, nil return beacon.PayloadStatusV1{Status: beacon.SYNCING, LatestValidHash: nil}, nil
} }
parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash)
if parent == nil { if parent == nil {
@ -131,12 +132,21 @@ func (api *ConsensusAPI) ExecutePayloadV1(params beacon.ExecutableDataV1) (beaco
if merger := api.les.Merger(); !merger.TDDReached() { if merger := api.les.Merger(); !merger.TDDReached() {
merger.ReachTTD() merger.ReachTTD()
} }
return beacon.ExecutePayloadResponse{Status: beacon.VALID, LatestValidHash: block.Hash()}, nil hash := block.Hash()
return beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &hash}, nil
}
func (api *ConsensusAPI) validForkChoiceResponse() beacon.ForkChoiceResponse {
currentHash := api.les.BlockChain().CurrentHeader().Hash()
return beacon.ForkChoiceResponse{
PayloadStatus: beacon.PayloadStatusV1{Status: beacon.VALID, LatestValidHash: &currentHash},
}
} }
// invalid returns a response "INVALID" with the latest valid hash set to the current head. // invalid returns a response "INVALID" with the latest valid hash set to the current head.
func (api *ConsensusAPI) invalid() beacon.ExecutePayloadResponse { func (api *ConsensusAPI) invalid() beacon.PayloadStatusV1 {
return beacon.ExecutePayloadResponse{Status: beacon.INVALID, LatestValidHash: api.les.BlockChain().CurrentHeader().Hash()} currentHash := api.les.BlockChain().CurrentHeader().Hash()
return beacon.PayloadStatusV1{Status: beacon.INVALID, LatestValidHash: &currentHash}
} }
func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error {

@ -173,7 +173,7 @@ func (n *ethNode) insertBlock(eb beacon.ExecutableDataV1) error {
} }
switch n.typ { switch n.typ {
case eth2NormalNode, eth2MiningNode: case eth2NormalNode, eth2MiningNode:
newResp, err := n.api.ExecutePayloadV1(eb) newResp, err := n.api.NewPayloadV1(eb)
if err != nil { if err != nil {
return err return err
} else if newResp.Status != "VALID" { } else if newResp.Status != "VALID" {

@ -139,12 +139,16 @@ type Config struct {
// HTTPPathPrefix specifies a path prefix on which http-rpc is to be served. // HTTPPathPrefix specifies a path prefix on which http-rpc is to be served.
HTTPPathPrefix string `toml:",omitempty"` HTTPPathPrefix string `toml:",omitempty"`
// AuthHost is the listening address on which authenticated APIs are provided. // AuthAddr is the listening address on which authenticated APIs are provided.
AuthHost string `toml:",omitempty"` AuthAddr string `toml:",omitempty"`
// AuthPort is the port number on which authenticated APIs are provided. // AuthPort is the port number on which authenticated APIs are provided.
AuthPort int `toml:",omitempty"` AuthPort int `toml:",omitempty"`
// AuthVirtualHosts is the list of virtual hostnames which are allowed on incoming requests
// for the authenticated api. This is by default {'localhost'}.
AuthVirtualHosts []string `toml:",omitempty"`
// WSHost is the host interface on which to start the websocket RPC server. If // WSHost is the host interface on which to start the websocket RPC server. If
// this field is empty, no websocket API endpoint will be started. // this field is empty, no websocket API endpoint will be started.
WSHost string WSHost string

@ -50,8 +50,9 @@ var (
var DefaultConfig = Config{ var DefaultConfig = Config{
DataDir: DefaultDataDir(), DataDir: DefaultDataDir(),
HTTPPort: DefaultHTTPPort, HTTPPort: DefaultHTTPPort,
AuthHost: DefaultAuthHost, AuthAddr: DefaultAuthHost,
AuthPort: DefaultAuthPort, AuthPort: DefaultAuthPort,
AuthVirtualHosts: DefaultAuthVhosts,
HTTPModules: []string{"net", "web3"}, HTTPModules: []string{"net", "web3"},
HTTPVirtualHosts: []string{"localhost"}, HTTPVirtualHosts: []string{"localhost"},
HTTPTimeouts: rpc.DefaultHTTPTimeouts, HTTPTimeouts: rpc.DefaultHTTPTimeouts,

@ -439,12 +439,12 @@ func (n *Node) startRPC() error {
initAuth := func(apis []rpc.API, port int, secret []byte) error { initAuth := func(apis []rpc.API, port int, secret []byte) error {
// Enable auth via HTTP // Enable auth via HTTP
server := n.httpAuth server := n.httpAuth
if err := server.setListenAddr(n.config.AuthHost, port); err != nil { if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
return err return err
} }
if err := server.enableRPC(apis, httpConfig{ if err := server.enableRPC(apis, httpConfig{
CorsAllowedOrigins: DefaultAuthCors, CorsAllowedOrigins: DefaultAuthCors,
Vhosts: DefaultAuthVhosts, Vhosts: n.config.AuthVirtualHosts,
Modules: DefaultAuthModules, Modules: DefaultAuthModules,
prefix: DefaultAuthPrefix, prefix: DefaultAuthPrefix,
jwtSecret: secret, jwtSecret: secret,
@ -454,7 +454,7 @@ func (n *Node) startRPC() error {
servers = append(servers, server) servers = append(servers, server)
// Enable auth via WS // Enable auth via WS
server = n.wsServerForPort(port, true) server = n.wsServerForPort(port, true)
if err := server.setListenAddr(n.config.AuthHost, port); err != nil { if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
return err return err
} }
if err := server.enableWS(apis, wsConfig{ if err := server.enableWS(apis, wsConfig{