@lodestar/beacon-node 1.51.0-dev.23bc3d7be9 → 1.51.0-dev.2531f8fae8
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/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +12 -25
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +5 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +4 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/GetBlobsTracker.d.ts +11 -0
- package/lib/chain/GetBlobsTracker.d.ts.map +1 -1
- package/lib/chain/GetBlobsTracker.js +83 -27
- package/lib/chain/GetBlobsTracker.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +1 -0
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +4 -0
- package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js +7 -1
- package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +6 -5
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +4 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +7 -2
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.js +2 -2
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.d.ts +5 -5
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +7 -7
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +1 -1
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.js +8 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.js.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts +3 -3
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +13 -6
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
- package/lib/chain/bls/multithread/index.js +5 -5
- package/lib/chain/bls/multithread/index.js.map +1 -1
- package/lib/chain/chain.js +4 -4
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +1 -1
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +1 -1
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/interface.d.ts +1 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/options.d.ts +0 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +0 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/db/beacon.d.ts +7 -0
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +32 -0
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/dataColumnStore.d.ts +1 -1
- package/lib/db/dataColumnStore.d.ts.map +1 -1
- package/lib/db/dataColumnStore.js +1 -4
- package/lib/db/dataColumnStore.js.map +1 -1
- package/lib/db/interface.d.ts +1 -0
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/dataColumnSidecarArchive.d.ts +6 -0
- package/lib/db/repositories/dataColumnSidecarArchive.d.ts.map +1 -1
- package/lib/db/repositories/dataColumnSidecarArchive.js +19 -0
- package/lib/db/repositories/dataColumnSidecarArchive.js.map +1 -1
- package/lib/execution/engine/index.d.ts +1 -1
- package/lib/execution/engine/index.d.ts.map +1 -1
- package/lib/execution/engine/index.js +1 -1
- package/lib/execution/engine/index.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +1 -1
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/interface.js.map +1 -1
- package/lib/monitoring/service.js +1 -1
- package/lib/monitoring/service.js.map +1 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +3 -1
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +6 -3
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +9 -1
- package/lib/node/nodejs.js.map +1 -1
- package/lib/node/options.d.ts +1 -1
- package/lib/node/options.d.ts.map +1 -1
- package/lib/node/options.js +1 -1
- package/lib/node/options.js.map +1 -1
- package/lib/sync/interface.d.ts.map +1 -1
- package/lib/sync/interface.js.map +1 -1
- package/lib/util/queue/itemQueue.js +1 -1
- package/lib/util/queue/itemQueue.js.map +1 -1
- package/lib/util/sszBytes.js +2 -2
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +29 -43
- package/src/api/impl/beacon/pool/index.ts +5 -1
- package/src/api/impl/validator/index.ts +4 -1
- package/src/chain/GetBlobsTracker.ts +95 -38
- package/src/chain/archiveStore/archiveStore.ts +1 -0
- package/src/chain/archiveStore/utils/archiveBlocks.ts +12 -4
- package/src/chain/blocks/importBlock.ts +8 -7
- package/src/chain/blocks/importExecutionPayload.ts +12 -2
- package/src/chain/blocks/index.ts +6 -2
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +7 -7
- package/src/chain/blocks/verifyBlock.ts +1 -0
- package/src/chain/blocks/verifyBlocksDataAvailability.ts +10 -1
- package/src/chain/blocks/verifyPayloadsDataAvailability.ts +16 -6
- package/src/chain/bls/multithread/index.ts +5 -5
- package/src/chain/chain.ts +4 -4
- package/src/chain/errors/blockError.ts +1 -1
- package/src/chain/interface.ts +1 -2
- package/src/chain/options.ts +0 -2
- package/src/db/beacon.ts +39 -0
- package/src/db/dataColumnStore.ts +2 -4
- package/src/db/interface.ts +2 -0
- package/src/db/repositories/dataColumnSidecarArchive.ts +20 -0
- package/src/execution/engine/index.ts +1 -1
- package/src/execution/engine/interface.ts +1 -1
- package/src/monitoring/service.ts +1 -1
- package/src/network/network.ts +3 -1
- package/src/network/processor/gossipHandlers.ts +6 -3
- package/src/node/nodejs.ts +18 -1
- package/src/node/options.ts +4 -5
- package/src/sync/interface.ts +1 -0
- package/src/util/queue/itemQueue.ts +1 -1
- package/src/util/sszBytes.ts +2 -2
|
@@ -13,11 +13,11 @@ const QUEUE_MAX_LENGTH = 16;
|
|
|
13
13
|
*
|
|
14
14
|
* Jobs are enqueued only on envelope arrival (gossip or API). The envelope may reach us before
|
|
15
15
|
* the sampled data columns; importExecutionPayload awaits `verifyPayloadsDataAvailability`
|
|
16
|
-
* internally, so a queued job can pend for up to
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
16
|
+
* internally, so a queued job can pend for up to one slot while waiting for columns. Duplicate
|
|
17
|
+
* triggers for the same payloadInput are deduped by sharing the in-flight import promise: every
|
|
18
|
+
* caller observes the real import outcome. Resolving duplicates early instead would let
|
|
19
|
+
* BlockInputSync mark a still-pending payload as processed, re-queue it, and spin re-processing
|
|
20
|
+
* the same root until OOM while the import is blocked (e.g. EL syncing).
|
|
21
21
|
*/
|
|
22
22
|
export class PayloadEnvelopeProcessor {
|
|
23
23
|
readonly jobQueue: JobItemQueue<[PayloadEnvelopeInput, ImportPayloadOpts], void>;
|
|
@@ -35,7 +35,7 @@ export class PayloadEnvelopeProcessor {
|
|
|
35
35
|
|
|
36
36
|
async processPayloadEnvelopeJob(payloadInput: PayloadEnvelopeInput, opts: ImportPayloadOpts = {}): Promise<void> {
|
|
37
37
|
const existing = this.imports.get(payloadInput);
|
|
38
|
-
if (existing) {
|
|
38
|
+
if (existing !== undefined) {
|
|
39
39
|
return existing;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ export class PayloadEnvelopeProcessor {
|
|
|
43
43
|
|
|
44
44
|
// Re-check after await, as another call may have queued this payload.
|
|
45
45
|
const queued = this.imports.get(payloadInput);
|
|
46
|
-
if (queued) {
|
|
46
|
+
if (queued !== undefined) {
|
|
47
47
|
return queued;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -143,6 +143,7 @@ export async function verifyBlocksInEpoch(
|
|
|
143
143
|
? Array.from(payloadEnvelopes.values()).filter((payloadInput) => payloadInput.hasPayloadEnvelope())
|
|
144
144
|
: [];
|
|
145
145
|
const {dataAvailabilityStatuses, availableTime} = await verifyPayloadsDataAvailability(
|
|
146
|
+
this.config,
|
|
146
147
|
payloadInputsForDa,
|
|
147
148
|
abortController.signal
|
|
148
149
|
);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {DataAvailabilityStatus} from "@lodestar/state-transition";
|
|
2
|
+
import {TimeoutError} from "@lodestar/utils";
|
|
3
|
+
import {BlockError, BlockErrorCode} from "../errors/index.js";
|
|
2
4
|
import {DAData, DAType, IBlockInput} from "./blockInput/index.js";
|
|
3
5
|
|
|
4
6
|
// we can now wait for full 12 seconds because unavailable block sync will try pulling
|
|
@@ -22,7 +24,14 @@ export async function verifyBlocksDataAvailability(
|
|
|
22
24
|
for (const blockInput of blocks) {
|
|
23
25
|
// block verification is triggered on a verified gossip block so we only need to wait for all data
|
|
24
26
|
if (!blockInput.hasAllData()) {
|
|
25
|
-
promises.push(
|
|
27
|
+
promises.push(
|
|
28
|
+
blockInput.waitForAllData(BLOB_AVAILABILITY_TIMEOUT, signal).catch((e) => {
|
|
29
|
+
if (e instanceof TimeoutError) {
|
|
30
|
+
throw new BlockError(blockInput.getBlock(), {code: BlockErrorCode.DATA_UNAVAILABLE});
|
|
31
|
+
}
|
|
32
|
+
throw e;
|
|
33
|
+
})
|
|
34
|
+
);
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
await Promise.all(promises);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import {ChainForkConfig} from "@lodestar/config";
|
|
1
2
|
import {DataAvailabilityStatus} from "@lodestar/state-transition";
|
|
2
3
|
import {gloas} from "@lodestar/types";
|
|
4
|
+
import {TimeoutError} from "@lodestar/utils";
|
|
3
5
|
import {PayloadEnvelopeInput} from "../seenCache/seenPayloadEnvelopeInput.js";
|
|
4
|
-
|
|
5
|
-
// we can now wait for full 12 seconds because sync and reconstruction will try pulling
|
|
6
|
-
// the data columns from the network anyway while the envelope is being processed
|
|
7
|
-
export const PAYLOAD_DATA_AVAILABILITY_TIMEOUT = 12_000;
|
|
6
|
+
import {PayloadError, PayloadErrorCode} from "./importExecutionPayload.js";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Verifies that all payload envelope inputs have their data columns available.
|
|
11
|
-
* - Waits a max of
|
|
10
|
+
* - Waits a max of one slot duration of the payload's fork for all data to be available
|
|
12
11
|
* - Returns the time at which all data was available
|
|
13
12
|
* - Returns the data availability status for each payload input
|
|
14
13
|
*/
|
|
15
14
|
export async function verifyPayloadsDataAvailability(
|
|
15
|
+
config: ChainForkConfig,
|
|
16
16
|
payloadInputs: PayloadEnvelopeInput[],
|
|
17
17
|
signal: AbortSignal
|
|
18
18
|
): Promise<{
|
|
@@ -22,7 +22,17 @@ export async function verifyPayloadsDataAvailability(
|
|
|
22
22
|
const promises: Promise<gloas.DataColumnSidecar[]>[] = [];
|
|
23
23
|
for (const payloadInput of payloadInputs) {
|
|
24
24
|
if (!payloadInput.hasAllData()) {
|
|
25
|
-
|
|
25
|
+
// Sync and reconstruction keep pulling the data columns from the network while we wait. The wait is
|
|
26
|
+
// capped at one slot because the payload queue is serial: a longer wait lets consecutive unavailable
|
|
27
|
+
// payloads push every later payload import further behind with each slot.
|
|
28
|
+
promises.push(
|
|
29
|
+
payloadInput.waitForAllData(config.getSlotDurationMs(payloadInput.forkName), signal).catch((e) => {
|
|
30
|
+
if (e instanceof TimeoutError) {
|
|
31
|
+
throw new PayloadError(payloadInput, {code: PayloadErrorCode.DATA_UNAVAILABLE});
|
|
32
|
+
}
|
|
33
|
+
throw e;
|
|
34
|
+
})
|
|
35
|
+
);
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
await Promise.all(promises);
|
|
@@ -342,7 +342,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
|
|
|
342
342
|
} else {
|
|
343
343
|
this.jobs.push(job);
|
|
344
344
|
}
|
|
345
|
-
callInNextEventLoop(this.runJob);
|
|
345
|
+
callInNextEventLoop(() => void this.runJob());
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
|
|
@@ -377,7 +377,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
|
|
|
377
377
|
let completedJobsCount = 0;
|
|
378
378
|
|
|
379
379
|
if (this.jobs.length > 0) {
|
|
380
|
-
callInNextEventLoop(this.runJob);
|
|
380
|
+
callInNextEventLoop(() => void this.runJob());
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
try {
|
|
@@ -423,7 +423,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
|
|
|
423
423
|
if (workReqs.length === 0) {
|
|
424
424
|
worker.status = {code: WorkerStatusCode.idle, workerApi};
|
|
425
425
|
this.workersBusy--;
|
|
426
|
-
callInNextEventLoop(this.runJob);
|
|
426
|
+
callInNextEventLoop(() => void this.runJob());
|
|
427
427
|
return;
|
|
428
428
|
}
|
|
429
429
|
|
|
@@ -499,7 +499,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
|
|
|
499
499
|
this.workersBusy--;
|
|
500
500
|
|
|
501
501
|
// Potentially run a new job
|
|
502
|
-
callInNextEventLoop(this.runJob);
|
|
502
|
+
callInNextEventLoop(() => void this.runJob());
|
|
503
503
|
};
|
|
504
504
|
|
|
505
505
|
/**
|
|
@@ -542,7 +542,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier {
|
|
|
542
542
|
this.jobs.unshift(job);
|
|
543
543
|
}
|
|
544
544
|
this.bufferedJobs = null;
|
|
545
|
-
callInNextEventLoop(this.runJob);
|
|
545
|
+
callInNextEventLoop(() => void this.runJob());
|
|
546
546
|
}
|
|
547
547
|
};
|
|
548
548
|
|
package/src/chain/chain.ts
CHANGED
|
@@ -1486,10 +1486,10 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1486
1486
|
this.metrics?.forkChoice.requests.inc();
|
|
1487
1487
|
const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.predictProposerHead});
|
|
1488
1488
|
const slot = this.clock.currentSlot;
|
|
1489
|
-
const
|
|
1489
|
+
const msFromSlot = this.clock.msFromSlot(slot);
|
|
1490
1490
|
|
|
1491
1491
|
try {
|
|
1492
|
-
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead,
|
|
1492
|
+
return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, msFromSlot, slot}).head;
|
|
1493
1493
|
} catch (e) {
|
|
1494
1494
|
this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetPredictedProposerHead});
|
|
1495
1495
|
throw e;
|
|
@@ -1501,13 +1501,13 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1501
1501
|
getProposerHead(slot: Slot): ProtoBlock {
|
|
1502
1502
|
this.metrics?.forkChoice.requests.inc();
|
|
1503
1503
|
const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.getProposerHead});
|
|
1504
|
-
const
|
|
1504
|
+
const msFromSlot = this.clock.msFromSlot(slot);
|
|
1505
1505
|
|
|
1506
1506
|
try {
|
|
1507
1507
|
// Do not emit head event here, when proposing we rely on the one emitted when importing our own block
|
|
1508
1508
|
const {head, isHeadTimely, notReorgedReason} = this.forkChoice.updateAndGetHead({
|
|
1509
1509
|
mode: UpdateHeadOpt.GetProposerHead,
|
|
1510
|
-
|
|
1510
|
+
msFromSlot,
|
|
1511
1511
|
slot,
|
|
1512
1512
|
});
|
|
1513
1513
|
|
|
@@ -72,7 +72,7 @@ export enum BlockErrorCode {
|
|
|
72
72
|
* whoever served the block MAY be downscored. Mirrors PayloadErrorCode.EXECUTION_ENGINE_INVALID.
|
|
73
73
|
*/
|
|
74
74
|
EXECUTION_ENGINE_INVALID = "BLOCK_ERROR_EXECUTION_INVALID",
|
|
75
|
-
/** The
|
|
75
|
+
/** The blob or column data did not arrive within the availability wait */
|
|
76
76
|
DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
|
|
77
77
|
/** Block contains too many kzg commitments */
|
|
78
78
|
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
|
package/src/chain/interface.ts
CHANGED
|
@@ -77,8 +77,7 @@ import {PayloadEnvelopeInput, SeenPayloadEnvelopeInput} from "./seenCache/seenPa
|
|
|
77
77
|
import {ShufflingCache} from "./shufflingCache.js";
|
|
78
78
|
import {ValidatorMonitor} from "./validatorMonitor.js";
|
|
79
79
|
|
|
80
|
-
export {BlockType, type
|
|
81
|
-
export {type ProposerPreparationData};
|
|
80
|
+
export {type AssembledBlockType, BlockType, type ProposerPreparationData};
|
|
82
81
|
export type BlockHash = RootHex;
|
|
83
82
|
|
|
84
83
|
export type StateGetOpts = {
|
package/src/chain/options.ts
CHANGED
|
@@ -48,7 +48,6 @@ export type IChainOptions = BlockProcessOpts &
|
|
|
48
48
|
/** Max number of produced block roots (blinded or full) cached for broadcast validations */
|
|
49
49
|
maxCachedProducedRoots?: number;
|
|
50
50
|
initialCustodyGroupCount?: number;
|
|
51
|
-
broadcastValidationStrictness?: string;
|
|
52
51
|
minSameMessageSignatureSetsToBatch: number;
|
|
53
52
|
archiveDateEpochs?: number;
|
|
54
53
|
nHistoricalStatesFileDataStore?: boolean;
|
|
@@ -121,7 +120,6 @@ export const defaultChainOptions: IChainOptions = {
|
|
|
121
120
|
// for attestation validation, having this value ensures we don't have to regen states most of the time
|
|
122
121
|
maxSkipSlots: 32,
|
|
123
122
|
disableProposerSlashings: false,
|
|
124
|
-
broadcastValidationStrictness: "warn",
|
|
125
123
|
// should be less than or equal to MIN_SIGNATURE_SETS_TO_BATCH_VERIFY
|
|
126
124
|
// batching too much may block the I/O thread so if useWorker=false, suggest this value to be 32
|
|
127
125
|
// since this batch attestation work is designed to work with useWorker=true, make this the lowest value
|
package/src/db/beacon.ts
CHANGED
|
@@ -30,6 +30,9 @@ import {
|
|
|
30
30
|
VoluntaryExitRepository,
|
|
31
31
|
} from "./repositories/index.js";
|
|
32
32
|
|
|
33
|
+
/** Below this size, seeks over leftover legacy column data stay cheap */
|
|
34
|
+
const LEGACY_DATA_COLUMNS_COMPACTION_MIN_BYTES = 1024 ** 3;
|
|
35
|
+
|
|
33
36
|
export type BeaconDbOpts = {
|
|
34
37
|
dataColumnDir: string;
|
|
35
38
|
logger: Logger;
|
|
@@ -67,6 +70,7 @@ export class BeaconDb implements IBeaconDb {
|
|
|
67
70
|
readonly dataColumns: IDataColumnStore;
|
|
68
71
|
lastLegacyArchiveSlot: Slot | null = null;
|
|
69
72
|
private readonly flatFileStore: FlatFileStore;
|
|
73
|
+
private readonly logger: Logger;
|
|
70
74
|
|
|
71
75
|
constructor(
|
|
72
76
|
config: ChainForkConfig,
|
|
@@ -100,6 +104,7 @@ export class BeaconDb implements IBeaconDb {
|
|
|
100
104
|
this.syncCommittee = new SyncCommitteeRepository(config, db);
|
|
101
105
|
this.syncCommitteeWitness = new SyncCommitteeWitnessRepository(config, db);
|
|
102
106
|
|
|
107
|
+
this.logger = opts.logger;
|
|
103
108
|
this.flatFileStore = new FlatFileStore(opts.dataColumnDir, config, opts.logger, opts.metrics);
|
|
104
109
|
this.dataColumns = new LegacyDataColumnStore(
|
|
105
110
|
this.flatFileStore,
|
|
@@ -125,6 +130,40 @@ export class BeaconDb implements IBeaconDb {
|
|
|
125
130
|
this.flatFileStore.setMetrics(flatFileStoreMetrics);
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Deletes made while an iterator held a snapshot can stay in the bottom LevelDB level, which `compactRange` never
|
|
135
|
+
* rewrites, so every seek into the legacy column buckets walks them. Writing a key at each end of the pruned range
|
|
136
|
+
* makes the compaction overlap those files and rewrite them.
|
|
137
|
+
*/
|
|
138
|
+
async compactLegacyDataColumns(
|
|
139
|
+
prunedBeforeSlot: Slot,
|
|
140
|
+
minSizeBytes = LEGACY_DATA_COLUMNS_COMPACTION_MIN_BYTES
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
const archiveBucket = Bucket.allForks_dataColumnSidecarsArchive;
|
|
143
|
+
const start = encodeKey(Bucket.allForks_dataColumnSidecars, Buffer.alloc(0));
|
|
144
|
+
const end = encodeKey(archiveBucket, this.dataColumnSidecarArchive.getMinKeyRaw(prunedBeforeSlot));
|
|
145
|
+
const sizeBytes = await this.db.approximateSize(start, end);
|
|
146
|
+
if (sizeBytes < minSizeBytes) return;
|
|
147
|
+
|
|
148
|
+
// Column indices stay below 0xffff and hot column keys are shorter, so no sentinel matches a real key
|
|
149
|
+
const upperSentinel =
|
|
150
|
+
prunedBeforeSlot > 0
|
|
151
|
+
? encodeKey(archiveBucket, this.dataColumnSidecarArchive.encodeKeyRaw(prunedBeforeSlot - 1, 0xffff))
|
|
152
|
+
: encodeKey(Bucket.allForks_dataColumnSidecars, Buffer.alloc(35, 0xff));
|
|
153
|
+
const sentinels = [start, upperSentinel];
|
|
154
|
+
|
|
155
|
+
this.logger.info("Compacting legacy data columns, this may take a while", {sizeBytes});
|
|
156
|
+
const startTime = Date.now();
|
|
157
|
+
await this.db.batchPut(sentinels.map((key) => ({key, value: new Uint8Array()})));
|
|
158
|
+
await this.db.compactRange(start, end);
|
|
159
|
+
await this.db.batchDelete(sentinels);
|
|
160
|
+
await this.db.compactRange(start, end);
|
|
161
|
+
this.logger.verbose("Compacted legacy data columns", {
|
|
162
|
+
durationMs: Date.now() - startTime,
|
|
163
|
+
sizeBytes: await this.db.approximateSize(start, end),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
128
167
|
async pruneHotDb(): Promise<void> {
|
|
129
168
|
// Prune all hot blobs
|
|
130
169
|
await this.blobSidecars.batchDelete(await this.blobSidecars.keys());
|
|
@@ -13,7 +13,7 @@ export type IndexedDataColumnBytes = {index: ColumnIndex; data: Uint8Array};
|
|
|
13
13
|
type LegacyHotDataColumnStore = Pick<DataColumnSidecarRepository, "values" | "getManyBinary" | "deleteMany">;
|
|
14
14
|
type LegacyArchiveDataColumnStore = Pick<
|
|
15
15
|
DataColumnSidecarArchiveRepository,
|
|
16
|
-
"values" | "getManyBinary" | "
|
|
16
|
+
"values" | "getManyBinary" | "pruneBefore"
|
|
17
17
|
>;
|
|
18
18
|
type BlockRootIndex = Pick<BlockArchiveRepository, "getSlotByRoot">;
|
|
19
19
|
|
|
@@ -86,9 +86,7 @@ export class LegacyDataColumnStore implements IDataColumnStore {
|
|
|
86
86
|
|
|
87
87
|
async pruneBefore(slot: Slot): Promise<Slot[]> {
|
|
88
88
|
const flatFileSlots = await this.flatFiles.pruneBefore(slot);
|
|
89
|
-
const
|
|
90
|
-
const slots = [...new Set(prefixedKeys.map(({prefix}) => prefix))];
|
|
91
|
-
if (slots.length > 0) await this.legacyArchive.deleteMany(slots);
|
|
89
|
+
const slots = await this.legacyArchive.pruneBefore(slot);
|
|
92
90
|
return [...new Set([...flatFileSlots, ...slots])].sort((a, b) => a - b);
|
|
93
91
|
}
|
|
94
92
|
}
|
package/src/db/interface.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface IBeaconDb {
|
|
|
68
68
|
readonly lastLegacyArchiveSlot: Slot | null;
|
|
69
69
|
init(): Promise<void>;
|
|
70
70
|
|
|
71
|
+
compactLegacyDataColumns(prunedBeforeSlot: Slot, minSizeBytes?: number): Promise<void>;
|
|
72
|
+
|
|
71
73
|
pruneHotDb(): Promise<void>;
|
|
72
74
|
|
|
73
75
|
deleteDeprecatedEth1Data(): Promise<void>;
|
|
@@ -15,6 +15,8 @@ const SLOT_BYTE_SIZE = 8;
|
|
|
15
15
|
* Indexed data by `slot` + `columnIndex`
|
|
16
16
|
*/
|
|
17
17
|
export class DataColumnSidecarArchiveRepository extends PrefixedRepository<Slot, ColumnIndex, DataColumnSidecar> {
|
|
18
|
+
private prunedBeforeSlot: Slot | null = null;
|
|
19
|
+
|
|
18
20
|
constructor(config: ChainForkConfig, db: Db) {
|
|
19
21
|
const bucket = Bucket.allForks_dataColumnSidecarsArchive;
|
|
20
22
|
// Type won't be used since we select it dynamically based on fork
|
|
@@ -70,4 +72,22 @@ export class DataColumnSidecarArchiveRepository extends PrefixedRepository<Slot,
|
|
|
70
72
|
encodeNumberForDbKey(0, COLUMN_INDEX_BYTE_SIZE),
|
|
71
73
|
]);
|
|
72
74
|
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Deletes all entries before `slot`. Prunes only delete from the start of the bucket, so the first prune
|
|
78
|
+
* compacts the range to drop tombstones of earlier runs and later prunes resume where the previous one ended.
|
|
79
|
+
*/
|
|
80
|
+
async pruneBefore(slot: Slot): Promise<Slot[]> {
|
|
81
|
+
if (this.prunedBeforeSlot === null) {
|
|
82
|
+
await this.db.compactRange(this.wrapKey(this.getMinKeyRaw(0)), this.wrapKey(this.getMinKeyRaw(slot)));
|
|
83
|
+
} else if (slot <= this.prunedBeforeSlot) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const keys = await this.keys({gte: {prefix: this.prunedBeforeSlot ?? 0, id: 0}, lt: {prefix: slot, id: 0}});
|
|
88
|
+
const slots = [...new Set(keys.map(({prefix}) => prefix))];
|
|
89
|
+
if (slots.length > 0) await this.deleteMany(slots);
|
|
90
|
+
this.prunedBeforeSlot = slot;
|
|
91
|
+
return slots;
|
|
92
|
+
}
|
|
73
93
|
}
|
|
@@ -17,8 +17,8 @@ import {EngineRestHttpClient} from "./restHttpClient.js";
|
|
|
17
17
|
import {RestEngineTransport, formatClientVersionHeader} from "./restTransport.js";
|
|
18
18
|
import {ExecutionEngineMockJsonRpcClient, JsonRpcBackend} from "./utils.js";
|
|
19
19
|
|
|
20
|
-
export {ExecutionEngineHttp, ExecutionEngineDisabled, defaultExecutionEngineHttpOpts, engineApiModes};
|
|
21
20
|
export type {EngineApiMode};
|
|
21
|
+
export {ExecutionEngineDisabled, ExecutionEngineHttp, defaultExecutionEngineHttpOpts, engineApiModes};
|
|
22
22
|
|
|
23
23
|
export type ExecutionEngineOpts =
|
|
24
24
|
| ({mode?: "http"} & ExecutionEngineHttpOpts)
|
|
@@ -25,7 +25,7 @@ import {PayloadId, PayloadIdCache, WithdrawalV1} from "./payloadIdCache.js";
|
|
|
25
25
|
import {ExecutionPayloadBodyV2} from "./types.js";
|
|
26
26
|
import {DATA} from "./utils.js";
|
|
27
27
|
|
|
28
|
-
export {
|
|
28
|
+
export {type PayloadId, PayloadIdCache, type WithdrawalV1};
|
|
29
29
|
export type ClientVersion = routes.node.ClientVersion;
|
|
30
30
|
export const ClientCode = routes.node.ClientCode;
|
|
31
31
|
|
|
@@ -101,7 +101,7 @@ export class MonitoringService {
|
|
|
101
101
|
if (this.monitoringInterval) {
|
|
102
102
|
clearTimeout(this.monitoringInterval);
|
|
103
103
|
}
|
|
104
|
-
if (this.pendingRequest) {
|
|
104
|
+
if (this.pendingRequest !== undefined) {
|
|
105
105
|
this.fetchAbortController?.abort(FetchAbortReason.Close);
|
|
106
106
|
await this.pendingRequest;
|
|
107
107
|
}
|
package/src/network/network.ts
CHANGED
|
@@ -855,7 +855,9 @@ export class Network implements INetwork {
|
|
|
855
855
|
};
|
|
856
856
|
|
|
857
857
|
private onTargetGroupCountUpdated = (count: number): void => {
|
|
858
|
-
this.core.setTargetGroupCount(count)
|
|
858
|
+
this.core.setTargetGroupCount(count).catch((e) => {
|
|
859
|
+
this.logger.debug("Error setting target custody group count", {count}, e as Error);
|
|
860
|
+
});
|
|
859
861
|
};
|
|
860
862
|
|
|
861
863
|
private onPublishDataColumns = async (sidecars: DataColumnSidecar[]): Promise<void> => {
|
|
@@ -718,11 +718,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
718
718
|
|
|
719
719
|
if (e instanceof BlockError) {
|
|
720
720
|
switch (e.type.code) {
|
|
721
|
-
case BlockErrorCode.DATA_UNAVAILABLE:
|
|
722
|
-
//
|
|
721
|
+
case BlockErrorCode.DATA_UNAVAILABLE:
|
|
722
|
+
// Data did not arrive in time, BlockInputSync keeps pulling it and retries the import
|
|
723
723
|
logLevel = LogLevel.debug;
|
|
724
724
|
break;
|
|
725
|
-
}
|
|
726
725
|
// ALREADY_KNOWN should not happen with ignoreIfKnown=true above
|
|
727
726
|
// PARENT_BLOCK_UNKNOWN should not happen, we handled this in validateBeaconBlock() function above
|
|
728
727
|
case BlockErrorCode.ALREADY_KNOWN:
|
|
@@ -1358,6 +1357,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1358
1357
|
|
|
1359
1358
|
if (e instanceof PayloadError) {
|
|
1360
1359
|
switch (e.type.code) {
|
|
1360
|
+
case PayloadErrorCode.DATA_UNAVAILABLE:
|
|
1361
|
+
// Columns did not arrive in time, BlockInputSync keeps pulling them and retries the import
|
|
1362
|
+
logLevel = LogLevel.debug;
|
|
1363
|
+
break;
|
|
1361
1364
|
// BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
|
|
1362
1365
|
// already verified the block is in fork choice
|
|
1363
1366
|
case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
|
package/src/node/nodejs.ts
CHANGED
|
@@ -13,11 +13,17 @@ import {BeaconApiMethods} from "@lodestar/api/beacon/server";
|
|
|
13
13
|
import {BeaconConfig} from "@lodestar/config";
|
|
14
14
|
import type {LoggerNode} from "@lodestar/logger/node";
|
|
15
15
|
import {ZERO_HASH_HEX} from "@lodestar/params";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
IBeaconStateView,
|
|
18
|
+
computeStartSlotAtEpoch,
|
|
19
|
+
isStatePostBellatrix,
|
|
20
|
+
isStatePostGloas,
|
|
21
|
+
} from "@lodestar/state-transition";
|
|
17
22
|
import {Slot} from "@lodestar/types";
|
|
18
23
|
import {sleep, toRootHex} from "@lodestar/utils";
|
|
19
24
|
import {ProcessShutdownCallback} from "@lodestar/validator";
|
|
20
25
|
import {BeaconRestApiServer, getApi} from "../api/index.js";
|
|
26
|
+
import {getDataColumnSidecarsMinEpoch} from "../chain/archiveStore/utils/archiveBlocks.js";
|
|
21
27
|
import {BeaconChain, IBeaconChain, initBeaconMetrics} from "../chain/index.js";
|
|
22
28
|
import {ValidatorMonitor, createValidatorMonitor} from "../chain/validatorMonitor.js";
|
|
23
29
|
import {IBeaconDb} from "../db/index.js";
|
|
@@ -212,6 +218,17 @@ export class BeaconNode {
|
|
|
212
218
|
|
|
213
219
|
const clock = new Clock({config, genesisTime: anchorState.genesisTime, signal});
|
|
214
220
|
|
|
221
|
+
// Before any lookup into the legacy column buckets, which can be slow until compacted
|
|
222
|
+
const columnsMinEpoch =
|
|
223
|
+
opts.chain.archiveDataEpochs === Infinity
|
|
224
|
+
? null
|
|
225
|
+
: getDataColumnSidecarsMinEpoch(config, clock.currentEpoch, opts.chain.archiveDataEpochs);
|
|
226
|
+
await db.compactLegacyDataColumns(
|
|
227
|
+
columnsMinEpoch !== null && columnsMinEpoch >= config.FULU_FORK_EPOCH
|
|
228
|
+
? computeStartSlotAtEpoch(columnsMinEpoch)
|
|
229
|
+
: 0
|
|
230
|
+
);
|
|
231
|
+
|
|
215
232
|
await db.init();
|
|
216
233
|
|
|
217
234
|
// Prune hot db repos
|
package/src/node/options.ts
CHANGED
|
@@ -19,16 +19,15 @@ import {SyncOptions, defaultSyncOptions} from "../sync/options.js";
|
|
|
19
19
|
|
|
20
20
|
// Re-export so the CLI doesn't need to depend on lodestar-api
|
|
21
21
|
export {allNamespaces} from "../api/rest/index.js";
|
|
22
|
-
|
|
22
|
+
export type {EngineApiMode};
|
|
23
23
|
// Re-export to use as default values in CLI args
|
|
24
24
|
export {
|
|
25
|
-
defaultExecutionEngineHttpOpts,
|
|
26
|
-
defaultExecutionBuilderHttpOpts,
|
|
27
|
-
engineApiModes,
|
|
28
25
|
ArchiveMode,
|
|
29
26
|
DEFAULT_ARCHIVE_MODE,
|
|
27
|
+
defaultExecutionBuilderHttpOpts,
|
|
28
|
+
defaultExecutionEngineHttpOpts,
|
|
29
|
+
engineApiModes,
|
|
30
30
|
};
|
|
31
|
-
export type {EngineApiMode};
|
|
32
31
|
|
|
33
32
|
export interface IBeaconNodeOptions {
|
|
34
33
|
api: ApiOptions;
|
package/src/sync/interface.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {IBeaconDb} from "../db/index.js";
|
|
|
7
7
|
import {Metrics} from "../metrics/index.js";
|
|
8
8
|
import {INetwork} from "../network/index.js";
|
|
9
9
|
import {SyncChainDebugState} from "./range/chain.js";
|
|
10
|
+
|
|
10
11
|
export type {SyncChainDebugState};
|
|
11
12
|
|
|
12
13
|
export type SyncingStatus = routes.node.SyncingStatus;
|
|
@@ -69,7 +69,7 @@ export class JobItemQueue<Args extends any[], R> {
|
|
|
69
69
|
if (this.jobs.length === 1 && this.opts.noYieldIfOneItem) {
|
|
70
70
|
void this.runJob();
|
|
71
71
|
} else if (this.runningJobs < this.opts.maxConcurrency) {
|
|
72
|
-
callInNextEventLoop(this.runJob);
|
|
72
|
+
callInNextEventLoop(() => void this.runJob());
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
}
|
package/src/util/sszBytes.ts
CHANGED
|
@@ -854,7 +854,7 @@ export function getBlobKzgCommitmentsCountFromSignedBeaconBlockSerialized(
|
|
|
854
854
|
|
|
855
855
|
const start = bidMsgStart + commitmentsRange.start;
|
|
856
856
|
const end = bidMsgStart + commitmentsRange.end;
|
|
857
|
-
return Math.round(((end
|
|
857
|
+
return Math.round((Math.min(end, blockBytes.byteLength) - start) / commitmentSize);
|
|
858
858
|
}
|
|
859
859
|
|
|
860
860
|
const {SignedBeaconBlock, BeaconBlock, BeaconBlockBody, KZGCommitment} = ssz[forkName as ForkPostDeneb];
|
|
@@ -880,5 +880,5 @@ export function getBlobKzgCommitmentsCountFromSignedBeaconBlockSerialized(
|
|
|
880
880
|
const end = messageRange.end + bodyRange.end + kzgCommitmentsRange.end;
|
|
881
881
|
const start = messageRange.start + bodyRange.start + kzgCommitmentsRange.start;
|
|
882
882
|
|
|
883
|
-
return Math.round(((end
|
|
883
|
+
return Math.round((Math.min(end, blockBytes.byteLength) - start) / commitmentSize);
|
|
884
884
|
}
|