@lodestar/beacon-node 1.49.0-dev.ee7c2aa8ee → 1.49.0-dev.fa4ae94ccc

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.
Files changed (101) hide show
  1. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/pool/index.js +4 -2
  3. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  4. package/lib/chain/archiveStore/archiveStore.d.ts +1 -2
  5. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  6. package/lib/chain/archiveStore/archiveStore.js +0 -5
  7. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  8. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/historicalState/metrics.js +0 -6
  10. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  11. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  12. package/lib/chain/produceBlock/computeNewStateRoot.js +4 -2
  13. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  14. package/lib/db/beacon.d.ts +1 -2
  15. package/lib/db/beacon.d.ts.map +1 -1
  16. package/lib/db/beacon.js +1 -3
  17. package/lib/db/beacon.js.map +1 -1
  18. package/lib/db/buckets.d.ts +0 -1
  19. package/lib/db/buckets.d.ts.map +1 -1
  20. package/lib/db/buckets.js +1 -1
  21. package/lib/db/buckets.js.map +1 -1
  22. package/lib/db/interface.d.ts +1 -2
  23. package/lib/db/interface.d.ts.map +1 -1
  24. package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
  25. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  26. package/lib/db/repositories/index.d.ts +0 -1
  27. package/lib/db/repositories/index.d.ts.map +1 -1
  28. package/lib/db/repositories/index.js +0 -1
  29. package/lib/db/repositories/index.js.map +1 -1
  30. package/lib/metrics/metrics/lodestar.d.ts +9 -11
  31. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  32. package/lib/metrics/metrics/lodestar.js +18 -23
  33. package/lib/metrics/metrics/lodestar.js.map +1 -1
  34. package/lib/node/nodejs.d.ts +2 -7
  35. package/lib/node/nodejs.d.ts.map +1 -1
  36. package/lib/node/nodejs.js +2 -22
  37. package/lib/node/nodejs.js.map +1 -1
  38. package/lib/sync/interface.d.ts +1 -2
  39. package/lib/sync/interface.d.ts.map +1 -1
  40. package/lib/sync/options.d.ts +0 -6
  41. package/lib/sync/options.d.ts.map +1 -1
  42. package/lib/sync/options.js +0 -2
  43. package/lib/sync/options.js.map +1 -1
  44. package/lib/sync/types.d.ts +18 -1
  45. package/lib/sync/types.d.ts.map +1 -1
  46. package/lib/sync/types.js +19 -1
  47. package/lib/sync/types.js.map +1 -1
  48. package/lib/sync/unknownBlock.d.ts +19 -0
  49. package/lib/sync/unknownBlock.d.ts.map +1 -1
  50. package/lib/sync/unknownBlock.js +143 -2
  51. package/lib/sync/unknownBlock.js.map +1 -1
  52. package/lib/util/shuffle.d.ts +0 -4
  53. package/lib/util/shuffle.d.ts.map +1 -1
  54. package/lib/util/shuffle.js +0 -8
  55. package/lib/util/shuffle.js.map +1 -1
  56. package/package.json +14 -14
  57. package/src/api/impl/beacon/pool/index.ts +6 -2
  58. package/src/chain/archiveStore/archiveStore.ts +0 -6
  59. package/src/chain/archiveStore/historicalState/metrics.ts +0 -7
  60. package/src/chain/produceBlock/computeNewStateRoot.ts +9 -2
  61. package/src/db/beacon.ts +0 -5
  62. package/src/db/buckets.ts +1 -1
  63. package/src/db/interface.ts +0 -3
  64. package/src/db/repositories/index.ts +0 -1
  65. package/src/metrics/metrics/lodestar.ts +27 -26
  66. package/src/node/nodejs.ts +0 -27
  67. package/src/sync/interface.ts +1 -2
  68. package/src/sync/options.ts +0 -8
  69. package/src/sync/types.ts +19 -1
  70. package/src/sync/unknownBlock.ts +151 -1
  71. package/src/util/shuffle.ts +0 -9
  72. package/lib/chain/archiveStore/utils/updateBackfillRange.d.ts +0 -20
  73. package/lib/chain/archiveStore/utils/updateBackfillRange.d.ts.map +0 -1
  74. package/lib/chain/archiveStore/utils/updateBackfillRange.js +0 -40
  75. package/lib/chain/archiveStore/utils/updateBackfillRange.js.map +0 -1
  76. package/lib/db/repositories/backfilledRanges.d.ts +0 -18
  77. package/lib/db/repositories/backfilledRanges.d.ts.map +0 -1
  78. package/lib/db/repositories/backfilledRanges.js +0 -26
  79. package/lib/db/repositories/backfilledRanges.js.map +0 -1
  80. package/lib/sync/backfill/backfill.d.ts +0 -169
  81. package/lib/sync/backfill/backfill.d.ts.map +0 -1
  82. package/lib/sync/backfill/backfill.js +0 -686
  83. package/lib/sync/backfill/backfill.js.map +0 -1
  84. package/lib/sync/backfill/errors.d.ts +0 -30
  85. package/lib/sync/backfill/errors.d.ts.map +0 -1
  86. package/lib/sync/backfill/errors.js +0 -15
  87. package/lib/sync/backfill/errors.js.map +0 -1
  88. package/lib/sync/backfill/index.d.ts +0 -2
  89. package/lib/sync/backfill/index.d.ts.map +0 -1
  90. package/lib/sync/backfill/index.js +0 -2
  91. package/lib/sync/backfill/index.js.map +0 -1
  92. package/lib/sync/backfill/verify.d.ts +0 -18
  93. package/lib/sync/backfill/verify.d.ts.map +0 -1
  94. package/lib/sync/backfill/verify.js +0 -36
  95. package/lib/sync/backfill/verify.js.map +0 -1
  96. package/src/chain/archiveStore/utils/updateBackfillRange.ts +0 -50
  97. package/src/db/repositories/backfilledRanges.ts +0 -29
  98. package/src/sync/backfill/backfill.ts +0 -916
  99. package/src/sync/backfill/errors.ts +0 -23
  100. package/src/sync/backfill/index.ts +0 -1
  101. package/src/sync/backfill/verify.ts +0 -58
