@ -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" , "ForkC hoiceUpdated" , "head" , update . HeadBlockHash , "finalized" , update . FinalizedBlockHash , "safe" , update . SafeBlockHash )
log . Trace ( "Engine API request received" , "method" , "Forkc hoiceUpdated" , "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 ( "Forkch oice 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 a s back to before the merge.
// attempt to push u s 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 { Payload Status: 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 accepte d
return api . validForkChoiceResponse ( nil ) , err // valid setHead, invalid payloa d
}
}
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 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 . ForkChoiceResponse { Status : beacon . VALID } , nil
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
}
}
// Execute PayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
// New PayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
func ( api * ConsensusAPI ) Execute PayloadV1( params beacon . ExecutableDataV1 ) ( beacon . ExecutePayloadResponse , error ) {
func ( api * ConsensusAPI ) New PayloadV1( 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
}