@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
137 lines • 7.37 kB
JavaScript
import { ForkSeq, GENESIS_SLOT } from "@lodestar/params";
import { RespStatus, ResponseError } from "@lodestar/reqresp";
import { computeEpochAtSlot } from "@lodestar/state-transition";
import { fromHex } from "@lodestar/utils";
import { prettyPrintPeerId } from "../../util.js";
import { handleColumnSidecarUnavailability, validateRequestedDataColumns, } from "../utils/dataColumnResponseValidation.js";
export async function* onDataColumnSidecarsByRange(request, chain, db, peerId, peerClient) {
// Non-finalized range of columns
const { startSlot, count, columns: requestedColumns, } = validateDataColumnSidecarsByRangeRequest(chain.config, chain.clock.currentEpoch, request);
const availableColumns = validateRequestedDataColumns(chain, requestedColumns);
const endSlot = startSlot + count;
if (availableColumns.length === 0) {
return;
}
if (startSlot < chain.earliestAvailableSlot) {
chain.logger.verbose("Peer did not respect earliestAvailableSlot for DataColumnSidecarsByRange", {
peer: prettyPrintPeerId(peerId),
client: peerClient,
});
return;
}
const finalized = db.dataColumnSidecarArchive;
const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
// Columns of the last finalized block live in different DBs depending on fork:
// - Pre-gloas (fulu): migrated to dataColumnSidecarArchive in the same finalization run.
// - Post-gloas: stay in the hot db (db.dataColumnSidecar) until the next finalization run,
// because the migration filter requires payloadStatus === FULL for gloas blocks.
// archiveMaxSlot is the last slot whose columns are served by the archive loop below;
// anything above it is served by the headChain loop.
const isPostGloasFinalized = chain.config.getForkSeq(finalizedSlot) >= ForkSeq.gloas;
const archiveMaxSlot = isPostGloasFinalized ? finalizedSlot - 1 : finalizedSlot;
// Finalized range of columns
if (startSlot <= archiveMaxSlot) {
const archiveEnd = Math.min(endSlot, archiveMaxSlot + 1);
for (let slot = startSlot; slot < archiveEnd; slot++) {
const dataColumnSidecars = await finalized.getManyBinary(slot, availableColumns);
const unavailableColumnIndices = [];
for (let i = 0; i < dataColumnSidecars.length; i++) {
const dataColumnSidecarBytes = dataColumnSidecars[i];
if (dataColumnSidecarBytes) {
yield {
data: dataColumnSidecarBytes,
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(slot)),
};
}
// TODO: Check blobs for that block and respond resource_unavailable
// After we have consensus from other teams on the specs
else {
unavailableColumnIndices.push(availableColumns[i]);
}
}
if (unavailableColumnIndices.length) {
await handleColumnSidecarUnavailability({
chain,
db,
metrics: chain.metrics,
unavailableColumnIndices,
slot,
requestedColumns,
availableColumns,
});
}
}
}
// Non-finalized range of columns
if (endSlot > archiveMaxSlot) {
const headBlock = chain.forkChoice.getHead();
const headRoot = headBlock.blockRoot;
// getAllAncestorBlocks includes the last finalized block as its final element.
// Skip anything the archive loop above already served via the block.slot > archiveMaxSlot
// filter below (pre-gloas this skips finalizedSlot, post-gloas it keeps it).
const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);
// Iterate head chain with ascending block numbers
for (let i = headChain.length - 1; i >= 0; i--) {
const block = headChain[i];
// Must include only columns in the range requested
if (block.slot > archiveMaxSlot && block.slot >= startSlot && block.slot < endSlot) {
// Note: Here the forkChoice head may change due to a re-org, so the headChain reflects the canonical chain
// at the time of the start of the request. Spec is clear the chain of columns must be consistent, but on
// re-org there's no need to abort the request
// Spec: https://github.com/ethereum/consensus-specs/blob/ad36024441cf910d428d03f87f331fbbd2b3e5f1/specs/fulu/p2p-interface.md#L425-L429
const dataColumnSidecars = await chain.getSerializedDataColumnSidecars(block.slot, block.blockRoot, availableColumns);
const unavailableColumnIndices = [];
for (let i = 0; i < dataColumnSidecars.length; i++) {
const dataColumnSidecarBytes = dataColumnSidecars[i];
if (dataColumnSidecarBytes) {
yield {
data: dataColumnSidecarBytes,
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(block.slot)),
};
}
// TODO: Check blobs for that block and respond resource_unavailable
// After we have consensus from other teams on the specs
else {
unavailableColumnIndices.push(availableColumns[i]);
}
}
if (unavailableColumnIndices.length) {
await handleColumnSidecarUnavailability({
chain,
db,
metrics: chain.metrics,
unavailableColumnIndices,
blockRoot: fromHex(block.blockRoot),
slot: block.slot,
requestedColumns,
availableColumns,
});
}
}
// If block is after endSlot, stop iterating
else if (block.slot >= endSlot) {
break;
}
}
}
}
export function validateDataColumnSidecarsByRangeRequest(config, currentEpoch, request) {
const { startSlot, columns } = request;
let { count } = request;
if (count < 1) {
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
}
if (startSlot < GENESIS_SLOT) {
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
}
// Spec: [max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH), current_epoch]
const minimumRequestEpoch = Math.max(currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, config.FULU_FORK_EPOCH);
if (computeEpochAtSlot(startSlot) < minimumRequestEpoch) {
throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "startSlot is before MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS");
}
if (count > config.MAX_REQUEST_BLOCKS_DENEB) {
count = config.MAX_REQUEST_BLOCKS_DENEB;
}
return { startSlot, count, columns };
}
//# sourceMappingURL=dataColumnSidecarsByRange.js.map