@lodestar/beacon-node 1.47.0-dev.b2cbfa50c2 → 1.47.0-dev.be9d5b774d

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 (81) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +3 -6
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +4 -0
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/events/index.d.ts +1 -1
  8. package/lib/api/impl/events/index.d.ts.map +1 -1
  9. package/lib/api/impl/events/index.js +10 -3
  10. package/lib/api/impl/events/index.js.map +1 -1
  11. package/lib/api/impl/validator/index.js +1 -1
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  14. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  15. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  16. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  17. package/lib/chain/blocks/importBlock.js +2 -7
  18. package/lib/chain/blocks/importBlock.js.map +1 -1
  19. package/lib/chain/builderCircuitBreaker.d.ts +6 -5
  20. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  21. package/lib/chain/builderCircuitBreaker.js +28 -15
  22. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  23. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  24. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  25. package/lib/chain/errors/proposerPreferences.js +1 -0
  26. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  27. package/lib/chain/options.d.ts +1 -1
  28. package/lib/chain/options.d.ts.map +1 -1
  29. package/lib/chain/prepareNextSlot.js +1 -1
  30. package/lib/chain/prepareNextSlot.js.map +1 -1
  31. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  32. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  33. package/lib/chain/validation/proposerPreferences.js +11 -2
  34. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  35. package/lib/execution/builder/http.d.ts +10 -7
  36. package/lib/execution/builder/http.d.ts.map +1 -1
  37. package/lib/execution/builder/http.js +11 -8
  38. package/lib/execution/builder/http.js.map +1 -1
  39. package/lib/metrics/metrics/beacon.d.ts +3 -3
  40. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  41. package/lib/metrics/metrics/beacon.js +9 -9
  42. package/lib/metrics/metrics/beacon.js.map +1 -1
  43. package/lib/metrics/metrics/lodestar.d.ts +10 -0
  44. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  45. package/lib/metrics/metrics/lodestar.js +16 -0
  46. package/lib/metrics/metrics/lodestar.js.map +1 -1
  47. package/lib/network/libp2p/index.d.ts.map +1 -1
  48. package/lib/network/libp2p/index.js +10 -2
  49. package/lib/network/libp2p/index.js.map +1 -1
  50. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  51. package/lib/network/processor/gossipHandlers.js +7 -2
  52. package/lib/network/processor/gossipHandlers.js.map +1 -1
  53. package/lib/network/reqresp/score.d.ts.map +1 -1
  54. package/lib/network/reqresp/score.js +5 -11
  55. package/lib/network/reqresp/score.js.map +1 -1
  56. package/lib/sync/types.d.ts.map +1 -1
  57. package/lib/sync/types.js.map +1 -1
  58. package/lib/sync/unknownBlock.d.ts +9 -0
  59. package/lib/sync/unknownBlock.d.ts.map +1 -1
  60. package/lib/sync/unknownBlock.js +93 -9
  61. package/lib/sync/unknownBlock.js.map +1 -1
  62. package/package.json +14 -14
  63. package/src/api/impl/beacon/blocks/index.ts +4 -6
  64. package/src/api/impl/beacon/pool/index.ts +5 -0
  65. package/src/api/impl/events/index.ts +10 -4
  66. package/src/api/impl/validator/index.ts +1 -1
  67. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  68. package/src/chain/blocks/importBlock.ts +1 -6
  69. package/src/chain/builderCircuitBreaker.ts +31 -17
  70. package/src/chain/errors/proposerPreferences.ts +7 -0
  71. package/src/chain/options.ts +1 -1
  72. package/src/chain/prepareNextSlot.ts +1 -1
  73. package/src/chain/validation/proposerPreferences.ts +12 -2
  74. package/src/execution/builder/http.ts +11 -11
  75. package/src/metrics/metrics/beacon.ts +9 -9
  76. package/src/metrics/metrics/lodestar.ts +16 -0
  77. package/src/network/libp2p/index.ts +11 -2
  78. package/src/network/processor/gossipHandlers.ts +9 -0
  79. package/src/network/reqresp/score.ts +5 -11
  80. package/src/sync/types.ts +3 -1
  81. package/src/sync/unknownBlock.ts +108 -12
