2022-02-07 01:26:20 +00:00
package blockchain
import (
"context"
2022-03-01 16:43:06 +00:00
"fmt"
2022-02-07 01:26:20 +00:00
2022-02-10 22:18:42 +00:00
"github.com/pkg/errors"
2022-04-06 23:36:52 +00:00
types "github.com/prysmaticlabs/eth2-types"
2022-02-07 01:26:20 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
2022-03-01 16:43:06 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
2022-04-06 23:36:52 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/beacon-chain/core/transition"
"github.com/prysmaticlabs/prysm/beacon-chain/db/kv"
2022-04-01 18:04:24 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/powchain"
2022-04-06 23:36:52 +00:00
"github.com/prysmaticlabs/prysm/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
2022-02-07 01:26:20 +00:00
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
2022-03-01 16:43:06 +00:00
enginev1 "github.com/prysmaticlabs/prysm/proto/engine/v1"
2022-03-09 20:26:23 +00:00
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
2022-02-07 01:26:20 +00:00
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block"
2022-04-06 23:36:52 +00:00
"github.com/prysmaticlabs/prysm/time/slots"
2022-03-01 16:43:06 +00:00
"github.com/sirupsen/logrus"
2022-03-23 22:17:09 +00:00
"go.opencensus.io/trace"
2022-02-07 01:26:20 +00:00
)
2022-03-01 16:43:06 +00:00
// notifyForkchoiceUpdate signals execution engine the fork choice updates. Execution engine should:
// 1. Re-organizes the execution payload chain and corresponding state to make head_block_hash the head.
// 2. Applies finality to the execution state: it irreversibly persists the chain of all execution payloads and corresponding state, up to and including finalized_block_hash.
2022-04-06 23:36:52 +00:00
func ( s * Service ) notifyForkchoiceUpdate ( ctx context . Context , headState state . BeaconState , headBlk block . BeaconBlock , headRoot [ 32 ] byte , finalizedRoot [ 32 ] byte ) ( * enginev1 . PayloadIDBytes , error ) {
2022-03-23 22:17:09 +00:00
ctx , span := trace . StartSpan ( ctx , "blockChain.notifyForkchoiceUpdate" )
defer span . End ( )
2022-03-01 16:43:06 +00:00
if headBlk == nil || headBlk . IsNil ( ) || headBlk . Body ( ) . IsNil ( ) {
return nil , errors . New ( "nil head block" )
}
// Must not call fork choice updated until the transition conditions are met on the Pow network.
2022-03-28 15:25:49 +00:00
isExecutionBlk , err := blocks . IsExecutionBlock ( headBlk . Body ( ) )
2022-03-01 16:43:06 +00:00
if err != nil {
return nil , errors . Wrap ( err , "could not determine if block is execution block" )
}
if ! isExecutionBlk {
return nil , nil
}
headPayload , err := headBlk . Body ( ) . ExecutionPayload ( )
if err != nil {
return nil , errors . Wrap ( err , "could not get execution payload" )
}
finalizedBlock , err := s . cfg . BeaconDB . Block ( ctx , s . ensureRootNotZeros ( finalizedRoot ) )
if err != nil {
return nil , errors . Wrap ( err , "could not get finalized block" )
}
2022-04-05 14:02:46 +00:00
if finalizedBlock == nil || finalizedBlock . IsNil ( ) {
finalizedBlock = s . getInitSyncBlock ( s . ensureRootNotZeros ( finalizedRoot ) )
if finalizedBlock == nil || finalizedBlock . IsNil ( ) {
return nil , errors . Errorf ( "finalized block with root %#x does not exist in the db or our cache" , s . ensureRootNotZeros ( finalizedRoot ) )
}
}
2022-03-01 16:43:06 +00:00
var finalizedHash [ ] byte
2022-03-28 15:25:49 +00:00
if blocks . IsPreBellatrixVersion ( finalizedBlock . Block ( ) . Version ( ) ) {
2022-03-01 16:43:06 +00:00
finalizedHash = params . BeaconConfig ( ) . ZeroHash [ : ]
} else {
payload , err := finalizedBlock . Block ( ) . Body ( ) . ExecutionPayload ( )
if err != nil {
return nil , errors . Wrap ( err , "could not get finalized block execution payload" )
}
finalizedHash = payload . BlockHash
}
fcs := & enginev1 . ForkchoiceState {
HeadBlockHash : headPayload . BlockHash ,
SafeBlockHash : headPayload . BlockHash ,
FinalizedBlockHash : finalizedHash ,
}
2022-04-06 23:36:52 +00:00
nextSlot := s . CurrentSlot ( ) + 1 // Cache payload ID for next slot proposer.
hasAttr , attr , proposerId , err := s . getPayloadAttribute ( ctx , headState , nextSlot )
if err != nil {
return nil , errors . Wrap ( err , "could not get payload attribute" )
}
payloadID , _ , err := s . cfg . ExecutionEngineCaller . ForkchoiceUpdated ( ctx , fcs , attr )
2022-03-01 16:43:06 +00:00
if err != nil {
switch err {
2022-04-01 18:04:24 +00:00
case powchain . ErrAcceptedSyncingPayloadStatus :
2022-04-12 09:51:13 +00:00
forkchoiceUpdatedOptimisticNodeCount . Inc ( )
2022-03-01 16:43:06 +00:00
log . WithFields ( logrus . Fields {
2022-04-09 23:09:04 +00:00
"headSlot" : headBlk . Slot ( ) ,
"headPayloadBlockHash" : fmt . Sprintf ( "%#x" , bytesutil . Trunc ( headPayload . BlockHash ) ) ,
"finalizedPayloadBlockHash" : fmt . Sprintf ( "%#x" , bytesutil . Trunc ( finalizedHash ) ) ,
2022-03-01 16:43:06 +00:00
} ) . Info ( "Called fork choice updated with optimistic block" )
return payloadID , nil
default :
return nil , errors . Wrap ( err , "could not notify forkchoice update from execution engine" )
}
}
2022-04-12 09:51:13 +00:00
forkchoiceUpdatedValidNodeCount . Inc ( )
2022-03-17 14:35:50 +00:00
if err := s . cfg . ForkChoiceStore . SetOptimisticToValid ( ctx , headRoot ) ; err != nil {
2022-03-14 17:25:40 +00:00
return nil , errors . Wrap ( err , "could not set block to valid" )
}
2022-04-06 23:36:52 +00:00
if hasAttr { // If the forkchoice update call has an attribute, update the proposer payload ID cache.
var pId [ 8 ] byte
copy ( pId [ : ] , payloadID [ : ] )
s . cfg . ProposerSlotIndexCache . SetProposerAndPayloadIDs ( nextSlot , proposerId , pId )
}
2022-03-01 16:43:06 +00:00
return payloadID , nil
}
2022-03-31 12:17:49 +00:00
// notifyForkchoiceUpdate signals execution engine on a new payload.
// It returns true if the EL has returned VALID for the block
2022-03-25 16:03:15 +00:00
func ( s * Service ) notifyNewPayload ( ctx context . Context , preStateVersion , postStateVersion int ,
2022-03-31 12:17:49 +00:00
preStateHeader , postStateHeader * ethpb . ExecutionPayloadHeader , blk block . SignedBeaconBlock ) ( bool , error ) {
2022-03-23 22:17:09 +00:00
ctx , span := trace . StartSpan ( ctx , "blockChain.notifyNewPayload" )
defer span . End ( )
2022-03-22 03:35:02 +00:00
// Execution payload is only supported in Bellatrix and beyond. Pre
// merge blocks are never optimistic
2022-03-28 15:25:49 +00:00
if blocks . IsPreBellatrixVersion ( postStateVersion ) {
2022-03-31 12:17:49 +00:00
return true , nil
2022-03-01 16:43:06 +00:00
}
if err := helpers . BeaconBlockIsNil ( blk ) ; err != nil {
2022-03-31 12:17:49 +00:00
return false , err
2022-03-01 16:43:06 +00:00
}
body := blk . Block ( ) . Body ( )
2022-03-25 16:03:15 +00:00
enabled , err := blocks . IsExecutionEnabledUsingHeader ( postStateHeader , body )
2022-03-01 16:43:06 +00:00
if err != nil {
2022-03-31 12:17:49 +00:00
return false , errors . Wrap ( err , "could not determine if execution is enabled" )
2022-03-01 16:43:06 +00:00
}
if ! enabled {
2022-03-31 12:17:49 +00:00
return true , nil
2022-03-01 16:43:06 +00:00
}
payload , err := body . ExecutionPayload ( )
if err != nil {
2022-03-31 12:17:49 +00:00
return false , errors . Wrap ( err , "could not get execution payload" )
2022-03-01 16:43:06 +00:00
}
2022-04-06 21:24:00 +00:00
lastValidHash , err := s . cfg . ExecutionEngineCaller . NewPayload ( ctx , payload )
2022-03-01 16:43:06 +00:00
if err != nil {
switch err {
2022-04-01 18:04:24 +00:00
case powchain . ErrAcceptedSyncingPayloadStatus :
2022-04-12 09:51:13 +00:00
newPayloadOptimisticNodeCount . Inc ( )
2022-03-01 16:43:06 +00:00
log . WithFields ( logrus . Fields {
2022-04-09 23:09:04 +00:00
"slot" : blk . Block ( ) . Slot ( ) ,
"payloadBlockHash" : fmt . Sprintf ( "%#x" , bytesutil . Trunc ( payload . BlockHash ) ) ,
2022-03-01 16:43:06 +00:00
} ) . Info ( "Called new payload with optimistic block" )
2022-03-31 12:17:49 +00:00
return false , nil
2022-04-06 21:24:00 +00:00
case powchain . ErrInvalidPayloadStatus :
2022-04-12 09:51:13 +00:00
newPayloadInvalidNodeCount . Inc ( )
2022-04-06 21:24:00 +00:00
root , err := blk . Block ( ) . HashTreeRoot ( )
if err != nil {
return false , err
}
invalidRoots , err := s . ForkChoicer ( ) . SetOptimisticToInvalid ( ctx , root , bytesutil . ToBytes32 ( lastValidHash ) )
if err != nil {
return false , err
}
if err := s . removeInvalidBlockAndState ( ctx , invalidRoots ) ; err != nil {
return false , err
}
return false , errors . New ( "could not validate an INVALID payload from execution engine" )
2022-03-01 16:43:06 +00:00
default :
2022-03-31 12:17:49 +00:00
return false , errors . Wrap ( err , "could not validate execution payload from execution engine" )
2022-03-01 16:43:06 +00:00
}
}
2022-04-12 09:51:13 +00:00
newPayloadValidNodeCount . Inc ( )
2022-03-01 16:43:06 +00:00
// During the transition event, the transition block should be verified for sanity.
2022-03-28 15:25:49 +00:00
if blocks . IsPreBellatrixVersion ( preStateVersion ) {
2022-03-14 22:40:08 +00:00
// Handle case where pre-state is Altair but block contains payload.
// To reach here, the block must have contained a valid payload.
2022-03-31 12:17:49 +00:00
return true , s . validateMergeBlock ( ctx , blk )
2022-03-01 16:43:06 +00:00
}
2022-03-28 15:25:49 +00:00
atTransition , err := blocks . IsMergeTransitionBlockUsingPreStatePayloadHeader ( preStateHeader , body )
2022-03-01 16:43:06 +00:00
if err != nil {
2022-03-31 12:17:49 +00:00
return true , errors . Wrap ( err , "could not check if merge block is terminal" )
2022-03-01 16:43:06 +00:00
}
if ! atTransition {
2022-03-31 12:17:49 +00:00
return true , nil
2022-03-01 16:43:06 +00:00
}
2022-03-31 12:17:49 +00:00
return true , s . validateMergeBlock ( ctx , blk )
2022-03-01 16:43:06 +00:00
}
2022-02-07 01:26:20 +00:00
// optimisticCandidateBlock returns true if this block can be optimistically synced.
//
// Spec pseudocode definition:
// def is_optimistic_candidate_block(opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock) -> bool:
2022-03-15 19:12:52 +00:00
// if is_execution_block(opt_store.blocks[block.parent_root]):
// return True
//
// if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot:
// return True
//
// return False
2022-02-07 01:26:20 +00:00
func ( s * Service ) optimisticCandidateBlock ( ctx context . Context , blk block . BeaconBlock ) ( bool , error ) {
if blk . Slot ( ) + params . BeaconConfig ( ) . SafeSlotsToImportOptimistically <= s . CurrentSlot ( ) {
return true , nil
}
2022-03-15 19:12:52 +00:00
parent , err := s . cfg . BeaconDB . Block ( ctx , bytesutil . ToBytes32 ( blk . ParentRoot ( ) ) )
if err != nil {
return false , err
}
if parent == nil {
return false , errNilParentInDB
}
2022-03-28 15:25:49 +00:00
parentIsExecutionBlock , err := blocks . IsExecutionBlock ( parent . Block ( ) . Body ( ) )
2022-03-15 19:12:52 +00:00
if err != nil {
return false , err
}
2022-03-31 12:17:49 +00:00
return parentIsExecutionBlock , nil
2022-02-07 01:26:20 +00:00
}
2022-04-06 21:24:00 +00:00
2022-04-06 23:36:52 +00:00
// getPayloadAttributes returns the payload attributes for the given state and slot.
// The attribute is required to initiate a payload build process in the context of an `engine_forkchoiceUpdated` call.
func ( s * Service ) getPayloadAttribute ( ctx context . Context , st state . BeaconState , slot types . Slot ) ( bool , * enginev1 . PayloadAttributes , types . ValidatorIndex , error ) {
proposerID , _ , ok := s . cfg . ProposerSlotIndexCache . GetProposerPayloadIDs ( slot )
if ! ok { // There's no need to build attribute if there is no proposer for slot.
return false , nil , 0 , nil
}
// Get previous randao.
st = st . Copy ( )
st , err := transition . ProcessSlotsIfPossible ( ctx , st , slot )
if err != nil {
return false , nil , 0 , err
}
prevRando , err := helpers . RandaoMix ( st , time . CurrentEpoch ( st ) )
if err != nil {
return false , nil , 0 , nil
}
// Get fee recipient.
feeRecipient := params . BeaconConfig ( ) . DefaultFeeRecipient
recipient , err := s . cfg . BeaconDB . FeeRecipientByValidatorID ( ctx , proposerID )
switch {
case errors . Is ( err , kv . ErrNotFoundFeeRecipient ) :
if feeRecipient . String ( ) == fieldparams . EthBurnAddressHex {
logrus . WithFields ( logrus . Fields {
"validatorIndex" : proposerID ,
"burnAddress" : fieldparams . EthBurnAddressHex ,
} ) . Error ( "Fee recipient not set. Using burn address" )
}
case err != nil :
return false , nil , 0 , errors . Wrap ( err , "could not get fee recipient in db" )
default :
feeRecipient = recipient
}
// Get timestamp.
t , err := slots . ToTime ( uint64 ( s . genesisTime . Unix ( ) ) , slot )
if err != nil {
return false , nil , 0 , err
}
attr := & enginev1 . PayloadAttributes {
Timestamp : uint64 ( t . Unix ( ) ) ,
PrevRandao : prevRando ,
SuggestedFeeRecipient : feeRecipient . Bytes ( ) ,
}
return true , attr , proposerID , nil
}
2022-04-06 21:24:00 +00:00
// removeInvalidBlockAndState removes the invalid block and its corresponding state from the cache and DB.
func ( s * Service ) removeInvalidBlockAndState ( ctx context . Context , blkRoots [ ] [ 32 ] byte ) error {
for _ , root := range blkRoots {
if err := s . cfg . StateGen . DeleteStateFromCaches ( ctx , root ) ; err != nil {
return err
}
// Delete block also deletes the state as well.
if err := s . cfg . BeaconDB . DeleteBlock ( ctx , root ) ; err != nil {
// TODO(10487): If a caller requests to delete a root that's justified and finalized. We should gracefully shutdown.
// This is an irreparable condition, it would me a justified or finalized block has become invalid.
return err
}
}
return nil
}