@lodestar/beacon-node 1.40.0-rc.1 → 1.40.0-rc.3
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/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +10 -8
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +10 -4
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +9 -1
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +5 -2
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/lightClient/index.d.ts +2 -0
- package/lib/chain/lightClient/index.d.ts.map +1 -1
- package/lib/chain/lightClient/index.js +10 -4
- package/lib/chain/lightClient/index.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -7
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +20 -9
- package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
- package/lib/util/queue/itemQueue.d.ts +10 -0
- package/lib/util/queue/itemQueue.d.ts.map +1 -1
- package/lib/util/queue/itemQueue.js +57 -0
- package/lib/util/queue/itemQueue.js.map +1 -1
- package/package.json +15 -15
- package/src/api/rest/base.ts +11 -9
- package/src/chain/archiveStore/archiveStore.ts +10 -4
- package/src/chain/blocks/importBlock.ts +9 -1
- package/src/chain/chain.ts +5 -2
- package/src/chain/lightClient/index.ts +11 -4
- package/src/chain/seenCache/seenGossipBlockInput.ts +28 -9
- package/src/util/queue/itemQueue.ts +62 -0
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
2
|
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ForkName,
|
|
5
|
+
ForkPostFulu,
|
|
6
|
+
ForkPreGloas,
|
|
7
|
+
SLOTS_PER_EPOCH,
|
|
8
|
+
isForkPostDeneb,
|
|
9
|
+
isForkPostFulu,
|
|
10
|
+
isForkPostGloas,
|
|
11
|
+
} from "@lodestar/params";
|
|
4
12
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
5
13
|
import {BLSSignature, RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
|
|
6
14
|
import {LodestarError, Logger, byteArrayEquals, pruneSetToMax} from "@lodestar/utils";
|
|
7
15
|
import {Metrics} from "../../metrics/metrics.js";
|
|
16
|
+
import {MAX_LOOK_AHEAD_EPOCHS} from "../../sync/constants.js";
|
|
8
17
|
import {IClock} from "../../util/clock.js";
|
|
9
18
|
import {CustodyConfig} from "../../util/dataColumns.js";
|
|
10
19
|
import {
|
|
@@ -26,7 +35,17 @@ import {
|
|
|
26
35
|
} from "../blocks/blockInput/index.js";
|
|
27
36
|
import {ChainEvent, ChainEventEmitter} from "../emitter.js";
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
// Target size for the block input cache, enforced by pruneToMaxSize() which runs after prune()
|
|
39
|
+
// and onFinalized() — NOT on insertion. The cache can temporarily exceed this during range sync
|
|
40
|
+
// (e.g. 32 blocks inserted per batch) but is trimmed back after blocks are processed.
|
|
41
|
+
//
|
|
42
|
+
// Must be large enough to hold blocks from all concurrently downloaded range sync batches.
|
|
43
|
+
// Range sync downloads up to MAX_LOOK_AHEAD_EPOCHS batches ahead of the processing head,
|
|
44
|
+
// so up to (MAX_LOOK_AHEAD_EPOCHS + 1) batches (current + look-ahead) of SLOTS_PER_EPOCH
|
|
45
|
+
// blocks can be in the cache simultaneously. If this value is too small, pruneToMaxSize()
|
|
46
|
+
// will evict blocks from the batch being processed before they are persisted to the database,
|
|
47
|
+
// causing errors when async handlers like onForkChoiceFinalized run.
|
|
48
|
+
const MAX_BLOCK_INPUT_CACHE_SIZE = (MAX_LOOK_AHEAD_EPOCHS + 1) * SLOTS_PER_EPOCH;
|
|
30
49
|
|
|
31
50
|
export type SeenBlockInputCacheModules = {
|
|
32
51
|
config: ChainForkConfig;
|
|
@@ -64,14 +83,14 @@ export type GetByBlobOptions = {
|
|
|
64
83
|
* - onFinalized event handler will help to prune any non-canonical forks once the chain finalizes. Any block-slots that
|
|
65
84
|
* are before the finalized checkpoint will be pruned.
|
|
66
85
|
* - Range-sync periods. The range process uses this cache to store and sync blocks with DA data as the chain is pulled
|
|
67
|
-
* from peers. We pull batches, by epoch, so 32 slots are pulled at a time and several batches are
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
86
|
+
* from peers. We pull batches, by epoch, so 32 slots are pulled at a time and several batches are downloaded
|
|
87
|
+
* concurrently (up to MAX_LOOK_AHEAD_EPOCHS ahead). All downloaded blocks are added to this shared cache, so it
|
|
88
|
+
* must be large enough to hold blocks from all concurrent batches. If pruneToMaxSize() evicts blocks from the batch
|
|
89
|
+
* currently being processed, those blocks may not yet be persisted to the database, causing getBlockByRoot() to fail
|
|
90
|
+
* when async event handlers (e.g. onForkChoiceFinalized) try to look them up.
|
|
71
91
|
* - Non-Finality times. This is a bit more tricky. There can be long periods of non-finality and storing everything
|
|
72
|
-
* will cause OOM. The
|
|
73
|
-
*
|
|
74
|
-
* practice this value may need to be massaged in the future if we find issues when debugging non-finality
|
|
92
|
+
* will cause OOM. The pruneToMaxSize will help ensure the number of stored blocks (with DA) is trimmed back to
|
|
93
|
+
* MAX_BLOCK_INPUT_CACHE_SIZE after each prune() or onFinalized() call
|
|
75
94
|
*/
|
|
76
95
|
|
|
77
96
|
export class SeenBlockInput {
|
|
@@ -24,6 +24,8 @@ export class JobItemQueue<Args extends any[], R> {
|
|
|
24
24
|
private readonly metrics?: QueueMetrics;
|
|
25
25
|
private runningJobs = 0;
|
|
26
26
|
private lastYield = 0;
|
|
27
|
+
/** Resolvers waiting for space in the queue */
|
|
28
|
+
private spaceWaiters: (() => void)[] = [];
|
|
27
29
|
|
|
28
30
|
constructor(
|
|
29
31
|
private readonly itemProcessor: (...args: Args) => Promise<R>,
|
|
@@ -72,12 +74,57 @@ export class JobItemQueue<Args extends any[], R> {
|
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Returns a promise that resolves when there is space in the queue.
|
|
79
|
+
* If the queue already has space, resolves immediately (noop).
|
|
80
|
+
* Use this to apply backpressure when the caller should wait rather than
|
|
81
|
+
* have push() throw QUEUE_MAX_LENGTH.
|
|
82
|
+
*/
|
|
83
|
+
async waitForSpace(): Promise<void> {
|
|
84
|
+
if (this.opts.signal.aborted) {
|
|
85
|
+
throw new QueueError({code: QueueErrorCode.QUEUE_ABORTED});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.jobs.length < this.opts.maxLength) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new Promise<void>((resolve, reject) => {
|
|
93
|
+
let settled = false;
|
|
94
|
+
|
|
95
|
+
const onAbort = (): void => {
|
|
96
|
+
if (settled) return;
|
|
97
|
+
settled = true;
|
|
98
|
+
const index = this.spaceWaiters.indexOf(wrappedResolve);
|
|
99
|
+
if (index >= 0) {
|
|
100
|
+
this.spaceWaiters.splice(index, 1);
|
|
101
|
+
}
|
|
102
|
+
reject(new QueueError({code: QueueErrorCode.QUEUE_ABORTED}));
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const wrappedResolve = (): void => {
|
|
106
|
+
if (settled) return;
|
|
107
|
+
settled = true;
|
|
108
|
+
this.opts.signal.removeEventListener("abort", onAbort);
|
|
109
|
+
resolve();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
this.spaceWaiters.push(wrappedResolve);
|
|
113
|
+
this.opts.signal.addEventListener("abort", onAbort, {once: true});
|
|
114
|
+
|
|
115
|
+
// Re-check after attaching listener to close the race window where
|
|
116
|
+
// signal.abort() fires between the initial check and addEventListener
|
|
117
|
+
if (this.opts.signal.aborted) onAbort();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
75
121
|
getItems(): {args: Args; addedTimeMs: number}[] {
|
|
76
122
|
return this.jobs.map((job) => ({args: job.args, addedTimeMs: job.addedTimeMs}));
|
|
77
123
|
}
|
|
78
124
|
|
|
79
125
|
dropAllJobs = (): void => {
|
|
80
126
|
this.jobs.clear();
|
|
127
|
+
this.notifySpaceWaiters();
|
|
81
128
|
};
|
|
82
129
|
|
|
83
130
|
private runJob = async (): Promise<void> => {
|
|
@@ -115,10 +162,25 @@ export class JobItemQueue<Args extends any[], R> {
|
|
|
115
162
|
|
|
116
163
|
this.runningJobs = Math.max(0, this.runningJobs - 1);
|
|
117
164
|
|
|
165
|
+
// Notify any waiters that space is available
|
|
166
|
+
this.notifySpaceWaiters();
|
|
167
|
+
|
|
118
168
|
// Potentially run a new job
|
|
119
169
|
void this.runJob();
|
|
120
170
|
};
|
|
121
171
|
|
|
172
|
+
private notifySpaceWaiters(): void {
|
|
173
|
+
// Compute available slots once to avoid thundering herd: resolved waiters
|
|
174
|
+
// won't push() until the next microtask, so jobs.length doesn't change
|
|
175
|
+
// inside this loop. Without the cap we'd wake ALL waiters on a single slot.
|
|
176
|
+
let available = this.opts.maxLength - this.jobs.length;
|
|
177
|
+
while (available > 0 && this.spaceWaiters.length > 0) {
|
|
178
|
+
const resolve = this.spaceWaiters.shift();
|
|
179
|
+
if (resolve) resolve();
|
|
180
|
+
available--;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
122
184
|
private abortAllJobs = (): void => {
|
|
123
185
|
while (this.jobs.length > 0) {
|
|
124
186
|
const job = this.jobs.pop();
|