@@ -1,5 +1,6 @@
1
1
  import {routes} from "@lodestar/api";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
+ import {CheckpointWithHex} from "@lodestar/fork-choice";
3
4
  import {ForkSeq, isForkPostGloas} from "@lodestar/params";
4
5
  import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
5
6
  import {computeTimeAtSlot} from "@lodestar/state-transition";
@@ -154,6 +155,7 @@ export class BlockInputSync {
154
155
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
155
156
  this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
156
157
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
158
+ this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
157
159
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
158
160
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
159
161
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -170,6 +172,7 @@ export class BlockInputSync {
170
172
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
171
173
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
172
174
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
175
+ this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
173
176
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
174
177
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
175
178
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -215,7 +218,7 @@ export class BlockInputSync {
215
218
 
216
219
  private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
217
220
  try {
218
- this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
221
+ this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
219
222
  this.triggerUnknownBlockSearch();
220
223
  this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
221
224
  this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
@@ -258,7 +261,7 @@ export class BlockInputSync {
258
261
  }
259
262
 
260
263
  if (missingDependency.kind === "parentPayload") {
261
- this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
264
+ this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
262
265
  } else if (missingDependency.kind === "parentBlock") {
263
266
  this.addByRootHex(missingDependency.rootHex, data.peer);
264
267
  }
@@ -284,6 +287,61 @@ export class BlockInputSync {
284
287
  this.triggerUnknownBlockSearch();
285
288
  };
286
289
 
290
+ /**
291
+ * Drop pending payloads and blocks that fell below the finalized slot.
292
+ * See https://github.com/ChainSafe/lodestar/issues/9800
293
+ */
294
+ private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
295
+ // Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
296
+ // because the payload at the finalized block is not finalized
297
+ const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
298
+
299
+ let deletedPayloads = 0;
300
+ let unresolvedSlotPayloads = 0;
301
+ for (const [rootHex, payload] of this.pendingPayloads) {
302
+ // Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
303
+ if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
304
+ payload.slot = this.resolvePayloadSlot(rootHex);
305
+ }
306
+
307
+ const slot = getPayloadSyncCacheItemSlot(payload);
308
+ if (typeof slot !== "number") {
309
+ unresolvedSlotPayloads++;
310
+ this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
311
+ root: rootHex,
312
+ finalizedSlot,
313
+ });
314
+ continue;
315
+ }
316
+
317
+ if (slot < finalizedSlot) {
318
+ this.pendingPayloads.delete(rootHex);
319
+ deletedPayloads++;
320
+ }
321
+ }
322
+
323
+ let deletedBlocks = 0;
324
+ for (const [rootHex, block] of this.pendingBlocks) {
325
+ const slot = getBlockInputSyncCacheItemSlot(block);
326
+ if (typeof slot === "number" && slot < finalizedSlot) {
327
+ this.pendingBlocks.delete(rootHex);
328
+ deletedBlocks++;
329
+ }
330
+ }
331
+
332
+ if (deletedPayloads > 0 || deletedBlocks > 0) {
333
+ this.metrics?.blockInputSync.removedBlocks.inc(deletedPayloads + deletedBlocks);
334
+ }
335
+
336
+ this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
337
+ finalizedSlot,
338
+ finalizedRoot: checkpoint.rootHex,
339
+ deletedPayloads,
340
+ deletedBlocks,
341
+ unresolvedSlotPayloads,
342
+ });
343
+ };
344
+
287
345
  private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
288
346
  let pendingBlock = this.pendingBlocks.get(rootHex);
289
347
  let added = false;
@@ -382,6 +440,29 @@ export class BlockInputSync {
382
440
  return added;
383
441
  };
384
442
 
