@lodestar/beacon-node 1.51.0-dev.2593063a82 → 1.51.0-dev.75239241d4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chain/ColumnReconstructionTracker.d.ts +11 -11
- package/lib/chain/ColumnReconstructionTracker.d.ts.map +1 -1
- package/lib/chain/ColumnReconstructionTracker.js +46 -40
- package/lib/chain/ColumnReconstructionTracker.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +4 -5
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/blockInput/utils.js +2 -2
- package/lib/chain/blocks/blockInput/utils.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +1 -0
- package/lib/chain/chain.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +7 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +24 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +3 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +4 -4
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +3 -3
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
- package/package.json +15 -15
- package/src/chain/ColumnReconstructionTracker.ts +60 -41
- package/src/chain/archiveStore/utils/archiveBlocks.ts +6 -7
- package/src/chain/blocks/blockInput/utils.ts +2 -2
- package/src/chain/chain.ts +1 -0
- package/src/metrics/metrics/lodestar.ts +25 -0
- package/src/network/gossip/gossipsub.ts +3 -1
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +4 -10
- package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +3 -3
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
+
import {NUMBER_OF_COLUMNS} from "@lodestar/params";
|
|
3
|
+
import {RootHex} from "@lodestar/types";
|
|
2
4
|
import {Logger, sleep} from "@lodestar/utils";
|
|
3
5
|
import {Metrics} from "../metrics/metrics.js";
|
|
4
6
|
import {DataColumnReconstructionCode, recoverDataColumnSidecars} from "../util/dataColumns.js";
|
|
7
|
+
import {JobItemQueue} from "../util/queue/index.js";
|
|
5
8
|
import {BlockInputColumns} from "./blocks/blockInput/index.js";
|
|
6
9
|
import {PayloadEnvelopeInput} from "./blocks/payloadEnvelopeInput/index.js";
|
|
7
10
|
import {ChainEventEmitter} from "./emitter.js";
|
|
@@ -16,15 +19,22 @@ const RECONSTRUCTION_DELAY_MIN_BPS = 667;
|
|
|
16
19
|
*/
|
|
17
20
|
const RECONSTRUCTION_DELAY_MAX_BPS = 1000;
|
|
18
21
|
|
|
22
|
+
const QUEUE_MAX_LENGTH = 16;
|
|
23
|
+
|
|
24
|
+
export type ColumnReconstructionInput = BlockInputColumns | PayloadEnvelopeInput;
|
|
25
|
+
|
|
19
26
|
export type ColumnReconstructionTrackerInit = {
|
|
20
27
|
logger: Logger;
|
|
21
28
|
emitter: ChainEventEmitter;
|
|
22
29
|
metrics: Metrics | null;
|
|
23
30
|
config: ChainForkConfig;
|
|
31
|
+
signal: AbortSignal;
|
|
24
32
|
};
|
|
25
33
|
|
|
26
34
|
/**
|
|
27
|
-
*
|
|
35
|
+
* Runs column reconstruction one block root at a time, in trigger order. A trigger for a root that is
|
|
36
|
+
* queued or in flight is a no-op. A root is removed once its attempt finishes so a later column can
|
|
37
|
+
* retry a failed attempt.
|
|
28
38
|
*/
|
|
29
39
|
export class ColumnReconstructionTracker {
|
|
30
40
|
logger: Logger;
|
|
@@ -32,62 +42,71 @@ export class ColumnReconstructionTracker {
|
|
|
32
42
|
metrics: Metrics | null;
|
|
33
43
|
config: ChainForkConfig;
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* This is sufficient to avoid duplicate calls since we only call this
|
|
39
|
-
* function when we see a new data column sidecar from gossip.
|
|
40
|
-
*/
|
|
41
|
-
lastBlockRootHex: string | null = null;
|
|
42
|
-
/** Track if a reconstruction attempt is in-flight */
|
|
43
|
-
running = false;
|
|
45
|
+
private readonly signal: AbortSignal;
|
|
46
|
+
private readonly pending = new Set<RootHex>();
|
|
47
|
+
private readonly queue: JobItemQueue<[ColumnReconstructionInput, number], void>;
|
|
44
48
|
|
|
45
49
|
constructor(init: ColumnReconstructionTrackerInit) {
|
|
46
50
|
this.logger = init.logger;
|
|
47
51
|
this.emitter = init.emitter;
|
|
48
52
|
this.metrics = init.metrics;
|
|
49
53
|
this.config = init.config;
|
|
54
|
+
this.signal = init.signal;
|
|
55
|
+
this.queue = new JobItemQueue(
|
|
56
|
+
this.reconstruct,
|
|
57
|
+
{maxLength: QUEUE_MAX_LENGTH, signal: init.signal},
|
|
58
|
+
init.metrics?.columnReconstructionQueue
|
|
59
|
+
);
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
triggerColumnReconstruction(input:
|
|
53
|
-
if (this.
|
|
62
|
+
triggerColumnReconstruction(input: ColumnReconstructionInput): void {
|
|
63
|
+
if (this.pending.has(input.blockRootHex)) {
|
|
54
64
|
return;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
if (
|
|
67
|
+
if (input.getAllColumns().length < NUMBER_OF_COLUMNS / 2) {
|
|
58
68
|
return;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
this.pending.add(input.blockRootHex);
|
|
72
|
+
void this.enqueue(input);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async enqueue(input: ColumnReconstructionInput): Promise<void> {
|
|
76
|
+
try {
|
|
77
|
+
await this.queue.push(input, Date.now());
|
|
78
|
+
} catch (e) {
|
|
79
|
+
this.logger.debug(
|
|
80
|
+
"Data column sidecar reconstruction dropped",
|
|
81
|
+
{slot: input.slot, root: input.blockRootHex},
|
|
82
|
+
e as Error
|
|
83
|
+
);
|
|
84
|
+
} finally {
|
|
85
|
+
this.pending.delete(input.blockRootHex);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private reconstruct = async (input: ColumnReconstructionInput, queuedAtMs: number): Promise<void> => {
|
|
65
90
|
const fork = this.config.getForkName(input.slot);
|
|
66
91
|
const minDelayMs = this.config.getSlotComponentDurationMs(fork, RECONSTRUCTION_DELAY_MIN_BPS);
|
|
67
92
|
const maxDelayMs = this.config.getSlotComponentDurationMs(fork, RECONSTRUCTION_DELAY_MAX_BPS);
|
|
68
93
|
const delay = minDelayMs + Math.random() * (maxDelayMs - minDelayMs);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
})
|
|
89
|
-
.catch((err) => {
|
|
90
|
-
this.logger.debug("ColumnReconstructionTracker unreachable error", {}, err);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
94
|
+
|
|
95
|
+
// The delay gives gossip a chance to deliver the remaining columns, so time already spent in the
|
|
96
|
+
// queue behind another root counts towards it
|
|
97
|
+
await sleep(delay - (Date.now() - queuedAtMs), this.signal);
|
|
98
|
+
|
|
99
|
+
const logCtx = {slot: input.slot, root: input.blockRootHex};
|
|
100
|
+
this.logger.debug("Attempting data column sidecar reconstruction", logCtx);
|
|
101
|
+
try {
|
|
102
|
+
const result = await recoverDataColumnSidecars(input, this.emitter, this.metrics);
|
|
103
|
+
this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({result});
|
|
104
|
+
this.logger.debug("Data column sidecar reconstruction complete", {...logCtx, result});
|
|
105
|
+
} catch (e) {
|
|
106
|
+
this.metrics?.recoverDataColumnSidecars.reconstructionResult.inc({result: DataColumnReconstructionCode.Failed});
|
|
107
|
+
this.logger.debug("Error during data column sidecar reconstruction", logCtx, e as Error);
|
|
108
|
+
} finally {
|
|
109
|
+
this.logger.debug("Data column sidecar reconstruction attempt finished", logCtx);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
93
112
|
}
|
|
@@ -3,7 +3,7 @@ import {ChainForkConfig} from "@lodestar/config";
|
|
|
3
3
|
import {KeyValue} from "@lodestar/db";
|
|
4
4
|
import {CheckpointWithHex, ExecutionStatus, IForkChoice, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
|
|
5
5
|
import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
6
|
-
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
6
|
+
import {computeEpochAtSlot, computeStartSlotAtEpoch, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
|
|
7
7
|
import {Epoch, Slot} from "@lodestar/types";
|
|
8
8
|
import {Logger, fromAsync, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
9
9
|
import {IBeaconDb} from "../../../db/index.js";
|
|
@@ -253,14 +253,13 @@ export async function archiveBlocks(
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
// Delete expired data column sidecars
|
|
256
|
-
// Keep only `[current_epoch -
|
|
256
|
+
// Keep only `[min(compute_blob_data_retention_start_epoch(current_epoch), current_epoch - archiveDataEpochs)]`
|
|
257
257
|
if (finalizedPostFulu) {
|
|
258
258
|
if (archiveDataEpochs !== Infinity) {
|
|
259
|
-
const
|
|
260
|
-
config
|
|
261
|
-
archiveDataEpochs ?? 0
|
|
259
|
+
const dataColumnSidecarsMinEpoch = Math.min(
|
|
260
|
+
getBlobDataRetentionStartEpoch(config, currentEpoch),
|
|
261
|
+
currentEpoch - (archiveDataEpochs ?? 0)
|
|
262
262
|
);
|
|
263
|
-
const dataColumnSidecarsMinEpoch = currentEpoch - dataColumnSidecarsArchiveWindow;
|
|
264
263
|
if (dataColumnSidecarsMinEpoch >= config.FULU_FORK_EPOCH) {
|
|
265
264
|
const columnsPruneSlot = computeStartSlotAtEpoch(dataColumnSidecarsMinEpoch);
|
|
266
265
|
const prunedColumnSlots = await db.dataColumns.pruneBefore(columnsPruneSlot);
|
|
@@ -439,7 +438,7 @@ async function migrateDataColumnSidecarsFromHotToColdDb(
|
|
|
439
438
|
const blockEpoch = computeEpochAtSlot(block.slot);
|
|
440
439
|
if (
|
|
441
440
|
config.getForkSeq(block.slot) < ForkSeq.fulu ||
|
|
442
|
-
blockEpoch < currentEpoch
|
|
441
|
+
blockEpoch < getBlobDataRetentionStartEpoch(config, currentEpoch)
|
|
443
442
|
) {
|
|
444
443
|
continue;
|
|
445
444
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
2
|
import {ForkName, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
|
|
3
|
-
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
3
|
+
import {computeEpochAtSlot, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
|
|
4
4
|
import {Epoch, Slot} from "@lodestar/types";
|
|
5
5
|
|
|
6
6
|
export function isDaOutOfRange(
|
|
@@ -10,7 +10,7 @@ export function isDaOutOfRange(
|
|
|
10
10
|
currentEpoch: Epoch
|
|
11
11
|
): boolean {
|
|
12
12
|
if (isForkPostFulu(forkName)) {
|
|
13
|
-
return computeEpochAtSlot(blockSlot) < currentEpoch
|
|
13
|
+
return computeEpochAtSlot(blockSlot) < getBlobDataRetentionStartEpoch(config, currentEpoch);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (isForkPostDeneb(forkName)) {
|
package/src/chain/chain.ts
CHANGED
|
@@ -330,6 +330,31 @@ export function createLodestarMetrics(
|
|
|
330
330
|
}),
|
|
331
331
|
},
|
|
332
332
|
|
|
333
|
+
columnReconstructionQueue: {
|
|
334
|
+
length: register.gauge({
|
|
335
|
+
name: "lodestar_column_reconstruction_queue_length",
|
|
336
|
+
help: "Count of total column reconstruction queue length",
|
|
337
|
+
}),
|
|
338
|
+
droppedJobs: register.gauge({
|
|
339
|
+
name: "lodestar_column_reconstruction_queue_dropped_jobs_total",
|
|
340
|
+
help: "Count of total column reconstruction queue dropped jobs",
|
|
341
|
+
}),
|
|
342
|
+
jobTime: register.histogram({
|
|
343
|
+
name: "lodestar_column_reconstruction_queue_job_time_seconds",
|
|
344
|
+
help: "Time to process column reconstruction queue job in seconds",
|
|
345
|
+
buckets: [0.01, 0.1, 1, 4, 12],
|
|
346
|
+
}),
|
|
347
|
+
jobWaitTime: register.histogram({
|
|
348
|
+
name: "lodestar_column_reconstruction_queue_job_wait_time_seconds",
|
|
349
|
+
help: "Time from job added to the column reconstruction queue to starting in seconds",
|
|
350
|
+
buckets: [0.01, 0.1, 1, 4, 12],
|
|
351
|
+
}),
|
|
352
|
+
concurrency: register.gauge({
|
|
353
|
+
name: "lodestar_column_reconstruction_queue_concurrency",
|
|
354
|
+
help: "Current concurrency of column reconstruction queue",
|
|
355
|
+
}),
|
|
356
|
+
},
|
|
357
|
+
|
|
333
358
|
engineHttpProcessorQueue: {
|
|
334
359
|
length: register.gauge({
|
|
335
360
|
name: "lodestar_engine_http_processor_queue_length",
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
SLOTS_PER_EPOCH,
|
|
22
22
|
SYNC_COMMITTEE_SUBNET_COUNT,
|
|
23
23
|
} from "@lodestar/params";
|
|
24
|
+
import {getSlotDurations} from "@lodestar/state-transition";
|
|
24
25
|
import {SubnetID} from "@lodestar/types";
|
|
25
26
|
import {Logger, Map2d, Map2dArr} from "@lodestar/utils";
|
|
26
27
|
import {RegistryMetricCreator} from "../../metrics/index.js";
|
|
@@ -172,7 +173,8 @@ export class Eth2Gossipsub {
|
|
|
172
173
|
mcacheLength: 6,
|
|
173
174
|
mcacheGossip: 3,
|
|
174
175
|
// this should be in ms
|
|
175
|
-
|
|
176
|
+
// use the largest configured slot duration so the TTL stays conservative across duration changes
|
|
177
|
+
seenTTL: Math.max(...getSlotDurations(config).map(([, slotDurationMs]) => slotDurationMs)) * SLOTS_PER_EPOCH * 2,
|
|
176
178
|
scoreParams,
|
|
177
179
|
scoreThresholds: gossipScoreThresholds,
|
|
178
180
|
// For a single stream, await processing each RPC before processing the next
|
|
@@ -3,7 +3,7 @@ import {ChainConfig} from "@lodestar/config";
|
|
|
3
3
|
import {PayloadStatus} from "@lodestar/fork-choice";
|
|
4
4
|
import {ForkSeq, GENESIS_SLOT, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
|
|
5
5
|
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
|
|
6
|
-
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
6
|
+
import {computeEpochAtSlot, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
|
|
7
7
|
import {ColumnIndex, Epoch, RootHex, fulu} from "@lodestar/types";
|
|
8
8
|
import {fromHex, toRootHex} from "@lodestar/utils";
|
|
9
9
|
import {IBeaconChain} from "../../../chain/index.js";
|
|
@@ -216,16 +216,10 @@ export function validateDataColumnSidecarsByRangeRequest(
|
|
|
216
216
|
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
// Spec: [max(current_epoch
|
|
220
|
-
const minimumRequestEpoch = Math.max(
|
|
221
|
-
currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS,
|
|
222
|
-
config.FULU_FORK_EPOCH
|
|
223
|
-
);
|
|
219
|
+
// Spec: [max(compute_blob_data_retention_start_epoch(current_epoch), FULU_FORK_EPOCH), current_epoch]
|
|
220
|
+
const minimumRequestEpoch = Math.max(getBlobDataRetentionStartEpoch(config, currentEpoch), config.FULU_FORK_EPOCH);
|
|
224
221
|
if (computeEpochAtSlot(startSlot) < minimumRequestEpoch) {
|
|
225
|
-
throw new ResponseError(
|
|
226
|
-
RespStatus.RESOURCE_UNAVAILABLE,
|
|
227
|
-
"startSlot is before MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS"
|
|
228
|
-
);
|
|
222
|
+
throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "startSlot is before the blob data retention window");
|
|
229
223
|
}
|
|
230
224
|
|
|
231
225
|
if (count > config.MAX_REQUEST_BLOCKS_DENEB) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {PeerId} from "@libp2p/interface";
|
|
2
2
|
import {ResponseOutgoing} from "@lodestar/reqresp";
|
|
3
|
-
import {computeEpochAtSlot} from "@lodestar/state-transition";
|
|
3
|
+
import {computeEpochAtSlot, getBlobDataRetentionStartEpoch} from "@lodestar/state-transition";
|
|
4
4
|
import {ColumnIndex} from "@lodestar/types";
|
|
5
5
|
import {toRootHex} from "@lodestar/utils";
|
|
6
6
|
import {IBeaconChain} from "../../../chain/index.js";
|
|
@@ -19,10 +19,10 @@ export async function* onDataColumnSidecarsByRoot(
|
|
|
19
19
|
peerId: PeerId,
|
|
20
20
|
peerClient: string
|
|
21
21
|
): AsyncIterable<ResponseOutgoing> {
|
|
22
|
-
// SPEC: minimum_request_epoch = max(current_epoch
|
|
22
|
+
// SPEC: minimum_request_epoch = max(compute_blob_data_retention_start_epoch(current_epoch), FULU_FORK_EPOCH)
|
|
23
23
|
const currentEpoch = chain.clock.currentEpoch;
|
|
24
24
|
const minimumRequestEpoch = Math.max(
|
|
25
|
-
|
|
25
|
+
getBlobDataRetentionStartEpoch(chain.config, currentEpoch),
|
|
26
26
|
chain.config.FULU_FORK_EPOCH
|
|
27
27
|
);
|
|
28
28
|
|