UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

298 lines (268 loc) • 11.2 kB
import {ChainForkConfig} from "@lodestar/config"; import { ExecutionStatus, ForkChoice, ForkChoiceStore, JustifiedBalancesGetter, PayloadStatus, ProtoArray, ProtoBlock, ForkChoiceOpts as RawForkChoiceOpts, } from "@lodestar/fork-choice"; import {ZERO_HASH_HEX} from "@lodestar/params"; import { DataAvailabilityStatus, IBeaconStateView, computeEpochAtSlot, computeStartSlotAtEpoch, isStatePostBellatrix, isStatePostGloas, } from "@lodestar/state-transition"; import {Slot, ssz} from "@lodestar/types"; import {Logger, toRootHex} from "@lodestar/utils"; import {GENESIS_SLOT} from "../../constants/index.js"; import {Metrics} from "../../metrics/index.js"; import {ChainEvent, ChainEventEmitter} from "../emitter.js"; export type ForkChoiceOpts = RawForkChoiceOpts & { // for testing only forkchoiceConstructor?: typeof ForkChoice; }; export enum ForkchoiceCaller { prepareNextSlot = "prepare_next_slot", importBlock = "import_block", } /** * Fork Choice extended with a ChainEventEmitter */ export function initializeForkChoice( config: ChainForkConfig, emitter: ChainEventEmitter, currentSlot: Slot, state: IBeaconStateView, isFinalizedState: boolean, opts: ForkChoiceOpts, justifiedBalancesGetter: JustifiedBalancesGetter, metrics: Metrics | null, logger?: Logger ): ForkChoice { return isFinalizedState ? initializeForkChoiceFromFinalizedState( config, emitter, currentSlot, state, opts, justifiedBalancesGetter, metrics, logger ) : initializeForkChoiceFromUnfinalizedState( config, emitter, currentSlot, state, opts, justifiedBalancesGetter, metrics, logger ); } /** * Initialize forkchoice from a finalized state. */ export function initializeForkChoiceFromFinalizedState( config: ChainForkConfig, emitter: ChainEventEmitter, currentSlot: Slot, state: IBeaconStateView, opts: ForkChoiceOpts, justifiedBalancesGetter: JustifiedBalancesGetter, metrics: Metrics | null, logger?: Logger ): ForkChoice { const {blockHeader, checkpoint} = state.computeAnchorCheckpoint(); const finalizedCheckpoint = {...checkpoint}; const justifiedCheckpoint = { ...checkpoint, // If not genesis epoch, justified checkpoint epoch must be set to finalized checkpoint epoch + 1 // So that we don't allow the chain to initially justify with a block that isn't also finalizing the anchor state. // If that happens, we will create an invalid head state, // with the head not matching the fork choice justified and finalized epochs. epoch: checkpoint.epoch === 0 ? checkpoint.epoch : checkpoint.epoch + 1, }; const justifiedBalances = state.getEffectiveBalanceIncrementsZeroInactive(); // forkchoiceConstructor is only used for some test cases // production code use ForkChoice constructor directly const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice; const isForkPostGloas = computeEpochAtSlot(state.slot) >= config.GLOAS_FORK_EPOCH; return new forkchoiceConstructor( config, new ForkChoiceStore( currentSlot, justifiedCheckpoint, finalizedCheckpoint, justifiedBalances, justifiedBalancesGetter, { onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp), onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp), } ), ProtoArray.initialize( { slot: blockHeader.slot, parentRoot: toRootHex(blockHeader.parentRoot), stateRoot: toRootHex(blockHeader.stateRoot), blockRoot: toRootHex(checkpoint.root), timeliness: true, // Optimistically assume is timely justifiedEpoch: justifiedCheckpoint.epoch, justifiedRoot: toRootHex(justifiedCheckpoint.root), finalizedEpoch: finalizedCheckpoint.epoch, finalizedRoot: toRootHex(finalizedCheckpoint.root), unrealizedJustifiedEpoch: justifiedCheckpoint.epoch, unrealizedJustifiedRoot: toRootHex(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root), ...(isStatePostBellatrix(state) && state.isExecutionStateType && state.isMergeTransitionComplete ? { executionPayloadBlockHash: isStatePostGloas(state) ? toRootHex(state.latestBlockHash) : toRootHex(state.latestExecutionPayloadHeader.blockHash), // TODO GLOAS: executionPayloadNumber is not tracked in BeaconState post-gloas (EIP-7732 removed // latestExecutionPayloadHeader). Using 0 as unavailable fallback until a solution is found. executionPayloadNumber: isStatePostGloas(state) ? 0 : state.payloadBlockNumber, executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, } : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), dataAvailabilityStatus: DataAvailabilityStatus.PreData, payloadStatus: isForkPostGloas ? PayloadStatus.PENDING : PayloadStatus.FULL, parentBlockHash: isStatePostGloas(state) ? toRootHex(state.latestBlockHash) : null, }, currentSlot ), state.validatorCount, metrics, opts, logger ); } /** * Initialize forkchoice from an unfinalized state. */ export function initializeForkChoiceFromUnfinalizedState( config: ChainForkConfig, emitter: ChainEventEmitter, currentSlot: Slot, unfinalizedState: IBeaconStateView, opts: ForkChoiceOpts, justifiedBalancesGetter: JustifiedBalancesGetter, metrics: Metrics | null, logger?: Logger ): ForkChoice { const {blockHeader} = unfinalizedState.computeAnchorCheckpoint(); const finalizedCheckpoint = unfinalizedState.finalizedCheckpoint; const justifiedCheckpoint = unfinalizedState.currentJustifiedCheckpoint; const headRoot = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader)); const logCtx = { currentSlot: currentSlot, stateSlot: unfinalizedState.slot, headSlot: blockHeader.slot, headRoot: headRoot, finalizedEpoch: finalizedCheckpoint.epoch, finalizedRoot: toRootHex(finalizedCheckpoint.root), justifiedEpoch: justifiedCheckpoint.epoch, justifiedRoot: toRootHex(justifiedCheckpoint.root), }; logger?.warn("Initializing fork choice from unfinalized state", logCtx); // this is not the justified state, but there is no other ways to get justified balances const justifiedBalances = unfinalizedState.getEffectiveBalanceIncrementsZeroInactive(); const isForkPostGloas = computeEpochAtSlot(unfinalizedState.slot) >= config.GLOAS_FORK_EPOCH; const store = new ForkChoiceStore( currentSlot, justifiedCheckpoint, finalizedCheckpoint, justifiedBalances, justifiedBalancesGetter, { onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp), onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp), } ); // this is the same to the finalized state const headBlock: ProtoBlock = { slot: blockHeader.slot, parentRoot: toRootHex(blockHeader.parentRoot), stateRoot: toRootHex(blockHeader.stateRoot), blockRoot: headRoot, targetRoot: headRoot, timeliness: true, // Optimistically assume is timely justifiedEpoch: justifiedCheckpoint.epoch, justifiedRoot: toRootHex(justifiedCheckpoint.root), finalizedEpoch: finalizedCheckpoint.epoch, finalizedRoot: toRootHex(finalizedCheckpoint.root), unrealizedJustifiedEpoch: justifiedCheckpoint.epoch, unrealizedJustifiedRoot: toRootHex(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root), ...(isStatePostBellatrix(unfinalizedState) && unfinalizedState.isExecutionStateType && unfinalizedState.isMergeTransitionComplete ? { executionPayloadBlockHash: isStatePostGloas(unfinalizedState) ? toRootHex(unfinalizedState.latestBlockHash) : toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash), // TODO GLOAS: executionPayloadNumber is not tracked in BeaconState post-gloas (EIP-7732 removed // latestExecutionPayloadHeader). Using 0 as unavailable fallback until a solution is found. executionPayloadNumber: isStatePostGloas(unfinalizedState) ? 0 : unfinalizedState.payloadBlockNumber, executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, } : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), dataAvailabilityStatus: DataAvailabilityStatus.PreData, payloadStatus: isForkPostGloas ? PayloadStatus.PENDING : PayloadStatus.FULL, parentBlockHash: isStatePostGloas(unfinalizedState) ? toRootHex(unfinalizedState.latestBlockHash) : null, }; const parentSlot = blockHeader.slot - 1; const parentEpoch = computeEpochAtSlot(parentSlot); // parent of head block const parentBlock: ProtoBlock = { ...headBlock, slot: parentSlot, // link this to the dummy justified block parentRoot: toRootHex(justifiedCheckpoint.root), // dummy data, we're not able to regen state before headBlock stateRoot: ZERO_HASH_HEX, blockRoot: headBlock.parentRoot, targetRoot: toRootHex(unfinalizedState.getBlockRootAtSlot(computeStartSlotAtEpoch(parentEpoch))), }; const justifiedBlock: ProtoBlock = { ...headBlock, slot: computeStartSlotAtEpoch(justifiedCheckpoint.epoch), // link this to the finalized root so that getAncestors can find the finalized block parentRoot: toRootHex(finalizedCheckpoint.root), // dummy data, we're not able to regen state before headBlock stateRoot: ZERO_HASH_HEX, blockRoot: toRootHex(justifiedCheckpoint.root), // same to blockRoot targetRoot: toRootHex(justifiedCheckpoint.root), }; const finalizedBlock: ProtoBlock = { ...headBlock, slot: computeStartSlotAtEpoch(finalizedCheckpoint.epoch), // we don't care parent of finalized block parentRoot: ZERO_HASH_HEX, // dummy data, we're not able to regen state before headBlock stateRoot: ZERO_HASH_HEX, blockRoot: toRootHex(finalizedCheckpoint.root), // same to blockRoot targetRoot: toRootHex(finalizedCheckpoint.root), }; const protoArray = ProtoArray.initialize(finalizedBlock, currentSlot); protoArray.onBlock(justifiedBlock, currentSlot, null); protoArray.onBlock(parentBlock, currentSlot, null); protoArray.onBlock(headBlock, currentSlot, null); logger?.verbose("Initialized protoArray successfully", {...logCtx, length: protoArray.length()}); // forkchoiceConstructor is only used for some test cases // production code use ForkChoice constructor directly const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice; return new forkchoiceConstructor(config, store, protoArray, unfinalizedState.validatorCount, metrics, opts, logger); }