@@ -1,916 +0,0 @@
1
- import {EventEmitter} from "node:events";
2
- import {StrictEventEmitter} from "strict-event-emitter-types";
3
- import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
4
- import {SLOTS_PER_EPOCH} from "@lodestar/params";
5
- import {IBeaconStateView, blockToHeader} from "@lodestar/state-transition";
6
- import {Root, SignedBeaconBlock, Slot, phase0, ssz} from "@lodestar/types";
7
- import {ErrorAborted, Logger, byteArrayEquals, sleep, toRootHex} from "@lodestar/utils";
8
- import {IBeaconChain} from "../../chain/index.js";
9
- import {GENESIS_SLOT, ZERO_HASH} from "../../constants/index.js";
10
- import {IBeaconDb} from "../../db/index.js";
11
- import {Metrics} from "../../metrics/metrics.js";
12
- import {INetwork, NetworkEvent, NetworkEventData, PeerAction} from "../../network/index.js";
13
- import {ItTrigger} from "../../util/itTrigger.js";
14
- import {PeerIdStr} from "../../util/peerId.js";
15
- import {shuffleOne} from "../../util/shuffle.js";
16
- import {BackfillSyncError, BackfillSyncErrorCode} from "./errors.js";
17
- import {BackfillBlock, BackfillBlockHeader, verifyBlockProposerSignature, verifyBlockSequence} from "./verify.js";
18
-
19
- /**
20
- * Timeout in ms to take a break from reading a backfillBatchSize from db, as just yielding
21
- * to sync loop gives hardly any.
22
- */
23
- const DB_READ_BREATHER_TIMEOUT = 1000;
24
-
25
- export type BackfillSyncModules = {
26
- chain: IBeaconChain;
27
- db: IBeaconDb;
28
- network: INetwork;
29
- config: BeaconConfig;
30
- logger: Logger;
31
- metrics: Metrics | null;
32
- anchorState: IBeaconStateView;
33
- wsCheckpoint?: phase0.Checkpoint;
34
- signal: AbortSignal;
35
- };
36
-
37
- type BackfillModules = BackfillSyncModules & {
38
- syncAnchor: BackFillSyncAnchor;
39
- backfillStartFromSlot: Slot;
40
- prevFinalizedCheckpointBlock: BackfillBlockHeader;
41
- wsCheckpointHeader: BackfillBlockHeader | null;
42
- backfillRangeWrittenSlot: Slot | null;
43
- };
44
-
45
- export type BackfillSyncOpts = {
46
- backfillBatchSize: number;
47
- };
48
-
49
- export enum BackfillSyncEvent {
50
- completed = "BackfillSync-completed",
51
- }
52
-
53
- export enum BackfillSyncMethod {
54
- database = "database",
55
- backfilled_ranges = "backfilled_ranges",
56
- rangesync = "rangesync",
57
- blockbyroot = "blockbyroot",
58
- }
59
-
60
- export enum BackfillSyncStatus {
61
- pending = "pending",
62
- syncing = "syncing",
63
- completed = "completed",
64
- aborted = "aborted",
65
- }
66
-
67
- /** Map a SyncState to an integer for rendering in Grafana */
68
- const syncStatus: {[K in BackfillSyncStatus]: number} = {
69
- [BackfillSyncStatus.aborted]: 0,
70
- [BackfillSyncStatus.pending]: 1,
71
- [BackfillSyncStatus.syncing]: 2,
72
- [BackfillSyncStatus.completed]: 3,
73
- };
74
-
75
- type BackfillSyncEvents = {
76
- [BackfillSyncEvent.completed]: (
77
- /** Oldest slot synced */
78
- oldestSlotSynced: Slot
79
- ) => void;
80
- };
81
-
82
- type BackfillSyncEmitter = StrictEventEmitter<EventEmitter, BackfillSyncEvents>;
83
-
84
- /**
85
- * At any given point, we should have
86
- * 1. anchorBlock (with its root anchorBlockRoot at anchorSlot) for next round of sync
87
- * which is the same as the lastBackSyncedBlock
88
- * 2. We know the anchorBlockRoot but don't have its anchorBlock and anchorSlot yet, and its
89
- * parent of lastBackSyncedBlock we synced in a previous successfull round
90
- * 3. We just started with only anchorBlockRoot, but we know (and will validate) its anchorSlot
91
- */
92
- type BackFillSyncAnchor =
93
- | {
94
- anchorBlock: SignedBeaconBlock;
95
- anchorBlockRoot: Root;
96
- anchorSlot: Slot;
97
- lastBackSyncedBlock: BackfillBlock;
98
- }
99
- | {anchorBlock: null; anchorBlockRoot: Root; anchorSlot: null; lastBackSyncedBlock: BackfillBlock}
100
- | {anchorBlock: null; anchorBlockRoot: Root; anchorSlot: Slot; lastBackSyncedBlock: null};
101
-
102
- export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter}) {
103
- /** Lowest slot that we have backfilled to */
104
- syncAnchor: BackFillSyncAnchor;
105
-
106
- private readonly chain: IBeaconChain;
107
- private readonly network: INetwork;
108
- private readonly db: IBeaconDb;
109
- private readonly config: BeaconConfig;
110
- private readonly logger: Logger;
111
- private readonly metrics: Metrics | null;
112
-
113
- /**
114
- * Process in blocks of at max batchSize
115
- */
116
- private opts: BackfillSyncOpts;
117
- /**
118
- * If wsCheckpoint provided was in past then the (db) state from which beacon node started,
119
- * needs to be validated as per spec.
120
- *
121
- * 1. This could lie in between of the previous backfilled range, in which case it would be
122
- * sufficient to check if its DB, once the linkage to that range has been verified.
123
- * 2. Else if it lies outside the backfilled range, the linkage to this checkpoint in
124
- * backfill needs to be verified.
125
- */
126
- private wsCheckpointHeader: BackfillBlockHeader | null;
127
- private wsValidated = false;
128
-
129
- /**
130
- * From https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/phase0/weak-subjectivity.md
131
- *
132
- *
133
- * If
134
- * 1. The wsCheckpoint provided was ahead of the db's finalized checkpoint or
135
- * 2. There were gaps in the backfill - keys to backfillRanges are always (by construction)
136
- * a) Finalized Checkpoint or b) previous wsCheckpoint
137
- *
138
- * the linkage to the previous finalized/wss checkpoint(s) needs to be verfied. If there is
139
- * no such checkpoint remaining, the linkage to genesis needs to be validated
140
- *
141
- * Initialize with the blockArchive's last block, and on verification update to the next
142
- * preceding backfillRange key's checkpoint.
143
- */
144
- private prevFinalizedCheckpointBlock: BackfillBlockHeader;
145
- /** Starting point that this specific backfill sync "session" started from */
146
- private backfillStartFromSlot: Slot;
147
- private backfillRangeWrittenSlot: Slot | null;
148
-
149
- private processor = new ItTrigger();
150
- private peers = new Set<PeerIdStr>();
151
- private status: BackfillSyncStatus = BackfillSyncStatus.pending;
152
- private signal: AbortSignal;
153
-
154
- constructor(opts: BackfillSyncOpts, modules: BackfillModules) {
155
- super();
156
-
157
- this.syncAnchor = modules.syncAnchor;
158
- this.backfillStartFromSlot = modules.backfillStartFromSlot;
159
- this.backfillRangeWrittenSlot = modules.backfillRangeWrittenSlot;
160
- this.prevFinalizedCheckpointBlock = modules.prevFinalizedCheckpointBlock;
161
- this.wsCheckpointHeader = modules.wsCheckpointHeader;
162
-
163
- this.chain = modules.chain;
164
- this.network = modules.network;
165
- this.db = modules.db;
166
- this.config = modules.config;
167
- this.logger = modules.logger;
168
- this.metrics = modules.metrics;
169
-
170
- this.opts = opts;
171
- this.network.events.on(NetworkEvent.peerConnected, this.addPeer);
172
- this.network.events.on(NetworkEvent.peerDisconnected, this.removePeer);
173
- this.signal = modules.signal;
174
-
175
- this.sync()
176
- .then((oldestSlotSynced) => {
177
- if (this.status !== BackfillSyncStatus.completed) {
178
- throw new ErrorAborted(`Invalid BackfillSyncStatus at the completion of sync loop status=${this.status}`);
179
- }
180
- this.emit(BackfillSyncEvent.completed, oldestSlotSynced);
181
- this.logger.info("BackfillSync completed", {oldestSlotSynced});
182
- // Sync completed, unsubscribe listeners and don't run the processor again.
183
- // Backfill is never necessary again until the node shuts down
184
- this.close();
185
- })
186
- .catch((e) => {
187
- this.logger.error("BackfillSync processor error", e);
188
- this.status = BackfillSyncStatus.aborted;
189
- this.close();
190
- });
191
-
192
- const metrics = this.metrics;
193
- if (metrics) {
194
- metrics.backfillSync.status.addCollect(() => metrics.backfillSync.status.set(syncStatus[this.status]));
195
- metrics.backfillSync.backfilledTillSlot.addCollect(() =>
196
- metrics.backfillSync.backfilledTillSlot.set(
197
- this.syncAnchor.lastBackSyncedBlock?.slot ?? this.backfillStartFromSlot
198
- )
199
- );
200
- metrics.backfillSync.prevFinOrWsSlot.addCollect(() =>
201
- metrics.backfillSync.prevFinOrWsSlot.set(Math.max(this.prevFinalizedCheckpointBlock.slot, GENESIS_SLOT))
202
- );
203
- }
204
- }
205
-
206
- /**
207
- * Use the root of the anchorState of the beacon node as the starting point of the
208
- * backfill sync with its expected slot to be anchorState.slot, which will be
209
- * validated once the block is resolved in the backfill sync.
210
- *
211
- * NOTE: init here is quite light involving couple of
212
- *
213
- * 1. db keys lookup in stateArchive/backfilledRanges
214
- * 2. computing root(s) for anchorBlockRoot and prevFinalizedCheckpointBlock
215
- *
216
- * The way we initialize beacon node, wsCheckpoint's slot is always <= anchorSlot
217
- * If:
218
- * the root belonging to wsCheckpoint is in the DB, we need to verify linkage to it
219
- * i.e. it becomes our first prevFinalizedCheckpointBlock
220
- * Else
221
- * we initialize prevFinalizedCheckpointBlock from the last stored db finalized state
222
- * for verification and when we go below its epoch we just check if a correct block
223
- * corresponding to wsCheckpoint root was stored.
224
- *
225
- * and then we continue going back and verifying the next unconnected previous finalized
226
- * or wsCheckpoints identifiable as the keys of backfill sync.
227
- */
228
- static async init<T extends BackfillSync = BackfillSync>(
229
- opts: BackfillSyncOpts,
230
- modules: BackfillSyncModules
231
- ): Promise<T> {
232
- const {config, anchorState, db, wsCheckpoint, logger} = modules;
233
-
234
- const {checkpoint: anchorCp} = anchorState.computeAnchorCheckpoint();
235
- const anchorSlot = anchorState.latestBlockHeader.slot;
236
- const syncAnchor = {
237
- anchorBlock: null,
238
- anchorBlockRoot: anchorCp.root,
239
- anchorSlot,
240
- lastBackSyncedBlock: null,
241
- };
242
-
243
- // Load the previous written to slot for the key backfillStartFromSlot
244
- // in backfilledRanges
245
- const backfillStartFromSlot = anchorSlot;
246
- const backfillRangeWrittenSlot = await db.backfilledRanges.get(backfillStartFromSlot);
247
- const previousBackfilledRanges = await db.backfilledRanges.entries({
248
- lte: backfillStartFromSlot,
249
- });
250
- modules.logger.info("Initializing from Checkpoint", {
251
- root: toRootHex(anchorCp.root),
252
- epoch: anchorCp.epoch,
253
- backfillStartFromSlot,
254
- previousBackfilledRanges: JSON.stringify(previousBackfilledRanges),
255
- });
256
-
257
- // wsCheckpointHeader is where the checkpoint can actually be validated
258
- const wsCheckpointHeader: BackfillBlockHeader | null = wsCheckpoint
259
- ? {root: wsCheckpoint.root, slot: wsCheckpoint.epoch * SLOTS_PER_EPOCH}
260
- : null;
261
- // Load a previous finalized or wsCheckpoint slot from DB below anchorSlot
262
- const prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint(config, db, anchorSlot, logger);
263
-
264
- return new BackfillSync(opts, {
265
- syncAnchor,
266
- backfillStartFromSlot,
267
- backfillRangeWrittenSlot,
268
- wsCheckpointHeader,
269
- prevFinalizedCheckpointBlock,
270
- ...modules,
271
- }) as T;
272
- }
273
-
274
- /** Throw / return all AsyncGenerators */
275
- close(): void {
276
- this.network.events.off(NetworkEvent.peerConnected, this.addPeer);
277
- this.network.events.off(NetworkEvent.peerDisconnected, this.removePeer);
278
- this.processor.end(new ErrorAborted("BackfillSync"));
279
- }
280
-
281
- /**
282
- * @returns Returns oldestSlotSynced
283
- */
284
- private async sync(): Promise<Slot> {
285
- this.processor.trigger();
286
-
287
- for await (const _ of this.processor) {
288
- if (this.status === BackfillSyncStatus.aborted) {
289
- /** Break out of sync loop and throw error */
290
- break;
291
- }
292
- this.status = BackfillSyncStatus.syncing;
293
-
294
- // 1. We should always have either anchorBlock or anchorBlockRoot, they are the
295
- // anchor points for this round of the sync
296
- // 2. Check and validate if we have reached prevFinalizedCheckpointBlock
297
- // On success Update prevFinalizedCheckpointBlock to check the *next* previous
298
- // 3. Validate Checkpoint as part of DB block tree if we have backfilled
299
- // before the checkpoint
300
- // 4. Exit the sync if backfilled till genesis
301
- //
302
- // 5. Check if we can jump back from available backfill sequence, if found yield and
303
- // recontinue from top making checks
304
- // 7. Check and read batchSize from DB, if found yield and recontinue from top
305
- // 8. If not in DB, and if peer available
306
- // a) Either fetch blockByRoot if only anchorBlockRoot is set, which could be because
307
- // i) its the unavailable root of the very first block to start off sync
308
- // ii) its parent of lastBackSyncedBlock and there was an issue in establishing
309
- // linear sequence in syncRange as there could be one or more
310
- // skipped/orphaned slots
311
- // between the parent we want to fetch and lastBackSyncedBlock
312
- // b) read previous batchSize blocks from network assuming most likely those blocks
313
- // form a linear anchored chain with anchorBlock. If not, try fetching the
314
- // parent of
315
- // the anchorBlock via strategy a) as it could be multiple skipped/orphaned slots
316
- // behind
317
- if (this.syncAnchor.lastBackSyncedBlock != null) {
318
- // If after a previous sync round:
319
- // lastBackSyncedBlock.slot < prevFinalizedCheckpointBlock.slot
320
- // then it means the prevFinalizedCheckpoint block has been missed because in each
321
- // round we backfill new blocks till (if the batchSize allows):
322
- // lastBackSyncedBlock.slot <= prevFinalizedCheckpointBlock.slot
323
- if (this.syncAnchor.lastBackSyncedBlock.slot < this.prevFinalizedCheckpointBlock.slot) {
324
- this.logger.error(
325
- `Backfilled till ${
326
- this.syncAnchor.lastBackSyncedBlock.slot
327
- } but not found previous saved finalized or wsCheckpoint with root=${toRootHex(
328
- this.prevFinalizedCheckpointBlock.root
329
- )}, slot=${this.prevFinalizedCheckpointBlock.slot}`
330
- );
331
- // Break sync loop and throw error
332
- break;
333
- }
334
-
335
- if (this.syncAnchor.lastBackSyncedBlock.slot === this.prevFinalizedCheckpointBlock.slot) {
336
- // Okay! we backfilled successfully till prevFinalizedCheckpointBlock
337
- if (!byteArrayEquals(this.syncAnchor.lastBackSyncedBlock.root, this.prevFinalizedCheckpointBlock.root)) {
338
- this.logger.error(
339
- `Invalid root synced at a previous finalized or wsCheckpoint, slot=${
340
- this.prevFinalizedCheckpointBlock.slot
341
- }: expected=${toRootHex(this.prevFinalizedCheckpointBlock.root)}, actual=${toRootHex(
342
- this.syncAnchor.lastBackSyncedBlock.root
343
- )}`
344
- );
345
- // Break sync loop and throw error
346
- break;
347
- }
348
- this.logger.verbose("Validated current prevFinalizedCheckpointBlock", {
349
- root: toRootHex(this.prevFinalizedCheckpointBlock.root),
350
- slot: this.prevFinalizedCheckpointBlock.slot,
351
- });
352
-
353
- // 1. If this is not a genesis block save this block in DB as this wasn't saved
354
- // earlier pending validation. Genesis block will be saved with extra validation
355
- // before returning from the sync.
356
- //
357
- // 2. Load another previous saved finalized or wsCheckpoint which has not
358
- // been validated yet. These are the keys of backfill ranges as each
359
- // range denotes
360
- // a validated connected segment having the slots of previous wsCheckpoint
361
- // or finalized as keys
362
- if (this.syncAnchor.lastBackSyncedBlock.slot !== GENESIS_SLOT) {
363
- await this.db.blockArchive.put(
364
- this.syncAnchor.lastBackSyncedBlock.slot,
365
- this.syncAnchor.lastBackSyncedBlock.block
366
- );
367
- }
368
- this.prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint(
369
- this.config,
370
- this.db,
371
- this.syncAnchor.lastBackSyncedBlock.slot,
372
- this.logger
373
- );
374
- }
375
-
376
- if (this.syncAnchor.lastBackSyncedBlock.slot === GENESIS_SLOT) {
377
- if (!byteArrayEquals(this.syncAnchor.lastBackSyncedBlock.block.message.parentRoot, ZERO_HASH)) {
378
- Error(
379
- `Invalid Gensis Block with non zero parentRoot=${toRootHex(
380
- this.syncAnchor.lastBackSyncedBlock.block.message.parentRoot
381
- )}`
382
- );
383
- }
384
- await this.db.blockArchive.put(GENESIS_SLOT, this.syncAnchor.lastBackSyncedBlock.block);
385
- }
386
-
387
- if (this.wsCheckpointHeader && !this.wsValidated) {
388
- await this.checkIfCheckpointSyncedAndValidate();
389
- }
390
-
391
- if (
392
- this.backfillRangeWrittenSlot === null ||
393
- this.syncAnchor.lastBackSyncedBlock.slot < this.backfillRangeWrittenSlot
394
- ) {
395
- this.backfillRangeWrittenSlot = this.syncAnchor.lastBackSyncedBlock.slot;
396
- await this.db.backfilledRanges.put(this.backfillStartFromSlot, this.backfillRangeWrittenSlot);
397
- this.logger.debug(
398
- `Updated the backfill range from=${this.backfillStartFromSlot} till=${this.backfillRangeWrittenSlot}`
399
- );
400
- }
401
-
402
- if (this.syncAnchor.lastBackSyncedBlock.slot === GENESIS_SLOT) {
403
- this.logger.verbose("Successfully synced to genesis.");
404
- this.status = BackfillSyncStatus.completed;
405
- return GENESIS_SLOT;
406
- }
407
-
408
- const foundValidSeq = await this.checkUpdateFromBackfillSequences();
409
- if (foundValidSeq) {
410
- // Go back to top and do checks till
411
- this.processor.trigger();
412
- continue;
413
- }
414
- }
415
-
416
- try {
417
- const foundBlocks = await this.fastBackfillDb();
418
- if (foundBlocks) {
419
- this.processor.trigger();
420
- continue;
421
- }
422
- } catch (e) {
423
- this.logger.error("Error while reading from DB", {}, e as Error);
424
- // Break sync loop and throw error
425
- break;
426
- }
427
-
428
- // Try the network if nothing found in DB
429
- const peer = shuffleOne(Array.from(this.peers.values()));
430
- if (!peer) {
431
- this.status = BackfillSyncStatus.pending;
432
- this.logger.debug("No peers yet");
433
- continue;
434
- }
435
-
436
- try {
437
- if (!this.syncAnchor.anchorBlock) {
438
- await this.syncBlockByRoot(peer, this.syncAnchor.anchorBlockRoot);
439
-
440
- // Go back and make the checks in case this block could be at or
441
- // behind prevFinalizedCheckpointBlock
442
- } else {
443
- await this.syncRange(peer);
444
-
445
- // Go back and make the checks in case the lastbackSyncedBlock could be at or
446
- // behind prevFinalizedCheckpointBlock
447
- }
448
- } catch (e) {
449
- this.metrics?.backfillSync.errors.inc();
450
- this.logger.error("Sync error", {}, e as Error);
451
-
452
- if (e instanceof BackfillSyncError) {
453
- switch (e.type.code) {
454
- case BackfillSyncErrorCode.INTERNAL_ERROR:
455
- // Break it out of the loop and throw error
456
- this.status = BackfillSyncStatus.aborted;
457
- break;
458
- case BackfillSyncErrorCode.NOT_ANCHORED:
459
- // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
460
- case BackfillSyncErrorCode.NOT_LINEAR:
461
- // Lets try to jump directly to the parent of this anchorBlock as previous
462
- // (segment) of blocks could be orphaned/missed
463
- if (this.syncAnchor.anchorBlock) {
464
- this.syncAnchor = {
465
- anchorBlock: null,
466
- anchorBlockRoot: this.syncAnchor.anchorBlock.message.parentRoot,
467
- anchorSlot: null,
468
- lastBackSyncedBlock: this.syncAnchor.lastBackSyncedBlock,
469
- };
470
- }
471
-
472
- // falls through
473
- case BackfillSyncErrorCode.INVALID_SIGNATURE:
474
- this.network.reportPeer(peer, PeerAction.LowToleranceError, "BadSyncBlocks");
475
- }
476
- }
477
- } finally {
478
- if (this.status !== BackfillSyncStatus.aborted) this.processor.trigger();
479
- }
480
- }
481
-
482
- throw new ErrorAborted("BackfillSync");
483
- }
484
-
485
- private addPeer = (data: NetworkEventData[NetworkEvent.peerConnected]): void => {
486
- const requiredSlot = this.syncAnchor.lastBackSyncedBlock?.slot ?? this.backfillStartFromSlot;
487
- this.logger.debug("Add peer", {peerhead: data.status.headSlot, requiredSlot});
488
- if (data.status.headSlot >= requiredSlot) {
489
- this.peers.add(data.peer);
490
- this.processor.trigger();
491
- }
492
- };
493
-
494
- private removePeer = (data: NetworkEventData[NetworkEvent.peerDisconnected]): void => {
495
- this.peers.delete(data.peer);
496
- };
497
-
498
- /**
499
- * Ensure that any weak subjectivity checkpoint provided in past with respect
500
- * the initialization point is the same block tree as the DB once backfill
501
- */
502
- private async checkIfCheckpointSyncedAndValidate(): Promise<void> {
503
- if (this.syncAnchor.lastBackSyncedBlock == null) {
504
- throw Error("Invalid lastBackSyncedBlock for checkpoint validation");
505
- }
506
- if (this.wsCheckpointHeader == null) {
507
- throw Error("Invalid null checkpoint for validation");
508
- }
509
- if (this.wsValidated) return;
510
-
511
- if (this.wsCheckpointHeader.slot >= this.syncAnchor.lastBackSyncedBlock.slot) {
512
- // Checkpoint root should be in db now , in case there are string of orphaned/missed
513
- // slots before/leading up to checkpoint, the block just backsynced before the
514
- // wsCheckpointHeader.slot will have the checkpoint root
515
- const wsDbCheckpointBlock = await this.db.blockArchive.getByRoot(this.wsCheckpointHeader.root);
516
- if (
517
- !wsDbCheckpointBlock ||
518
- // The only validation we can do here is that wsDbCheckpointBlock is found at/before
519
- // wsCheckpoint's epoch as there could be orphaned/missed slots all the way
520
- // from wsDbCheckpointBlock's slot to the wsCheckpoint's epoch
521
-
522
- // TODO: one can verify the child of wsDbCheckpointBlock is at
523
- // slot > wsCheckpointHeader
524
- // Note: next epoch is at wsCheckpointHeader.slot + SLOTS_PER_EPOCH
525
- wsDbCheckpointBlock.message.slot >= this.wsCheckpointHeader.slot + SLOTS_PER_EPOCH
526
- )
527
- // TODO: explode and stop the entire node
528
- throw new Error(
529
- `InvalidWsCheckpoint root=${toRootHex(this.wsCheckpointHeader.root)}, epoch=${
530
- this.wsCheckpointHeader.slot / SLOTS_PER_EPOCH
531
- }, ${
532
- wsDbCheckpointBlock
533
- ? "found at epoch=" + Math.floor(wsDbCheckpointBlock?.message.slot / SLOTS_PER_EPOCH)
534
- : "not found"
535
- }`
536
- );
537
- this.logger.info("wsCheckpoint validated!", {
538
- root: toRootHex(this.wsCheckpointHeader.root),
539
- epoch: this.wsCheckpointHeader.slot / SLOTS_PER_EPOCH,
540
- });
541
- this.wsValidated = true;
542
- }
543
- }
544
-
545
- private async checkUpdateFromBackfillSequences(): Promise<boolean> {
546
- if (this.syncAnchor.lastBackSyncedBlock === null) {
547
- throw Error("Backfill ranges can only be used once we have a valid lastBackSyncedBlock as a pivot point");
548
- }
549
-
550
- let validSequence = false;
551
- if (this.syncAnchor.lastBackSyncedBlock.slot === null) return validSequence;
552
- const lastBackSyncedSlot = this.syncAnchor.lastBackSyncedBlock.slot;
553
-
554
- const filteredSeqs = await this.db.backfilledRanges.entries({
555
- gte: lastBackSyncedSlot,
556
- });
557
-
558
- if (filteredSeqs.length > 0) {
559
- const jumpBackTo = Math.min(...filteredSeqs.map(({value: justToSlot}) => justToSlot));
560
-
561
- if (jumpBackTo < lastBackSyncedSlot) {
562
- validSequence = true;
563
- const anchorBlock = await this.db.blockArchive.get(jumpBackTo);
564
- if (!anchorBlock) {
565
- validSequence = false;
566
- this.logger.warn(
567
- `Invalid backfill sequence: expected a block at ${jumpBackTo} in blockArchive, ignoring the sequence`
568
- );
569
- }
570
- if (anchorBlock && validSequence && this.prevFinalizedCheckpointBlock.slot >= jumpBackTo) {
571
- this.logger.debug(
572
- `Found a sequence going back to ${jumpBackTo} before the previous finalized or wsCheckpoint`,
573
- {slot: this.prevFinalizedCheckpointBlock.slot}
574
- );
575
-
576
- // Everything saved in db between a backfilled range is a connected sequence
577
- // we only need to check if prevFinalizedCheckpointBlock is in db
578
- const prevBackfillCpBlock = await this.db.blockArchive.getByRoot(this.prevFinalizedCheckpointBlock.root);
579
- if (
580
- prevBackfillCpBlock != null &&
581
- this.prevFinalizedCheckpointBlock.slot === prevBackfillCpBlock.message.slot
582
- ) {
583
- this.logger.verbose("Validated current prevFinalizedCheckpointBlock", {
584
- root: toRootHex(this.prevFinalizedCheckpointBlock.root),
585
- slot: prevBackfillCpBlock.message.slot,
586
- });
587
- } else {
588
- validSequence = false;
589
- this.logger.warn(
590
- `Invalid backfill sequence: previous finalized or checkpoint block root=${toRootHex(
591
- this.prevFinalizedCheckpointBlock.root
592
- )}, slot=${this.prevFinalizedCheckpointBlock.slot} ${
593
- prevBackfillCpBlock ? "found at slot=" + prevBackfillCpBlock.message.slot : "not found"
594
- }, ignoring the sequence`
595
- );
596
- }
597
- }
598
-
599
- if (anchorBlock && validSequence) {
600
- // Update the current sequence in DB as we will be cleaning up previous sequences
601
- await this.db.backfilledRanges.put(this.backfillStartFromSlot, jumpBackTo);
602
- this.backfillRangeWrittenSlot = jumpBackTo;
603
- this.logger.verbose(
604
- `Jumped and updated the backfilled range ${this.backfillStartFromSlot}, ${this.backfillRangeWrittenSlot}`,
605
- {jumpBackTo}
606
- );
607
-
608
- const anchorBlockHeader = blockToHeader(this.config, anchorBlock.message);
609
- const anchorBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(anchorBlockHeader);
610
-
611
- this.syncAnchor = {
612
- anchorBlock,
613
- anchorBlockRoot,
614
- anchorSlot: jumpBackTo,
615
- lastBackSyncedBlock: {root: anchorBlockRoot, slot: jumpBackTo, block: anchorBlock},
616
- };
617
- if (this.prevFinalizedCheckpointBlock.slot >= jumpBackTo) {
618
- // prevFinalizedCheckpointBlock must have been validated, update to a
619
- // new unverified
620
- // finalized or wsCheckpoint behind the new lastBackSyncedBlock
621
- this.prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint(
622
- this.config,
623
- this.db,
624
- jumpBackTo,
625
- this.logger
626
- );
627
- }
628
-
629
- this.metrics?.backfillSync.totalBlocks.inc(
630
- {method: BackfillSyncMethod.backfilled_ranges},
631
- lastBackSyncedSlot - jumpBackTo
632
- );
633
- }
634
- }
635
- }
636
-
637
- // Only delete < backfillStartFromSlot, the keys greater than this would be cleaned
638
- // up by the archival process of forward sync
639
- const cleanupSeqs = filteredSeqs.filter((entry) => entry.key < this.backfillStartFromSlot);
640
- if (cleanupSeqs.length > 0) {
641
- await this.db.backfilledRanges.batchDelete(cleanupSeqs.map((entry) => entry.key));
642
- this.logger.debug(
643
- `Cleaned up the old sequences between ${this.backfillStartFromSlot},${toRootHex(
644
- this.syncAnchor.lastBackSyncedBlock.root
645
- )}`,
646
- {cleanupSeqs: JSON.stringify(cleanupSeqs)}
647
- );
648
- }
649
-
650
- return validSequence;
651
- }
652
-
653
- private async fastBackfillDb(): Promise<boolean> {
654
- // Block of this anchorBlockRoot can't be behind the prevFinalizedCheckpointBlock
655
- // as prevFinalizedCheckpointBlock can't be skipped
656
- let anchorBlockRoot: Root;
657
- let expectedSlot: Slot | null = null;
658
- if (this.syncAnchor.anchorBlock) {
659
- anchorBlockRoot = this.syncAnchor.anchorBlock.message.parentRoot;
660
- } else {
661
- anchorBlockRoot = this.syncAnchor.anchorBlockRoot;
662
- expectedSlot = this.syncAnchor.anchorSlot;
663
- }
664
- let anchorBlock = await this.db.blockArchive.getByRoot(anchorBlockRoot);
665
- if (!anchorBlock) return false;
666
-
667
- if (expectedSlot !== null && anchorBlock.message.slot !== expectedSlot)
668
- throw Error(
669
- `Invalid slot of anchorBlock read from DB with root=${toRootHex(
670
- anchorBlockRoot
671
- )}, expected=${expectedSlot}, actual=${anchorBlock.message.slot}`
672
- );
673
-
674
- // If possible, read back till anchorBlock > this.prevFinalizedCheckpointBlock
675
- let parentBlock: SignedBeaconBlock | null,
676
- backCount = 1;
677
-
678
- let isPrevFinWsConfirmedAnchorParent = false;
679
- while (
680
- backCount !== this.opts.backfillBatchSize &&
681
- // biome-ignore lint/suspicious/noAssignInExpressions: May be refactored later
682
- (parentBlock = await this.db.blockArchive.getByRoot(anchorBlock.message.parentRoot))
683
- ) {
684
- // Before moving anchorBlock back, we need check for prevFinalizedCheckpointBlock
685
- if (anchorBlock.message.slot < this.prevFinalizedCheckpointBlock.slot) {
686
- throw Error(
687
- `Skipped a prevFinalizedCheckpointBlock with slot=${toRootHex(
688
- this.prevFinalizedCheckpointBlock.root
689
- )}, root=${toRootHex(this.prevFinalizedCheckpointBlock.root)}`
690
- );
691
- }
692
- if (anchorBlock.message.slot === this.prevFinalizedCheckpointBlock.slot) {
693
- if (
694
- !isPrevFinWsConfirmedAnchorParent &&
695
- !byteArrayEquals(anchorBlockRoot, this.prevFinalizedCheckpointBlock.root)
696
- ) {
697
- throw Error(
698
- `Invalid root for prevFinalizedCheckpointBlock at slot=${
699
- this.prevFinalizedCheckpointBlock.slot
700
- }, expected=${toRootHex(this.prevFinalizedCheckpointBlock.root)}, found=${toRootHex(anchorBlockRoot)}`
701
- );
702
- }
703
-
704
- // If the new parentBlock is just one slot back, we can safely assign
705
- // prevFinalizedCheckpointBlock with the parentBlock and skip root
706
- // validation in next iteration. Else we need to extract
707
- // prevFinalizedCheckpointBlock
708
- if (parentBlock.message.slot === anchorBlock.message.slot - 1) {
709
- this.prevFinalizedCheckpointBlock = {root: anchorBlock.message.parentRoot, slot: parentBlock.message.slot};
710
- isPrevFinWsConfirmedAnchorParent = true;
711
- } else {
712
- // Extract new prevFinalizedCheckpointBlock below anchorBlock
713
- this.prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint(
714
- this.config,
715
- this.db,
716
- anchorBlock.message.slot,
717
- this.logger
718
- );
719
- isPrevFinWsConfirmedAnchorParent = false;
720
- }
721
- }
722
- anchorBlockRoot = anchorBlock.message.parentRoot;
723
- anchorBlock = parentBlock;
724
- backCount++;
725
- }
726
-
727
- this.syncAnchor = {
728
- anchorBlock,
729
- anchorBlockRoot,
730
- anchorSlot: anchorBlock.message.slot,
731
- lastBackSyncedBlock: {root: anchorBlockRoot, slot: anchorBlock.message.slot, block: anchorBlock},
732
- };
733
- this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.database}, backCount);
734
- this.logger.verbose(`Read ${backCount} blocks from DB till `, {
735
- slot: anchorBlock.message.slot,
736
- });
737
- if (backCount >= this.opts.backfillBatchSize) {
738
- // We should sleep as there seems to be more that can be read from db but yielding to
739
- // the sync loop hardly gives any breather to the beacon node
740
- await sleep(DB_READ_BREATHER_TIMEOUT, this.signal);
741
- }
742
- return true;
743
- }
744
-
745
- private async syncBlockByRoot(peer: PeerIdStr, anchorBlockRoot: Root): Promise<void> {
746
- const res = await this.network.sendBeaconBlocksByRoot(peer, [anchorBlockRoot]);
747
- if (res.length < 1) throw new Error("InvalidBlockSyncedFromPeer");
748
- const anchorBlock = res[0];
749
-
750
- // GENESIS_SLOT doesn't has valid signature
751
- if (anchorBlock.message.slot === GENESIS_SLOT) return;
752
- await verifyBlockProposerSignature(this.chain.config, this.chain.bls, [anchorBlock]);
753
-
754
- // We can write to the disk if this is ahead of prevFinalizedCheckpointBlock otherwise
755
- // we will need to go make checks on the top of sync loop before writing as it might
756
- // override prevFinalizedCheckpointBlock
757
- if (this.prevFinalizedCheckpointBlock.slot < anchorBlock.message.slot) {
758
- const serialized = this.chain.serializedCache.get(anchorBlock);
759
- if (serialized) {
760
- await this.db.blockArchive.putBinary(anchorBlock.message.slot, serialized);
761
- } else {
762
- await this.db.blockArchive.put(anchorBlock.message.slot, anchorBlock);
763
- }
764
- }
765
-
766
- this.syncAnchor = {
767
- anchorBlock: anchorBlock,
768
- anchorBlockRoot,
769
- anchorSlot: anchorBlock.message.slot,
770
- lastBackSyncedBlock: {root: anchorBlockRoot, slot: anchorBlock.message.slot, block: anchorBlock},
771
- };
772
-
773
- this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.blockbyroot});
774
-
775
- this.logger.verbose("Fetched new anchorBlock", {
776
- root: toRootHex(anchorBlockRoot),
777
- slot: anchorBlock.message.slot,
778
- });
779
-
780
- return;
781
- }
782
-
783
- private async syncRange(peer: PeerIdStr): Promise<void> {
784
- if (!this.syncAnchor.anchorBlock) {
785
- throw Error("Invalid anchorBlock null for syncRange");
786
- }
787
-
788
- const toSlot = this.syncAnchor.anchorBlock.message.slot;
789
- const fromSlot = Math.max(
790
- toSlot - this.opts.backfillBatchSize,
791
- this.prevFinalizedCheckpointBlock.slot,
792
- GENESIS_SLOT
793
- );
794
- const blocks = await this.network.sendBeaconBlocksByRange(peer, {
795
- startSlot: fromSlot,
796
- count: toSlot - fromSlot,
797
- step: 1,
798
- });
799
-
800
- const anchorParentRoot = this.syncAnchor.anchorBlock.message.parentRoot;
801
-
802
- if (blocks.length === 0) {
803
- // Lets just directly try to jump to anchorParentRoot
804
- this.syncAnchor = {
805
- anchorBlock: null,
806
- anchorBlockRoot: anchorParentRoot,
807
- anchorSlot: null,
808
- lastBackSyncedBlock: this.syncAnchor.lastBackSyncedBlock,
809
- };
810
- return;
811
- }
812
-
813
- const {nextAnchor, verifiedBlocks, error} = verifyBlockSequence(this.config, blocks, anchorParentRoot);
814
-
815
- // If any of the block's proposer signature fail, we can't trust this peer at all
816
- if (verifiedBlocks.length > 0) {
817
- await verifyBlockProposerSignature(this.chain.config, this.chain.bls, verifiedBlocks);
818
-
819
- // This is bad, like super bad. Abort the backfill
820
- if (!nextAnchor)
821
- throw new BackfillSyncError({
822
- code: BackfillSyncErrorCode.INTERNAL_ERROR,
823
- reason: "Invalid verifyBlockSequence result",
824
- });
825
-
826
- // Verified blocks are in reverse order with the nextAnchor being the smallest slot
827
- // if nextAnchor is on the same slot as prevFinalizedCheckpointBlock, we can't save
828
- // it before returning to top of sync loop for validation
829
- const blocksToPut =
830
- nextAnchor.slot > this.prevFinalizedCheckpointBlock.slot
831
- ? verifiedBlocks
832
- : verifiedBlocks.slice(0, verifiedBlocks.length - 1);
833
-
834
- const binaryPuts = [];
835
- const nonBinaryPuts = [];
836
-
837
- for (const block of blocksToPut) {
838
- const serialized = this.chain.serializedCache.get(block);
839
- const item = {
840
- key: block.message.slot,
841
- slot: block.message.slot,
842
- blockRoot: this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message),
843
- parentRoot: block.message.parentRoot,
844
- };
845
-
846
- if (serialized) {
847
- binaryPuts.push({...item, value: serialized});
848
- } else {
849
- nonBinaryPuts.push({...item, value: block});
850
- }
851
- }
852
-
853
- if (binaryPuts.length > 0) {
854
- await this.db.blockArchive.batchPutBinary(binaryPuts);
855
- }
856
- if (nonBinaryPuts.length > 0) {
857
- await this.db.blockArchive.batchPut(nonBinaryPuts);
858
- }
859
-
860
- this.metrics?.backfillSync.totalBlocks.inc({method: BackfillSyncMethod.rangesync}, verifiedBlocks.length);
861
- }
862
-
863
- // If nextAnchor provided, found some linear anchored blocks
864
- if (nextAnchor !== null) {
865
- this.syncAnchor = {
866
- anchorBlock: nextAnchor.block,
867
- anchorBlockRoot: nextAnchor.root,
868
- anchorSlot: nextAnchor.slot,
869
- lastBackSyncedBlock: nextAnchor,
870
- };
871
- this.logger.verbose(`syncRange discovered ${verifiedBlocks.length} valid blocks`, {
872
- backfilled: this.syncAnchor.lastBackSyncedBlock.slot,
873
- });
874
- }
875
- if (error != null) throw new BackfillSyncError({code: error});
876
- }
877
- }
878
-
879
- async function extractPreviousFinOrWsCheckpoint(
880
- config: ChainForkConfig,
881
- db: IBeaconDb,
882
- belowSlot: Slot,
883
- logger?: Logger
884
- ): Promise<BackfillBlockHeader> {
885
- // Anything below genesis block is just zero hash
886
- if (belowSlot <= GENESIS_SLOT) return {root: ZERO_HASH, slot: belowSlot - 1};
887
-
888
- // To extract the next prevFinalizedCheckpointBlock, we just need to look back in DB
889
- // Any saved previous finalized or ws checkpoint, will also have a corresponding block
890
- // saved in DB, as we make sure of that
891
- // 1. When we archive new finalized state and blocks
892
- // 2. When we backfill from a wsCheckpoint
893
- const nextPrevFinOrWsBlock = (
894
- await db.blockArchive.values({
895
- lt: belowSlot,
896
- reverse: true,
897
- limit: 1,
898
- })
899
- )[0];
900
-
901
- let prevFinalizedCheckpointBlock: BackfillBlockHeader;
902
- if (nextPrevFinOrWsBlock != null) {
903
- const header = blockToHeader(config, nextPrevFinOrWsBlock.message);
904
- const root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header);
905
- prevFinalizedCheckpointBlock = {root, slot: nextPrevFinOrWsBlock.message.slot};
906
- logger?.debug("Extracted new prevFinalizedCheckpointBlock as potential previous finalized or wsCheckpoint", {
907
- root: toRootHex(prevFinalizedCheckpointBlock.root),
908
- slot: prevFinalizedCheckpointBlock.slot,
909
- });
910
- } else {
911
- // GENESIS_SLOT -1 is the placeholder for parentHash of the genesis block
912
- // which should always be ZERO_HASH.
913
- prevFinalizedCheckpointBlock = {root: ZERO_HASH, slot: GENESIS_SLOT - 1};
914
- }
915
- return prevFinalizedCheckpointBlock;
916
- }