UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

107 lines 5.97 kB
import { DataAvailabilityStatus, computeTimeAtSlot } from "@lodestar/state-transition"; import { ErrorAborted } from "@lodestar/utils"; import { BlockError, BlockErrorCode } from "../errors/index.js"; import { validateBlobSidecars } from "../validation/blobSidecar.js"; import { BlobSidecarValidation, BlockInputType, getBlockInput } from "./types.js"; // we can now wait for full 12 seconds because unavailable block sync will try pulling // the blobs from the network anyway after 500ms of seeing the block const BLOB_AVAILABILITY_TIMEOUT = 12_000; /** * 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 async function verifyBlocksDataAvailability(chain, blocks, signal, opts) { const lastBlock = blocks.at(-1); if (!lastBlock) { throw Error("Empty partiallyVerifiedBlocks"); } const dataAvailabilityStatuses = []; const seenTime = opts.seenTimestampSec !== undefined ? opts.seenTimestampSec * 1000 : Date.now(); const availableBlockInputs = []; for (const blockInput of blocks) { if (signal.aborted) { throw new ErrorAborted("verifyBlocksDataAvailability"); } // Validate status of only not yet finalized blocks, we don't need yet to propogate the status // as it is not used upstream anywhere const { dataAvailabilityStatus, availableBlockInput } = await maybeValidateBlobs(chain, blockInput, signal, opts); dataAvailabilityStatuses.push(dataAvailabilityStatus); availableBlockInputs.push(availableBlockInput); } const availableTime = lastBlock.type === BlockInputType.dataPromise ? Date.now() : seenTime; if (blocks.length === 1 && opts.seenTimestampSec !== undefined && blocks[0].type !== BlockInputType.preData) { const recvToAvailableTime = availableTime / 1000 - opts.seenTimestampSec; const numBlobs = blocks[0].block.message.body.blobKzgCommitments.length; chain.metrics?.gossipBlock.receivedToBlobsAvailabilityTime.observe({ numBlobs }, recvToAvailableTime); chain.logger.verbose("Verified blobs availability", { slot: blocks[0].block.message.slot, recvToAvailableTime, type: blocks[0].type, }); } return { dataAvailabilityStatuses, availableTime, availableBlockInputs }; } async function maybeValidateBlobs(chain, blockInput, signal, opts) { switch (blockInput.type) { case BlockInputType.preData: return { dataAvailabilityStatus: DataAvailabilityStatus.PreData, availableBlockInput: blockInput }; case BlockInputType.outOfRangeData: return { dataAvailabilityStatus: DataAvailabilityStatus.OutOfRange, availableBlockInput: blockInput }; // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case BlockInputType.availableData: if (opts.validBlobSidecars === BlobSidecarValidation.Full) { return { dataAvailabilityStatus: DataAvailabilityStatus.Available, availableBlockInput: blockInput }; } case BlockInputType.dataPromise: { // run full validation const { block } = blockInput; const blockSlot = block.message.slot; const blobsData = blockInput.type === BlockInputType.availableData ? blockInput.blockData : await raceWithCutoff(chain, blockInput, blockInput.cachedData.availabilityPromise, signal); const { blobs } = blobsData; const { blobKzgCommitments } = block.message.body; const beaconBlockRoot = chain.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message); // if the blob siddecars have been individually verified then we can skip kzg proof check // but other checks to match blobs with block data still need to be performed const skipProofsCheck = opts.validBlobSidecars === BlobSidecarValidation.Individual; await validateBlobSidecars(blockSlot, beaconBlockRoot, blobKzgCommitments, blobs, { skipProofsCheck }); const availableBlockInput = getBlockInput.availableData(chain.config, blockInput.block, blockInput.source, blobsData); return { dataAvailabilityStatus: DataAvailabilityStatus.Available, availableBlockInput: availableBlockInput }; } } } /** * Wait for blobs to become available with a cutoff time. If fails then throw DATA_UNAVAILABLE error * which may try unknownblock/blobs fill (by root). */ async function raceWithCutoff(chain, blockInput, availabilityPromise, signal) { const { block } = blockInput; const blockSlot = block.message.slot; const cutoffTime = computeTimeAtSlot(chain.config, blockSlot, chain.genesisTime) * 1000 + BLOB_AVAILABILITY_TIMEOUT - Date.now(); const cutoffTimeout = cutoffTime > 0 ? new Promise((_resolve, reject) => { setTimeout(() => reject(new Error("Timeout exceeded")), cutoffTime); signal.addEventListener("abort", () => reject(signal.reason)); }) : Promise.reject(new Error("Cutoff time must be greater than 0")); chain.logger.debug("Racing for blob availabilityPromise", { blockSlot, cutoffTime }); try { await Promise.race([availabilityPromise, cutoffTimeout]); } catch (_e) { // throw unavailable so that the unknownblock/blobs can be triggered to pull the block throw new BlockError(block, { code: BlockErrorCode.DATA_UNAVAILABLE }); } // we can only be here if availabilityPromise has resolved else an error will be thrown return availabilityPromise; } //# sourceMappingURL=verifyBlocksDataAvailability.js.map