@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
137 lines • 6.9 kB
JavaScript
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