diff --git a/packages/beacon-node/src/chain/blocks/blockInput/blockInput.ts b/packages/beacon-node/src/chain/blocks/blockInput/blockInput.ts index a35c155663e3..caf823cacca2 100644 --- a/packages/beacon-node/src/chain/blocks/blockInput/blockInput.ts +++ b/packages/beacon-node/src/chain/blocks/blockInput/blockInput.ts @@ -1,4 +1,4 @@ -import {ForkName, ForkPostFulu, ForkPreDeneb, ForkPreGloas} from "@lodestar/params"; +import {ForkName, ForkPostFulu, ForkPreDeneb, ForkPreGloas, NUMBER_OF_COLUMNS} from "@lodestar/params"; import {BeaconBlockBody, BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types"; import {fromHex, prettyBytes, toRootHex, withTimeout} from "@lodestar/utils"; import {VersionedHashes} from "../../../execution/index.js"; @@ -561,6 +561,7 @@ type BlockInputColumnsState = | { hasBlock: true; hasAllData: true; + hasComputedAllData: boolean; versionedHashes: VersionedHashes; block: SignedBeaconBlock; source: SourceMeta; @@ -569,6 +570,7 @@ type BlockInputColumnsState = | { hasBlock: true; hasAllData: false; + hasComputedAllData: false; versionedHashes: VersionedHashes; block: SignedBeaconBlock; source: SourceMeta; @@ -576,11 +578,13 @@ type BlockInputColumnsState = | { hasBlock: false; hasAllData: true; + hasComputedAllData: boolean; versionedHashes: VersionedHashes; } | { hasBlock: false; hasAllData: false; + hasComputedAllData: false; versionedHashes: VersionedHashes; }; /** @@ -598,6 +602,12 @@ export class BlockInputColumns extends AbstractBlockInput(); private readonly sampledColumns: ColumnIndex[]; private readonly custodyColumns: ColumnIndex[]; + /** + * This promise resolves when all sampled columns are available + * + * This is different from `dataPromise` which resolves when all data is available or could become available (e.g. through reconstruction) + */ + protected computedDataPromise = createPromise(); private constructor( init: BlockInputInit, @@ -626,6 +636,7 @@ export class BlockInputColumns extends AbstractBlockInput).blobKzgCommitments.length === 0 || this.state.hasAllData; + const hasComputedAllData = + props.block.message.body.blobKzgCommitments.length === 0 || this.state.hasComputedAllData; this.state = { ...this.state, hasBlock: true, hasAllData, + hasComputedAllData, block: props.block, source: { source: props.source, @@ -774,17 +791,32 @@ export class BlockInputColumns extends AbstractBlockInput= NUMBER_OF_COLUMNS / 2; + + const hasComputedAllData = + // has all sampled columns + sampledColumns.length === this.sampledColumns.length; this.state = { ...this.state, hasAllData: hasAllData || this.state.hasAllData, + hasComputedAllData: hasComputedAllData || this.state.hasComputedAllData, timeCompleteSec: hasAllData ? seenTimestampSec : undefined, } as BlockInputColumnsState; if (hasAllData && sampledColumns !== null) { this.dataPromise.resolve(sampledColumns); } + + if (hasComputedAllData && sampledColumns !== null) { + this.computedDataPromise.resolve(sampledColumns); + } } hasColumn(columnIndex: number): boolean { @@ -859,4 +891,15 @@ export class BlockInputColumns extends AbstractBlockInput { + if (!this.state.hasComputedAllData) { + return withTimeout(() => this.computedDataPromise.promise, timeout, signal); + } + return Promise.resolve(this.getSampledColumns()); + } } diff --git a/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts b/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts index b008bcd94eff..07d1e9f508d4 100644 --- a/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts +++ b/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts @@ -44,6 +44,15 @@ export async function writeBlockInputToDb(this: BeaconChain, blocksInputs: IBloc // NOTE: Old data is pruned on archive if (isBlockInputColumns(blockInput)) { + if (!blockInput.hasComputedAllData()) { + // Supernodes may only have a subset of the data columns by the time the block begins to be imported + // because full data availability can be assumed after NUMBER_OF_COLUMNS / 2 columns are available. + // Here, however, all data columns must be fully available/reconstructed before persisting to the DB. + await blockInput.waitForComputedAllData(BLOB_AVAILABILITY_TIMEOUT).catch(() => { + this.logger.debug("Failed to wait for computed all data", {slot, blockRoot: blockRootHex}); + }); + } + const {custodyColumns} = this.custodyConfig; const blobsLen = (block.message as fulu.BeaconBlock).body.blobKzgCommitments.length; let dataColumnsLen: number; diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 29712cee4aa6..773b35644ea7 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -576,7 +576,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand break; } - if (!blockInput.hasAllData()) { + if (!blockInput.hasComputedAllData()) { // immediately attempt fetch of data columns from execution engine chain.getBlobsTracker.triggerGetBlobs(blockInput); // if we've received at least half of the columns, trigger reconstruction of the rest