UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

101 lines 5.81 kB
import { isGloasBeaconBlock, ssz } from "@lodestar/types"; import { toRootHex } from "@lodestar/utils"; import { BlockError, BlockErrorCode } from "../../errors/index.js"; /** * Assert this chain segment of blocks is linear with slot numbers and hashes, * and that the provided envelopes are consistent with their respective blocks. * * * For each block: * - Verifies parent root + slot linearity * - For gloas: verifies bid.parentBlockHash matches the tracked execution hash; if not, the * previous FULL envelope is treated as orphaned (segment continues as if previous slot was EMPTY) * - If an envelope exists for this slot: verifies it references this block's root * - Advances the tracked execution hash (FULL if envelope present, EMPTY if not) */ export function assertLinearChainSegment(config, blocks, payloadEnvelopes, parentBlock) { const warnings = []; // Track the expected execution payload block hash through the segment. // Starts from the known forkchoice parent's execution hash. // - FULL variant (envelope present for slot): advances to envelope.payload.blockHash // - EMPTY variant (no envelope for slot): execution hash is unchanged let currentExecHash = parentBlock?.executionPayloadBlockHash ?? null; // Checkpoint sync first batch: parent is the anchor PENDING whose executionPayloadBlockHash // is the inherited parentBlockHash semantic (= grandparent's payload), not its own payload. // If parent's own payload envelope arrives in this batch, advance currentExecHash to that // payload's blockHash so the segment validation sees the true EL chain head. if (parentBlock !== null) { const parentPayloadInput = payloadEnvelopes?.get(parentBlock.slot); if (parentPayloadInput?.hasPayloadEnvelope()) { currentExecHash = parentPayloadInput.getBlockHashHex(); } } // Track the execution hash before the last FULL advancement so we can recover // if the next block reveals that envelope was orphaned. let prevExecHash = currentExecHash; // The slot whose envelope last advanced currentExecHash (for warning context). let lastFullSlot = null; for (let i = 0; i < blocks.length; i++) { const block = blocks[i].getBlock(); const slot = block.message.slot; if (i > 0) { const prevBlock = blocks[i - 1].getBlock(); // Ensure parent root matches the previous block's root if (!ssz.Root.equals(config.getForkTypes(prevBlock.message.slot).BeaconBlock.hashTreeRoot(prevBlock.message), block.message.parentRoot)) { throw new BlockError(block, { code: BlockErrorCode.NON_LINEAR_PARENT_ROOTS }); } // Ensure slots are strictly increasing if (slot <= prevBlock.message.slot) { throw new BlockError(block, { code: BlockErrorCode.NON_LINEAR_SLOTS }); } } if (isGloasBeaconBlock(block.message)) { // Bid check fires only when we have a seeded execution hash. With parentBlock=null the // chain is seeded mid-segment by the first FULL envelope. if (currentExecHash !== null) { // Verify the bid's parentBlockHash matches the tracked execution hash. // This ensures the block was built on the correct FULL or EMPTY variant of its parent. const bidParentHash = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash); if (bidParentHash !== currentExecHash) { // Maybe the previous slot's FULL envelope was orphaned — try falling back. // If even prevExecHash doesn't match, the segment is non-linear. if (bidParentHash !== prevExecHash) { throw new BlockError(block, { code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN, parentRoot: toRootHex(block.message.parentRoot), parentBlockHash: bidParentHash, }); } if (lastFullSlot !== null && payloadEnvelopes !== null) { const orphanedInput = payloadEnvelopes.get(lastFullSlot); if (orphanedInput != null) { warnings.push({ slot: lastFullSlot, payloadEnvelopeInput: orphanedInput }); } } currentExecHash = prevExecHash; } } const payloadInput = payloadEnvelopes?.get(slot) ?? null; const payloadEnvelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : null; if (payloadEnvelope !== null) { // Verify the envelope references this block's root const blockRoot = toRootHex(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block.message)); const envelopeBlockRoot = toRootHex(payloadEnvelope.message.beaconBlockRoot); if (blockRoot !== envelopeBlockRoot) { throw new BlockError(block, { code: BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH, envelopeBlockRoot, blockRoot, }); } // FULL variant: save state before advancing, then advance prevExecHash = currentExecHash; lastFullSlot = slot; currentExecHash = toRootHex(payloadEnvelope.message.payload.blockHash); } // EMPTY variant: currentExecHash unchanged } } return { warnings: warnings.length > 0 ? warnings : null }; } //# sourceMappingURL=chainSegment.js.map