UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

154 lines (137 loc) 5.94 kB
import {ChainForkConfig} from "@lodestar/config"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {RootHex, Slot, isGloasBeaconBlock} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {IClock} from "../../util/clock.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {IChainOptions} from "../options.js"; import {IBlockInput} from "./blockInput/types.js"; import {PayloadEnvelopeInput} from "./payloadEnvelopeInput/payloadEnvelopeInput.js"; import {ImportBlockOpts} from "./types.js"; /** * Verifies some early cheap sanity checks on the block before running the full state transition. * * - Parent is known to the fork-choice * - Check skipped slots limit * - check_block_relevancy() * - Block not in the future * - Not genesis block * - Block's slot is < Infinity * - Not finalized slot * - Not already known */ export function verifyBlocksSanityChecks( chain: { forkChoice: IForkChoice; clock: IClock; config: ChainForkConfig; opts: IChainOptions; blacklistedBlocks: Map<RootHex, Slot | null>; }, blocks: IBlockInput[], payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null, opts: ImportBlockOpts ): { relevantBlocks: IBlockInput[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null; } { if (blocks.length === 0) { throw Error("Empty partiallyVerifiedBlocks"); } const relevantBlocks: IBlockInput[] = []; const parentSlots: Slot[] = []; let parentBlock: ProtoBlock | null = null; for (const blockInput of blocks) { const block = blockInput.getBlock(); const blockSlot = block.message.slot; const blockHash = toRootHex(chain.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message)); if (chain.blacklistedBlocks.has(blockHash)) { // Blacklisting blocks via CLI flag only requires to set the block hash if (chain.blacklistedBlocks.get(blockHash) === null) { // Set actual slot observed when processing the block chain.blacklistedBlocks.set(blockHash, blockSlot); } throw new BlockError(block, {code: BlockErrorCode.BLACKLISTED_BLOCK}); } if (chain.blacklistedBlocks.has(toRootHex(block.message.parentRoot))) { chain.blacklistedBlocks.set(blockHash, blockSlot); throw new BlockError(block, {code: BlockErrorCode.BLACKLISTED_BLOCK}); } // Not genesis block // IGNORE if `partiallyVerifiedBlock.ignoreIfKnown` if (blockSlot === 0) { if (opts.ignoreIfKnown) { continue; } throw new BlockError(block, {code: BlockErrorCode.GENESIS_BLOCK}); } // Not finalized slot // IGNORE if `partiallyVerifiedBlock.ignoreIfFinalized` const finalizedSlot = computeStartSlotAtEpoch(chain.forkChoice.getFinalizedCheckpoint().epoch); if (blockSlot <= finalizedSlot) { if (opts.ignoreIfFinalized) { continue; } throw new BlockError(block, {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT, blockSlot, finalizedSlot}); } const relevantLastBlock = relevantBlocks.at(-1); let parentBlockSlot: Slot; if (relevantLastBlock) { parentBlockSlot = relevantLastBlock.getBlock().message.slot; } else { // When importing a block segment, only the first NON-IGNORED block must be known to the fork-choice. const parentRoot = toRootHex(block.message.parentRoot); const parentBlockDefaultStatus = chain.forkChoice.getBlockHexDefaultStatus(parentRoot); if (!parentBlockDefaultStatus) { throw new BlockError(block, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); } parentBlock = parentBlockDefaultStatus; if (isGloasBeaconBlock(block.message)) { const parentBlockHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash); const parentBlockWithPayload = chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash); if (!parentBlockWithPayload) { // Checkpoint sync: parent's FULL variant may not be in fork-choice yet because the // anchor block is initialized with PENDING+EMPTY only. The parent's payload arrives // in the same batch via payloadEnvelopes and will be imported by processBlocks. If // a matching payload is in the Map, accept the parent as known. const parentPayloadInput = payloadEnvelopes?.get(parentBlockDefaultStatus.slot); if (parentPayloadInput?.getBlockHashHex() !== parentBlockHash) { throw new BlockError(block, { code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN, parentRoot, parentBlockHash, }); } } else { parentBlock = parentBlockWithPayload; } } // Parent is known to the fork-choice parentBlockSlot = parentBlock.slot; } // Block not in the future, also checks for infinity const currentSlot = chain.clock.currentSlot; if (blockSlot > currentSlot) { throw new BlockError(block, {code: BlockErrorCode.FUTURE_SLOT, blockSlot, currentSlot}); } // Not already known // IGNORE if `partiallyVerifiedBlock.ignoreIfKnown` if (chain.forkChoice.hasBlockHex(blockHash)) { if (opts.ignoreIfKnown) { continue; } throw new BlockError(block, {code: BlockErrorCode.ALREADY_KNOWN, root: blockHash}); } // Block is relevant relevantBlocks.push(blockInput); parentSlots.push(parentBlockSlot); } // Just assert to be over cautious and for purposes to be more explicit for someone // going through the code segment if (parentBlock === null && relevantBlocks.length > 0) { throw Error(`Internal error, parentBlock should not be null for relevantBlocks=${relevantBlocks.length}`); } return {relevantBlocks, parentSlots, parentBlock}; }