@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
95 lines • 4.3 kB
JavaScript
import { computeStartSlotAtEpoch } from "@lodestar/state-transition";
import { toRootHex } from "@lodestar/utils";
import { BlockError, BlockErrorCode } from "../errors/index.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, blocks, opts) {
if (blocks.length === 0) {
throw Error("Empty partiallyVerifiedBlocks");
}
const relevantBlocks = [];
const parentSlots = [];
let parentBlock = null;
for (const blockInput of blocks) {
const { block } = blockInput;
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;
if (relevantLastBlock) {
parentBlockSlot = relevantLastBlock.block.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);
parentBlock = chain.forkChoice.getBlockHex(parentRoot);
if (!parentBlock) {
throw new BlockError(block, { code: BlockErrorCode.PARENT_UNKNOWN, parentRoot });
}
// 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 };
}
//# sourceMappingURL=verifyBlocksSanityChecks.js.map