443
+ /**
444
+ * Resolve the block slot for a payload root.
445
+ */
446
+ private resolvePayloadSlot(rootHex: RootHex): Slot | undefined {
447
+ // a pending block of the same root
448
+ const pendingBlock = this.pendingBlocks.get(rootHex);
449
+ if (pendingBlock) {
450
+ const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
451
+ if (typeof pendingBlockSlot === "number") {
452
+ return pendingBlockSlot;
453
+ }
454
+ }
455
+
456
+ // the block is in fork choice
457
+ const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
458
+ if (forkChoiceSlot !== undefined) {
459
+ return forkChoiceSlot;
460
+ }
461
+
462
+ // a seen payload envelope input
463
+ return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
464
+ }
465
+
385
466
  private addByPayloadInput = (
386
467
  payloadInput: PayloadEnvelopeInput,
387
468
  peerIdStr?: PeerIdStr,
@@ -436,10 +517,12 @@ export class BlockInputSync {
436
517
  blockInput: IBlockInput
437
518
  ):
438
519
  | {kind: "ready"}
439
- | {kind: "block" | "parentBlock" | "parentPayload"; rootHex: RootHex}
520
+ | {kind: "block" | "parentBlock"; rootHex: RootHex}
521
+ | {kind: "parentPayload"; rootHex: RootHex; slot: Slot}
440
522
  | {kind: "invalidParentPayload"; parentRootHex: RootHex; parentBlockHashHex: RootHex} {
441
523
  const parentRootHex = blockInput.parentRootHex;
442
- if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
524
+ const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
525
+ if (!parentBlock) {
443
526
  return {kind: "parentBlock", rootHex: parentRootHex};
444
527
  }
445
528
 
@@ -464,13 +547,13 @@ export class BlockInputSync {
464
547
  const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
465
548
  if (parentPayloadInput) {
466
549
  if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
467
- return {kind: "parentPayload", rootHex: parentRootHex};
550
+ return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
468
551
  }
469
552
 
470
553
  return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
471
554
  }
472
555
 
473
- return {kind: "parentPayload", rootHex: parentRootHex};
556
+ return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
474
557
  }
475
558
 
476
559
  private advancePendingBlock(pendingBlock: PendingBlockInput): AdvancePendingBlockResult {
@@ -493,9 +576,9 @@ export class BlockInputSync {
493
576
  }
494
577
 
495
578
  case "parentPayload": {
496
- let added = this.addByPayloadRootHex(missingDependency.rootHex);
579
+ let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
497
580
  for (const peerIdStr of pendingBlock.peerIdStrings) {
498
- added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
581
+ added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
499
582
  }
500
583
  return added ? "queued_parent_payload" : "blocked";
501
584
  }
@@ -928,6 +1011,7 @@ export class BlockInputSync {
928
1011
  const pendingPayloadByRoot: PendingPayloadRootHex = {
929
1012
  status: PendingPayloadInputStatus.pending,
930
1013
  rootHex,
1014
+ slot: pendingPayload.envelope.message.payload.slotNumber,
931
1015
  timeAddedSec: pendingPayload.timeAddedSec,
932
1016
  peerIdStrings: new Set(pendingPayload.peerIdStrings),
933
1017
  };
@@ -967,6 +1051,10 @@ export class BlockInputSync {
967
1051
  return;
968
1052
  }
969
1053
 
1054
+ if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
1055
+ payload.slot = this.resolvePayloadSlot(rootHex);
1056
+ }
1057
+
970
1058
  const payloadSlot = getPayloadSyncCacheItemSlot(payload);
971
1059
  const logCtx = {
972
1060
  slot: payloadSlot,
@@ -982,7 +1070,13 @@ export class BlockInputSync {
982
1070
  const res = await wrapError(this.fetchPayloadInput(payload));
983
1071
  if (!res.err) {
984
1072
  const pendingPayload = res.result;
985
- this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
1073
+ const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
1074
+ // finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
1075
+ if (!this.pendingPayloads.has(resultRootHex)) {
1076
+ this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
1077
+ return;
1078
+ }
1079
+ this.pendingPayloads.set(resultRootHex, pendingPayload);
986
1080
 
987
1081
  if (isPendingPayloadEnvelope(pendingPayload)) {
988
1082
  await this.reconcilePayloadEnvelope(pendingPayload);
@@ -1110,9 +1204,11 @@ export class BlockInputSync {
1110
1204
  );
1111
1205
  }
1112
1206
 
1113
- throw Error(
1114
- `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1115
- );
1207
+ const reason =
1208
+ pendingColumns.size > 0
1209
+ ? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1210
+ : "no peer available to download pending payload";
1211
+ throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
1116
1212
  }
1117
1213
 
1118
1214
  const {peerId, client: peerClient} = peerMeta;