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-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"
v1 "github.com/prysmaticlabs/prysm/beacon-chain/powchain/engine-api-client/v1"
"github.com/prysmaticlabs/prysm/beacon-chain/state"
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-03-01 16:43:06 +00:00
"github.com/prysmaticlabs/prysm/runtime/version"
"github.com/sirupsen/logrus"
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.
func ( s * Service ) notifyForkchoiceUpdate ( ctx context . Context , headBlk block . BeaconBlock , finalizedRoot [ 32 ] byte ) ( * enginev1 . PayloadIDBytes , error ) {
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.
if isPreBellatrix ( headBlk . Version ( ) ) {
return nil , nil
}
isExecutionBlk , err := blocks . ExecutionBlock ( headBlk . Body ( ) )
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" )
}
var finalizedHash [ ] byte
if isPreBellatrix ( finalizedBlock . Block ( ) . Version ( ) ) {
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 ,
}
// payload attribute is only required when requesting payload, here we are just updating fork choice, so it is nil.
payloadID , _ , err := s . cfg . ExecutionEngineCaller . ForkchoiceUpdated ( ctx , fcs , nil /*payload attribute*/ )
if err != nil {
switch err {
case v1 . ErrAcceptedSyncingPayloadStatus :
log . WithFields ( logrus . Fields {
"headSlot" : headBlk . Slot ( ) ,
"headHash" : fmt . Sprintf ( "%#x" , bytesutil . Trunc ( headPayload . BlockHash ) ) ,
"finalizedHash" : fmt . Sprintf ( "%#x" , bytesutil . Trunc ( finalizedHash ) ) ,
} ) . 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-03-14 17:25:40 +00:00
if err := s . cfg . ForkChoiceStore . SetOptimisticToValid ( ctx , s . headRoot ( ) ) ; err != nil {
return nil , errors . Wrap ( err , "could not set block to valid" )
}
2022-03-01 16:43:06 +00:00
return payloadID , nil
}
// notifyForkchoiceUpdate signals execution engine on a new payload
2022-03-09 20:26:23 +00:00
func ( s * Service ) notifyNewPayload ( ctx context . Context , preStateVersion int , header * ethpb . ExecutionPayloadHeader , postState state . BeaconState , blk block . SignedBeaconBlock ) error {
if postState == nil {
2022-03-01 16:43:06 +00:00
return errors . New ( "pre and post states must not be nil" )
}
// Execution payload is only supported in Bellatrix and beyond.
if isPreBellatrix ( postState . Version ( ) ) {
return nil
}
if err := helpers . BeaconBlockIsNil ( blk ) ; err != nil {
return err
}
body := blk . Block ( ) . Body ( )
enabled , err := blocks . ExecutionEnabled ( postState , blk . Block ( ) . Body ( ) )
if err != nil {
return errors . Wrap ( err , "could not determine if execution is enabled" )
}
if ! enabled {
return nil
}
payload , err := body . ExecutionPayload ( )
if err != nil {
return errors . Wrap ( err , "could not get execution payload" )
}
_ , err = s . cfg . ExecutionEngineCaller . NewPayload ( ctx , payload )
if err != nil {
switch err {
case v1 . ErrAcceptedSyncingPayloadStatus :
log . WithFields ( logrus . Fields {
"slot" : postState . Slot ( ) ,
"blockHash" : fmt . Sprintf ( "%#x" , bytesutil . Trunc ( payload . BlockHash ) ) ,
} ) . Info ( "Called new payload with optimistic block" )
return nil
default :
return errors . Wrap ( err , "could not validate execution payload from execution engine" )
}
}
// During the transition event, the transition block should be verified for sanity.
2022-03-09 20:26:23 +00:00
if isPreBellatrix ( 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.
return s . validateMergeBlock ( ctx , blk )
2022-03-01 16:43:06 +00:00
}
2022-03-09 20:26:23 +00:00
atTransition , err := blocks . IsMergeTransitionBlockUsingPayloadHeader ( header , body )
2022-03-01 16:43:06 +00:00
if err != nil {
return errors . Wrap ( err , "could not check if merge block is terminal" )
}
if ! atTransition {
return nil
}
return s . validateMergeBlock ( ctx , blk )
}
// isPreBellatrix returns true if input version is before bellatrix fork.
func isPreBellatrix ( v int ) bool {
return v == version . Phase0 || v == version . Altair
}
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:
// justified_root = opt_store.block_states[opt_store.head_block_root].current_justified_checkpoint.root
// justified_is_execution_block = is_execution_block(opt_store.blocks[justified_root])
// block_is_deep = block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot
// return justified_is_execution_block or block_is_deep
func ( s * Service ) optimisticCandidateBlock ( ctx context . Context , blk block . BeaconBlock ) ( bool , error ) {
if blk . Slot ( ) + params . BeaconConfig ( ) . SafeSlotsToImportOptimistically <= s . CurrentSlot ( ) {
return true , nil
}
j := s . store . JustifiedCheckpt ( )
if j == nil {
return false , errNilJustifiedInStore
}
jBlock , err := s . cfg . BeaconDB . Block ( ctx , bytesutil . ToBytes32 ( j . Root ) )
if err != nil {
return false , err
}
return blocks . ExecutionBlock ( jBlock . Block ( ) . Body ( ) )
}