UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

137 lines 6.9 kB
import { isForkPostDeneb } from "@lodestar/params"; import { ssz } from "@lodestar/types"; import { pruneSetToMax, toRootHex } from "@lodestar/utils"; import { BlobsSource, BlockSource, GossipedInputType, getBlockInput, getBlockInputBlobs, } from "../blocks/types.js"; export var BlockInputAvailabilitySource; (function (BlockInputAvailabilitySource) { BlockInputAvailabilitySource["GOSSIP"] = "gossip"; BlockInputAvailabilitySource["UNKNOWN_SYNC"] = "unknown_sync"; })(BlockInputAvailabilitySource || (BlockInputAvailabilitySource = {})); const MAX_GOSSIPINPUT_CACHE = 5; /** * For predeneb, SeenGossipBlockInput only tracks and caches block so that we don't need to download known block * roots. From deneb, it serves same purpose plus tracks and caches the live blobs and blocks on the network to * solve data availability for the blockInput. If no block has been seen yet for some already seen blobs, it * responds will null, but on the first block or the consequent blobs it responds with blobs promise till all blobs * become available. * * One can start processing block on blobs promise blockInput response and can await on the promise before * fully importing the block. The blobs promise is gets resolved as soon as all blobs corresponding to that * block are seen by SeenGossipBlockInput */ export class SeenGossipBlockInput { constructor() { this.blockInputCache = new Map(); } prune() { pruneSetToMax(this.blockInputCache, MAX_GOSSIPINPUT_CACHE); } hasBlock(blockRoot) { return this.blockInputCache.has(blockRoot); } getGossipBlockInput(config, gossipedInput, metrics) { let blockHex; let blockCache; let fork; if (gossipedInput.type === GossipedInputType.block) { const { signedBlock } = gossipedInput; fork = config.getForkName(signedBlock.message.slot); blockHex = toRootHex(config.getForkTypes(signedBlock.message.slot).BeaconBlock.hashTreeRoot(signedBlock.message)); blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(fork); blockCache.block = signedBlock; } else { const { blobSidecar } = gossipedInput; const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobSidecar.signedBlockHeader.message); fork = config.getForkName(blobSidecar.signedBlockHeader.message.slot); blockHex = toRootHex(blockRoot); blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(fork); // TODO: freetheblobs check if its the same blob or a duplicate and throw/take actions blockCache.cachedData?.blobsCache.set(blobSidecar.index, blobSidecar); } if (!this.blockInputCache.has(blockHex)) { this.blockInputCache.set(blockHex, blockCache); } const { block: signedBlock, blockInputPromise, resolveBlockInput, cachedData } = blockCache; if (signedBlock !== undefined) { if (!isForkPostDeneb(fork)) { return { blockInput: getBlockInput.preData(config, signedBlock, BlockSource.gossip), blockInputMeta: { pending: null, haveBlobs: 0, expectedBlobs: 0 }, }; } if (cachedData === undefined || !isForkPostDeneb(cachedData.fork)) { throw Error("Missing or Invalid fork cached Data for post-deneb block"); } const { blobsCache, resolveAvailability } = cachedData; // block is available, check if all blobs have shown up const { slot, body } = signedBlock.message; const { blobKzgCommitments } = body; const blockInfo = `blockHex=${blockHex}, slot=${slot}`; if (blobKzgCommitments.length < blobsCache.size) { throw Error(`Received more blobs=${blobsCache.size} than commitments=${blobKzgCommitments.length} for ${blockInfo}`); } if (blobKzgCommitments.length === blobsCache.size) { const allBlobs = getBlockInputBlobs(blobsCache); const blockData = { ...allBlobs, blobsSource: BlobsSource.gossip, fork: cachedData.fork }; resolveAvailability(blockData); metrics?.syncUnknownBlock.resolveAvailabilitySource.inc({ source: BlockInputAvailabilitySource.GOSSIP }); const blockInput = getBlockInput.availableData(config, signedBlock, BlockSource.gossip, blockData); resolveBlockInput(blockInput); return { blockInput, blockInputMeta: { pending: null, haveBlobs: allBlobs.blobs.length, expectedBlobs: blobKzgCommitments.length }, }; } const blockInput = getBlockInput.dataPromise(config, signedBlock, BlockSource.gossip, cachedData); resolveBlockInput(blockInput); return { blockInput, blockInputMeta: { pending: GossipedInputType.blob, haveBlobs: blobsCache.size, expectedBlobs: blobKzgCommitments.length, }, }; } // will need to wait for the block to showup if (cachedData === undefined) { throw Error("Missing cachedData for deneb+ blobs"); } const { blobsCache } = cachedData; return { blockInput: { block: null, blockRootHex: blockHex, cachedData, blockInputPromise, }, blockInputMeta: { pending: GossipedInputType.block, haveBlobs: blobsCache.size, expectedBlobs: null }, }; } } function getEmptyBlockInputCacheEntry(fork) { // Capture both the promise and its callbacks for blockInput and final availability // It is not spec'ed but in tests in Firefox and NodeJS the promise constructor is run immediately let resolveBlockInput = null; const blockInputPromise = new Promise((resolveCB) => { resolveBlockInput = resolveCB; }); if (resolveBlockInput === null) { throw Error("Promise Constructor was not executed immediately"); } if (!isForkPostDeneb(fork)) { return { fork, blockInputPromise, resolveBlockInput }; } let resolveAvailability = null; const availabilityPromise = new Promise((resolveCB) => { resolveAvailability = resolveCB; }); if (resolveAvailability === null) { throw Error("Promise Constructor was not executed immediately"); } const blobsCache = new Map(); const cachedData = { fork, blobsCache, availabilityPromise, resolveAvailability }; return { fork, blockInputPromise, resolveBlockInput, cachedData }; } //# sourceMappingURL=seenGossipBlockInput.js.map