UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

96 lines (85 loc) 3.38 kB
import {ChainForkConfig} from "@lodestar/config"; import {Logger, sleep} from "@lodestar/utils"; import {Metrics} from "../metrics/metrics.js"; import {DataColumnReconstructionCode, recoverDataColumnSidecars} from "../util/dataColumns.js"; import {BlockInputColumns} from "./blocks/blockInput/index.js"; import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/index.js"; import {ChainEventEmitter} from "./emitter.js"; /** * Minimum time to wait before attempting reconstruction */ const RECONSTRUCTION_DELAY_MIN_BPS = 667; /** * Maximum time to wait before attempting reconstruction */ const RECONSTRUCTION_DELAY_MAX_BPS = 1000; export type ColumnReconstructionTrackerInit = { logger: Logger; emitter: ChainEventEmitter; metrics: Metrics | null; config: ChainForkConfig; }; /** * Tracks column reconstruction attempts to avoid duplicate and multiple in-flight calls */ export class ColumnReconstructionTracker { logger: Logger; emitter: ChainEventEmitter; metrics: Metrics | null; config: ChainForkConfig; /** * Track last attempted block root * * This is sufficient to avoid duplicate calls since we only call this * function when we see a new data column sidecar from gossip. */ lastBlockRootHex: string | null = null; /** Track if a reconstruction attempt is in-flight */ running = false; private readonly minDelayMs: number; private readonly maxDelayMs: number; constructor(init: ColumnReconstructionTrackerInit) { this.logger = init.logger; this.emitter = init.emitter; this.metrics = init.metrics; this.config = init.config; this.minDelayMs = this.config.getSlotComponentDurationMs(RECONSTRUCTION_DELAY_MIN_BPS); this.maxDelayMs = this.config.getSlotComponentDurationMs(RECONSTRUCTION_DELAY_MAX_BPS); } triggerColumnReconstruction(input: BlockInputColumns | PayloadEnvelopeInput): void { if (this.running) { return; } if (this.lastBlockRootHex === input.blockRootHex) { return; } // We don't care about the outcome of this call, // just that it has been triggered for this block root. this.running = true; this.lastBlockRootHex = input.blockRootHex; const delay = this.minDelayMs + Math.random() * (this.maxDelayMs - this.minDelayMs); sleep(delay) .then(() => { const logCtx = {slot: input.slot, root: input.blockRootHex}; this.logger.debug("Attempting data column sidecar reconstruction", logCtx); recoverDataColumnSidecars(input, this.emitter, this.metrics) .then((result) => { this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({result}); this.logger.debug("Data column sidecar reconstruction complete", {...logCtx, result}); }) .catch((e) => { this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({ result: DataColumnReconstructionCode.Failed, }); this.logger.debug("Error during data column sidecar reconstruction", logCtx, e as Error); }) .finally(() => { this.logger.debug("Data column sidecar reconstruction attempt finished", logCtx); this.running = false; }); }) .catch((err) => { this.logger.debug("ColumnReconstructionTracker unreachable error", {}, err); }); } }