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

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