@lodestar/beacon-node 1.43.0-dev.a6d8600051 → 1.43.0-dev.ac258a8def

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 (220) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +10 -0
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/debug/index.d.ts.map +1 -1
  5. package/lib/api/impl/debug/index.js +0 -1
  6. package/lib/api/impl/debug/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +2 -1
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  11. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  12. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  13. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  14. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  15. package/lib/chain/blocks/importBlock.js +16 -29
  16. package/lib/chain/blocks/importBlock.js.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
  18. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  19. package/lib/chain/blocks/importExecutionPayload.js +15 -6
  20. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  21. package/lib/chain/blocks/index.d.ts.map +1 -1
  22. package/lib/chain/blocks/index.js +29 -17
  23. package/lib/chain/blocks/index.js.map +1 -1
  24. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
  25. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  26. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +24 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
  29. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  30. package/lib/chain/blocks/types.d.ts +2 -1
  31. package/lib/chain/blocks/types.d.ts.map +1 -1
  32. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
  33. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  34. package/lib/chain/blocks/utils/chainSegment.js +34 -23
  35. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  36. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  37. package/lib/chain/blocks/verifyBlock.js +5 -6
  38. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  39. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  40. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  43. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  44. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  45. package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
  46. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  47. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  48. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  49. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/chain.d.ts.map +1 -1
  52. package/lib/chain/chain.js +25 -10
  53. package/lib/chain/chain.js.map +1 -1
  54. package/lib/chain/emitter.d.ts +0 -11
  55. package/lib/chain/emitter.d.ts.map +1 -1
  56. package/lib/chain/emitter.js +0 -4
  57. package/lib/chain/emitter.js.map +1 -1
  58. package/lib/chain/errors/proposerPreferences.d.ts +8 -1
  59. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  60. package/lib/chain/errors/proposerPreferences.js +1 -0
  61. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  62. package/lib/chain/initState.d.ts.map +1 -1
  63. package/lib/chain/initState.js +6 -1
  64. package/lib/chain/initState.js.map +1 -1
  65. package/lib/chain/prepareNextSlot.js +1 -1
  66. package/lib/chain/prepareNextSlot.js.map +1 -1
  67. package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -0
  68. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  69. package/lib/chain/produceBlock/produceBlockBody.js +2 -7
  70. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  71. package/lib/chain/regen/queued.d.ts.map +1 -1
  72. package/lib/chain/regen/queued.js +1 -4
  73. package/lib/chain/regen/queued.js.map +1 -1
  74. package/lib/chain/regen/regen.d.ts.map +1 -1
  75. package/lib/chain/regen/regen.js +1 -4
  76. package/lib/chain/regen/regen.js.map +1 -1
  77. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
  78. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  79. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +63 -19
  80. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  81. package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
  82. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  83. package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
  84. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  85. package/lib/chain/validation/executionPayloadBid.js +11 -8
  86. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  87. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  88. package/lib/chain/validation/proposerPreferences.js +39 -17
  89. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  90. package/lib/network/gossip/topic.d.ts +2 -0
  91. package/lib/network/gossip/topic.d.ts.map +1 -1
  92. package/lib/network/interface.d.ts +1 -0
  93. package/lib/network/interface.d.ts.map +1 -1
  94. package/lib/network/network.d.ts +1 -0
  95. package/lib/network/network.d.ts.map +1 -1
  96. package/lib/network/network.js +3 -0
  97. package/lib/network/network.js.map +1 -1
  98. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  99. package/lib/network/processor/gossipHandlers.js +24 -7
  100. package/lib/network/processor/gossipHandlers.js.map +1 -1
  101. package/lib/network/processor/index.js +5 -5
  102. package/lib/network/processor/index.js.map +1 -1
  103. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  104. package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
  105. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  106. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
  107. package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
  108. package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
  109. package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
  110. package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
  111. package/lib/network/reqresp/handlers/index.js +5 -0
  112. package/lib/network/reqresp/handlers/index.js.map +1 -1
  113. package/lib/network/reqresp/interface.d.ts +1 -1
  114. package/lib/network/reqresp/interface.js +1 -1
  115. package/lib/network/reqresp/protocols.d.ts +1 -0
  116. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  117. package/lib/network/reqresp/protocols.js +5 -0
  118. package/lib/network/reqresp/protocols.js.map +1 -1
  119. package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
  120. package/lib/network/reqresp/rateLimit.js +4 -0
  121. package/lib/network/reqresp/rateLimit.js.map +1 -1
  122. package/lib/network/reqresp/score.d.ts.map +1 -1
  123. package/lib/network/reqresp/score.js +1 -0
  124. package/lib/network/reqresp/score.js.map +1 -1
  125. package/lib/network/reqresp/types.d.ts +3 -0
  126. package/lib/network/reqresp/types.d.ts.map +1 -1
  127. package/lib/network/reqresp/types.js +3 -0
  128. package/lib/network/reqresp/types.js.map +1 -1
  129. package/lib/node/nodejs.js +2 -2
  130. package/lib/node/nodejs.js.map +1 -1
  131. package/lib/node/notifier.js +1 -7
  132. package/lib/node/notifier.js.map +1 -1
  133. package/lib/sync/constants.d.ts +7 -1
  134. package/lib/sync/constants.d.ts.map +1 -1
  135. package/lib/sync/constants.js +7 -4
  136. package/lib/sync/constants.js.map +1 -1
  137. package/lib/sync/range/batch.d.ts +23 -3
  138. package/lib/sync/range/batch.d.ts.map +1 -1
  139. package/lib/sync/range/batch.js +191 -36
  140. package/lib/sync/range/batch.js.map +1 -1
  141. package/lib/sync/range/chain.d.ts +16 -2
  142. package/lib/sync/range/chain.d.ts.map +1 -1
  143. package/lib/sync/range/chain.js +99 -8
  144. package/lib/sync/range/chain.js.map +1 -1
  145. package/lib/sync/range/range.d.ts.map +1 -1
  146. package/lib/sync/range/range.js +54 -5
  147. package/lib/sync/range/range.js.map +1 -1
  148. package/lib/sync/sync.d.ts.map +1 -1
  149. package/lib/sync/sync.js +13 -0
  150. package/lib/sync/sync.js.map +1 -1
  151. package/lib/sync/unknownBlock.d.ts +7 -2
  152. package/lib/sync/unknownBlock.d.ts.map +1 -1
  153. package/lib/sync/unknownBlock.js +127 -57
  154. package/lib/sync/unknownBlock.js.map +1 -1
  155. package/lib/sync/utils/downloadByRange.d.ts +36 -9
  156. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  157. package/lib/sync/utils/downloadByRange.js +106 -42
  158. package/lib/sync/utils/downloadByRange.js.map +1 -1
  159. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  160. package/lib/sync/utils/downloadByRoot.js +10 -0
  161. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  162. package/lib/sync/utils/rateLimit.d.ts +2 -0
  163. package/lib/sync/utils/rateLimit.d.ts.map +1 -0
  164. package/lib/sync/utils/rateLimit.js +15 -0
  165. package/lib/sync/utils/rateLimit.js.map +1 -0
  166. package/lib/util/sszBytes.d.ts.map +1 -1
  167. package/lib/util/sszBytes.js +8 -6
  168. package/lib/util/sszBytes.js.map +1 -1
  169. package/package.json +16 -16
  170. package/src/api/impl/beacon/blocks/index.ts +13 -0
  171. package/src/api/impl/debug/index.ts +0 -1
  172. package/src/api/impl/validator/index.ts +2 -1
  173. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  174. package/src/chain/blocks/importBlock.ts +16 -49
  175. package/src/chain/blocks/importExecutionPayload.ts +21 -11
  176. package/src/chain/blocks/index.ts +18 -9
  177. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +32 -2
  178. package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
  179. package/src/chain/blocks/types.ts +2 -1
  180. package/src/chain/blocks/utils/chainSegment.ts +35 -24
  181. package/src/chain/blocks/verifyBlock.ts +7 -5
  182. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  183. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
  184. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
  185. package/src/chain/chain.ts +29 -9
  186. package/src/chain/emitter.ts +0 -11
  187. package/src/chain/errors/proposerPreferences.ts +9 -1
  188. package/src/chain/initState.ts +9 -1
  189. package/src/chain/prepareNextSlot.ts +1 -1
  190. package/src/chain/produceBlock/produceBlockBody.ts +3 -7
  191. package/src/chain/regen/queued.ts +2 -7
  192. package/src/chain/regen/regen.ts +2 -7
  193. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +78 -22
  194. package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
  195. package/src/chain/validation/executionPayloadBid.ts +11 -8
  196. package/src/chain/validation/proposerPreferences.ts +37 -18
  197. package/src/network/interface.ts +1 -0
  198. package/src/network/network.ts +12 -0
  199. package/src/network/processor/gossipHandlers.ts +30 -7
  200. package/src/network/processor/index.ts +5 -5
  201. package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
  202. package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
  203. package/src/network/reqresp/handlers/index.ts +5 -0
  204. package/src/network/reqresp/interface.ts +1 -1
  205. package/src/network/reqresp/protocols.ts +6 -0
  206. package/src/network/reqresp/rateLimit.ts +4 -0
  207. package/src/network/reqresp/score.ts +1 -0
  208. package/src/network/reqresp/types.ts +5 -0
  209. package/src/node/nodejs.ts +2 -2
  210. package/src/node/notifier.ts +1 -8
  211. package/src/sync/constants.ts +8 -4
  212. package/src/sync/range/batch.ts +240 -42
  213. package/src/sync/range/chain.ts +118 -9
  214. package/src/sync/range/range.ts +68 -5
  215. package/src/sync/sync.ts +13 -1
  216. package/src/sync/unknownBlock.ts +157 -60
  217. package/src/sync/utils/downloadByRange.ts +174 -44
  218. package/src/sync/utils/downloadByRoot.ts +12 -0
  219. package/src/sync/utils/rateLimit.ts +16 -0
  220. package/src/util/sszBytes.ts +8 -6
@@ -1,6 +1,6 @@
1
1
  import {ChainForkConfig} from "@lodestar/config";
2
- import {Epoch, Root, Slot} from "@lodestar/types";
3
- import {ErrorAborted, LodestarError, Logger, toRootHex} from "@lodestar/utils";
2
+ import {Epoch, Root, Slot, gloas} from "@lodestar/types";
3
+ import {ErrorAborted, LodestarError, Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
4
4
  import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
5
5
  import {BlockInputErrorCode} from "../../chain/blocks/blockInput/errors.js";
6
6
  import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
@@ -17,6 +17,7 @@ import {PeerIdStr} from "../../util/peerId.js";
17
17
  import {WarnResult, wrapError} from "../../util/wrapError.js";
18
18
  import {BATCH_BUFFER_SIZE, EPOCHS_PER_BATCH, MAX_LOOK_AHEAD_EPOCHS} from "../constants.js";
19
19
  import {DownloadByRangeError, DownloadByRangeErrorCode} from "../utils/downloadByRange.js";
20
+ import {getRateLimitedUntilMs} from "../utils/rateLimit.js";
20
21
  import {RangeSyncType} from "../utils/remoteSyncType.js";
21
22
  import {Batch, BatchError, BatchErrorCode, BatchMetadata, BatchStatus} from "./batch.js";
22
23
  import {
@@ -139,20 +140,33 @@ export class SyncChain {
139
140
  private readonly batchProcessor = new ItTrigger();
140
141
  /** Sorted map of batches undergoing some kind of processing. */
141
142
  private readonly batches = new Map<Epoch, Batch>();
143
+ /**
144
+ * `true` until the first `Batch` is constructed via `includeNextBatch`
145
+ */
146
+ private isFirstBatch = true;
142
147
  private readonly peerset = new Map<PeerIdStr, ChainTarget>();
148
+ /**
149
+ * Tracks peers that have rate-limited us, mapped to the timestamp (ms) until which we should avoid them.
150
+ * This is a sync-layer optimization to avoid assigning batches to backed-off peers.
151
+ * The reqresp SelfRateLimiter independently enforces backoff at the protocol level as a safety net.
152
+ */
153
+ private readonly rateLimitedPeers = new Map<PeerIdStr, number>();
154
+ private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
143
155
 
144
156
  private readonly logger: Logger;
145
157
  private readonly config: ChainForkConfig;
146
158
  private readonly clock: IClock;
147
159
  private readonly metrics: Metrics | null;
148
160
  private readonly custodyConfig: CustodyConfig;
161
+ private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
149
162
 
150
163
  constructor(
151
164
  initialBatchEpoch: Epoch,
152
165
  initialTarget: ChainTarget,
153
166
  syncType: RangeSyncType,
154
167
  fns: SyncChainFns,
155
- modules: SyncChainModules
168
+ modules: SyncChainModules,
169
+ latestBid: gloas.ExecutionPayloadBid | undefined
156
170
  ) {
157
171
  const {config, clock, custodyConfig, logger, metrics} = modules;
158
172
  this.firstBatchEpoch = initialBatchEpoch;
@@ -168,6 +182,7 @@ export class SyncChain {
168
182
  this.clock = clock;
169
183
  this.metrics = metrics;
170
184
  this.custodyConfig = custodyConfig;
185
+ this.latestBid = latestBid;
171
186
  this.logger = logger;
172
187
  this.logId = `${syncType}-${nextChainId++}`;
173
188
 
@@ -222,12 +237,16 @@ export class SyncChain {
222
237
  */
223
238
  stopSyncing(): void {
224
239
  this.status = SyncChainStatus.Stopped;
240
+ this.clearRateLimitBackoffTimer();
241
+ this.logger.debug("SyncChain stopSyncing", {id: this.logId});
225
242
  }
226
243
 
227
244
  /**
228
245
  * Permanently remove this chain. Throws the main AsyncIterable
229
246
  */
230
247
  remove(): void {
248
+ this.logger.debug("SyncChain remove", {id: this.logId});
249
+ this.clearRateLimitBackoffTimer();
231
250
  this.batchProcessor.end(new ErrorAborted("SyncChain"));
232
251
  }
233
252
 
@@ -246,6 +265,7 @@ export class SyncChain {
246
265
  */
247
266
  removePeer(peerId: PeerIdStr): boolean {
248
267
  const deleted = this.peerset.delete(peerId);
268
+ this.rateLimitedPeers.delete(peerId);
249
269
  this.computeTarget();
250
270
  return deleted;
251
271
  }
@@ -349,6 +369,8 @@ export class SyncChain {
349
369
  }
350
370
 
351
371
  throw e;
372
+ } finally {
373
+ this.clearRateLimitBackoffTimer();
352
374
  }
353
375
  }
354
376
 
@@ -372,6 +394,44 @@ export class SyncChain {
372
394
  }
373
395
  }
374
396
 
397
+ private scheduleRateLimitBackoffRetry(): void {
398
+ this.clearRateLimitBackoffTimer();
399
+
400
+ if (this.status !== SyncChainStatus.Syncing || this.rateLimitedPeers.size === 0) {
401
+ return;
402
+ }
403
+
404
+ const now = Date.now();
405
+ let retryAt: number | null = null;
406
+ for (const [peerId, rateLimitedUntil] of this.rateLimitedPeers.entries()) {
407
+ if (rateLimitedUntil <= now) {
408
+ this.rateLimitedPeers.delete(peerId);
409
+ continue;
410
+ }
411
+ retryAt = Math.min(retryAt ?? rateLimitedUntil, rateLimitedUntil);
412
+ }
413
+
414
+ if (retryAt === null) {
415
+ return;
416
+ }
417
+
418
+ this.rateLimitBackoffTimeout = setTimeout(
419
+ () => {
420
+ this.rateLimitBackoffTimeout = undefined;
421
+ this.triggerBatchDownloader();
422
+ this.scheduleRateLimitBackoffRetry();
423
+ },
424
+ Math.max(0, retryAt - now)
425
+ );
426
+ }
427
+
428
+ private clearRateLimitBackoffTimer(): void {
429
+ if (this.rateLimitBackoffTimeout !== undefined) {
430
+ clearTimeout(this.rateLimitBackoffTimeout);
431
+ this.rateLimitBackoffTimeout = undefined;
432
+ }
433
+ }
434
+
375
435
  /**
376
436
  * Attempts to request the next required batches from the peer pool if the chain is syncing.
377
437
  * It will exhaust the peer pool and left over batches until the batch buffer is reached.
@@ -381,8 +441,18 @@ export class SyncChain {
381
441
  return;
382
442
  }
383
443
 
444
+ const now = Date.now();
384
445
  const peersSyncInfo: PeerSyncInfo[] = [];
385
446
  for (const [peerId, target] of this.peerset.entries()) {
447
+ // Skip peers that are currently in rate-limit backoff
448
+ const rateLimitedUntil = this.rateLimitedPeers.get(peerId);
449
+ if (rateLimitedUntil !== undefined) {
450
+ if (now < rateLimitedUntil) {
451
+ continue;
452
+ }
453
+ this.rateLimitedPeers.delete(peerId);
454
+ }
455
+
386
456
  try {
387
457
  peersSyncInfo.push({...this.getConnectedPeerSyncMeta(peerId), target});
388
458
  } catch (e) {
@@ -456,7 +526,17 @@ export class SyncChain {
456
526
  return null;
457
527
  }
458
528
 
459
- const batch = new Batch(startEpoch, this.config, this.clock, this.custodyConfig);
529
+ const batch = new Batch(
530
+ startEpoch,
531
+ this.config,
532
+ this.clock,
533
+ this.custodyConfig,
534
+ this.isFirstBatch,
535
+ // `latestBid` is only meaningful for the first batch's parent-payload check
536
+ this.isFirstBatch ? this.latestBid : undefined,
537
+ this.target.slot
538
+ );
539
+ this.isFirstBatch = false;
460
540
  this.batches.set(startEpoch, batch);
461
541
  return batch;
462
542
  }
@@ -501,6 +581,8 @@ export class SyncChain {
501
581
  case DownloadByRangeErrorCode.OUT_OF_ORDER_BLOCKS:
502
582
  case DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS:
503
583
  case DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH:
584
+ case DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT:
585
+ case DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT:
504
586
  case BlobSidecarErrorCode.INCLUSION_PROOF_INVALID:
505
587
  case BlobSidecarErrorCode.INVALID_KZG_PROOF_BATCH:
506
588
  case DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT:
@@ -514,7 +596,16 @@ export class SyncChain {
514
596
  {id: this.logId, ...batch.getMetadata(), peer: prettyPrintPeerIdStr(peer.peerId)},
515
597
  res.err
516
598
  );
517
- batch.downloadingError(peer.peerId); // Throws after MAX_DOWNLOAD_ATTEMPTS
599
+ const rateLimitedUntilMs = getRateLimitedUntilMs(res.err);
600
+ if (rateLimitedUntilMs !== null) {
601
+ // Peer rate-limited us — don't count as a failed download attempt and mark peer for backoff
602
+ this.rateLimitedPeers.set(peer.peerId, rateLimitedUntilMs);
603
+ this.scheduleRateLimitBackoffRetry();
604
+ batch.downloadingRateLimited();
605
+ this.triggerBatchDownloader();
606
+ } else {
607
+ batch.downloadingError(peer.peerId); // Throws after MAX_DOWNLOAD_ATTEMPTS
608
+ }
518
609
  } else {
519
610
  this.logger.verbose("Batch download success", {
520
611
  id: this.logId,
@@ -534,7 +625,7 @@ export class SyncChain {
534
625
  this.metrics?.syncRange.downloadByRange.warn.inc({client: peer.client, code: warning.type.code});
535
626
  this.logger.debug(
536
627
  "Batch downloaded with warning",
537
- {id: this.logId, epoch: batch.startEpoch, ...logMeta, peer: prettyPrintPeerIdStr(peer.peerId)},
628
+ {id: this.logId, ...batch.getMetadata(), ...logMeta, peer: prettyPrintPeerIdStr(peer.peerId)},
538
629
  warning
539
630
  );
540
631
  }
@@ -560,10 +651,17 @@ export class SyncChain {
560
651
  // the flow will continue to call triggerBatchDownloader() below
561
652
  }
562
653
 
654
+ const blockSlots = downloadSuccessOutput.blocks.map((b) => b.slot);
655
+ const envelopeSlots = downloadSuccessOutput.payloadEnvelopes
656
+ ? Array.from(downloadSuccessOutput.payloadEnvelopes.keys())
657
+ : null;
658
+
563
659
  this.logger.debug(logMessage, {
564
660
  id: this.logId,
565
- epoch: batch.startEpoch,
661
+ ...batch.getMetadata(),
566
662
  ...logMeta,
663
+ blockSlots: prettyPrintIndices(blockSlots),
664
+ ...(envelopeSlots ? {envelopeSlots: prettyPrintIndices(envelopeSlots)} : {}),
567
665
  peer: prettyPrintPeerIdStr(peer.peerId),
568
666
  });
569
667
  }
@@ -586,13 +684,24 @@ export class SyncChain {
586
684
  * Sends `batch` to the processor. Note: batch may be empty
587
685
  */
588
686
  private async processBatch(batch: Batch): Promise<void> {
589
- const {blocks, payloadEnvelopes} = batch.startProcessing();
687
+ const {blocks, payloadEnvelopes, peers} = batch.startProcessing();
688
+
689
+ const logCtx = {
690
+ id: this.logId,
691
+ ...batch.getMetadata(),
692
+ blockCount: blocks.length,
693
+ blockSlots: prettyPrintIndices(blocks.map((b) => b.slot)),
694
+ ...(payloadEnvelopes ? {envelopeSlots: prettyPrintIndices(Array.from(payloadEnvelopes.keys()))} : {}),
695
+ peers: peers.map(prettyPrintPeerIdStr).join(","),
696
+ };
697
+ this.logger.verbose("Processing batch", logCtx);
590
698
 
591
699
  // wrapError ensures to never call both batch success() and batch error()
592
700
  const res = await wrapError(this.processChainSegment(blocks, payloadEnvelopes, this.syncType));
593
701
 
594
702
  if (!res.err) {
595
703
  batch.processingSuccess();
704
+ this.logger.verbose("Processed batch", {...logCtx, ...batch.getMetadata()});
596
705
 
597
706
  // If the processed batch is not empty, validate previous AwaitingValidation blocks.
598
707
  if (blocks.length > 0) {
@@ -602,7 +711,7 @@ export class SyncChain {
602
711
  // Potentially process next AwaitingProcessing batch
603
712
  this.triggerBatchProcessor();
604
713
  } else {
605
- this.logger.verbose("Batch process error", {id: this.logId, ...batch.getMetadata()}, res.err);
714
+ this.logger.verbose("Batch process error", logCtx, res.err);
606
715
  batch.processingError(res.err); // Throws after MAX_BATCH_PROCESSING_ATTEMPTS
607
716
 
608
717
  // At least one block was successfully verified and imported, so we can be sure all
@@ -1,16 +1,23 @@
1
1
  import {EventEmitter} from "node:events";
2
2
  import {StrictEventEmitter} from "strict-event-emitter-types";
3
3
  import {BeaconConfig} from "@lodestar/config";
4
- import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
+ import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "@lodestar/state-transition";
5
5
  import {Epoch, Status, fulu} from "@lodestar/types";
6
- import {Logger, toRootHex} from "@lodestar/utils";
6
+ import {Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
7
7
  import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
8
8
  import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
9
+ import {assertLinearChainSegment} from "../../chain/blocks/utils/chainSegment.js";
10
+ import {BlockError} from "../../chain/errors/index.js";
9
11
  import {IBeaconChain} from "../../chain/index.js";
10
12
  import {Metrics} from "../../metrics/index.js";
11
13
  import {INetwork} from "../../network/index.js";
12
14
  import {PeerIdStr} from "../../util/peerId.js";
13
- import {cacheByRangeResponses, downloadByRange} from "../utils/downloadByRange.js";
15
+ import {
16
+ DownloadByRangeError,
17
+ DownloadByRangeErrorCode,
18
+ cacheByRangeResponses,
19
+ downloadByRange,
20
+ } from "../utils/downloadByRange.js";
14
21
  import {RangeSyncType, getRangeSyncTarget, rangeSyncTypes} from "../utils/remoteSyncType.js";
15
22
  import {ChainTarget, SyncChain, SyncChainDebugState, SyncChainFns} from "./chain.js";
16
23
  import {updateChains} from "./utils/index.js";
@@ -206,14 +213,18 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
206
213
 
207
214
  private downloadByRange: SyncChainFns["downloadByRange"] = async (peer, batch) => {
208
215
  const batchBlocks = batch.getBlocks();
216
+ const requests = batch.getRequestsForPeer(peer);
217
+ const parentRoot = requests.parentPayloadRequest?.envelopeBlockRoot ?? requests.parentPayloadRequest?.blockRoot;
218
+ const parentPayloadCommitments = parentRoot ? batch.getParentPayloadCommitments(parentRoot) : undefined;
209
219
  const {result, warnings} = await downloadByRange({
210
220
  config: this.config,
211
221
  network: this.network,
212
222
  logger: this.logger,
213
223
  peerIdStr: peer.peerId,
214
224
  batchBlocks,
225
+ parentPayloadCommitments,
215
226
  peerDasMetrics: this.chain.metrics?.peerDas,
216
- ...batch.getRequestsForPeer(peer),
227
+ ...requests,
217
228
  });
218
229
  const {responses, payloadEnvelopes: downloadedPayloadEnvelopes} = result;
219
230
  const {blocks, payloadEnvelopes} = cacheByRangeResponses({
@@ -227,6 +238,49 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
227
238
  custodyConfig: this.chain.custodyConfig,
228
239
  seenTimestampSec: Date.now() / 1000,
229
240
  });
241
+
242
+ const segmentBlocks = blocks.filter((b) => b.hasBlock()).sort((a, b) => a.slot - b.slot);
243
+ const envelopeSlots = payloadEnvelopes
244
+ ? Array.from(payloadEnvelopes.entries())
245
+ .filter(([, pi]) => pi.hasPayloadEnvelope())
246
+ .map(([slot]) => slot)
247
+ .sort((a, b) => a - b)
248
+ : [];
249
+ this.logger.verbose("downloadByRange batch ready", {
250
+ peer: peer.peerId,
251
+ blockSlots: prettyPrintIndices(segmentBlocks.map((b) => b.slot)),
252
+ envelopeSlots: prettyPrintIndices(envelopeSlots),
253
+ ...batch.getMetadata(),
254
+ });
255
+
256
+ if (segmentBlocks.length > 1) {
257
+ try {
258
+ assertLinearChainSegment(this.config, segmentBlocks, payloadEnvelopes, null);
259
+ } catch (err) {
260
+ if (err instanceof BlockError) {
261
+ this.logger.debug(
262
+ "downloadByRange segment validation failed",
263
+ {
264
+ peer: peer.peerId,
265
+ reason: err.type.code,
266
+ slot: err.signedBlock.message.slot,
267
+ detail: JSON.stringify(err.type),
268
+ ...batch.getMetadata(),
269
+ },
270
+ err
271
+ );
272
+ // with this error, the peer will be penalized inside SyncChain
273
+ throw new DownloadByRangeError({
274
+ code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT,
275
+ slot: err.signedBlock.message.slot,
276
+ reason: err.type.code,
277
+ });
278
+ }
279
+
280
+ throw err;
281
+ }
282
+ }
283
+
230
284
  return {result: {blocks, payloadEnvelopes}, warnings};
231
285
  };
232
286
 
@@ -258,6 +312,14 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
258
312
  private addPeerOrCreateChain(startEpoch: Epoch, target: ChainTarget, peer: PeerIdStr, syncType: RangeSyncType): void {
259
313
  let syncChain = this.chains.get(syncType);
260
314
  if (!syncChain) {
315
+ // The first batch of a new sync chain may need to detect whether the parent block was an
316
+ // gloas "empty" block (no envelope produced). It does so by comparing the first
317
+ // downloaded block's `bid.parentBlockHash` against the head state's `latestExecutionPayloadBid.blockHash`.
318
+ const headState = this.chain.getHeadState();
319
+ const latestBid = isStatePostGloas(headState)
320
+ ? (headState as IBeaconStateViewGloas).latestExecutionPayloadBid
321
+ : undefined;
322
+
261
323
  syncChain = new SyncChain(
262
324
  startEpoch,
263
325
  target,
@@ -276,7 +338,8 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
276
338
  logger: this.logger,
277
339
  custodyConfig: this.chain.custodyConfig,
278
340
  metrics: this.metrics,
279
- }
341
+ },
342
+ latestBid
280
343
  );
281
344
  this.chains.set(syncType, syncChain);
282
345
 
package/src/sync/sync.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {SLOTS_PER_EPOCH} from "@lodestar/params";
2
2
  import {Slot} from "@lodestar/types";
3
- import {Logger} from "@lodestar/utils";
3
+ import {Logger, toRootHex} from "@lodestar/utils";
4
4
  import {IBeaconChain} from "../chain/index.js";
5
5
  import {GENESIS_SLOT} from "../constants/constants.js";
6
6
  import {ExecutionEngineState} from "../execution/index.js";
@@ -188,6 +188,18 @@ export class BeaconSync implements IBeaconSync {
188
188
  private addPeer = (data: NetworkEventData[NetworkEvent.peerConnected]): void => {
189
189
  const localStatus = this.chain.getStatus();
190
190
  const syncType = getPeerSyncType(localStatus, data.status, this.chain.forkChoice, this.slotImportTolerance);
191
+ this.logger.verbose("Peer sync type classified", {
192
+ peer: data.peer,
193
+ syncType,
194
+ localFinalizedEpoch: localStatus.finalizedEpoch,
195
+ localFinalizedRoot: toRootHex(localStatus.finalizedRoot),
196
+ localHeadSlot: localStatus.headSlot,
197
+ localHeadRoot: toRootHex(localStatus.headRoot),
198
+ remoteFinalizedEpoch: data.status.finalizedEpoch,
199
+ remoteFinalizedRoot: toRootHex(data.status.finalizedRoot),
200
+ remoteHeadSlot: data.status.headSlot,
201
+ remoteHeadRoot: toRootHex(data.status.headRoot),
202
+ });
191
203
 
192
204
  // For metrics only
193
205
  this.peerSyncType.set(data.peer, syncType);