@lodestar/beacon-node 1.43.0-dev.66d2c102e3 → 1.43.0-dev.6f485b1b61

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 (195) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +13 -3
  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 +45 -2
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/debug/index.d.ts.map +1 -1
  8. package/lib/api/impl/debug/index.js +0 -1
  9. package/lib/api/impl/debug/index.js.map +1 -1
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +68 -2
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
  14. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  15. package/lib/chain/blocks/blockInput/blockInput.js +4 -1
  16. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  17. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  18. package/lib/chain/blocks/importBlock.js +16 -31
  19. package/lib/chain/blocks/importBlock.js.map +1 -1
  20. package/lib/chain/blocks/importExecutionPayload.d.ts +9 -3
  21. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  22. package/lib/chain/blocks/importExecutionPayload.js +37 -15
  23. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  24. package/lib/chain/blocks/index.d.ts.map +1 -1
  25. package/lib/chain/blocks/index.js +35 -21
  26. package/lib/chain/blocks/index.js.map +1 -1
  27. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
  28. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
  29. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
  30. package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
  31. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
  32. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  33. package/lib/chain/blocks/types.d.ts +2 -1
  34. package/lib/chain/blocks/types.d.ts.map +1 -1
  35. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  36. package/lib/chain/blocks/utils/chainSegment.js +8 -0
  37. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  38. package/lib/chain/blocks/verifyBlock.d.ts +2 -1
  39. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  40. package/lib/chain/blocks/verifyBlock.js +30 -12
  41. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  42. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
  43. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  44. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
  45. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  46. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
  47. package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
  48. package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
  49. package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
  50. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  51. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  52. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
  53. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  54. package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
  55. package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
  56. package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
  57. package/lib/chain/chain.d.ts.map +1 -1
  58. package/lib/chain/chain.js +25 -8
  59. package/lib/chain/chain.js.map +1 -1
  60. package/lib/chain/emitter.d.ts +0 -11
  61. package/lib/chain/emitter.d.ts.map +1 -1
  62. package/lib/chain/emitter.js +0 -4
  63. package/lib/chain/emitter.js.map +1 -1
  64. package/lib/chain/errors/proposerPreferences.d.ts +8 -1
  65. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  66. package/lib/chain/errors/proposerPreferences.js +1 -0
  67. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  68. package/lib/chain/initState.d.ts.map +1 -1
  69. package/lib/chain/initState.js +6 -1
  70. package/lib/chain/initState.js.map +1 -1
  71. package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
  72. package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
  73. package/lib/chain/opPools/payloadAttestationPool.js +26 -4
  74. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  75. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  76. package/lib/chain/prepareNextSlot.js +16 -18
  77. package/lib/chain/prepareNextSlot.js.map +1 -1
  78. package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
  79. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  80. package/lib/chain/produceBlock/produceBlockBody.js +34 -22
  81. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  82. package/lib/chain/regen/queued.d.ts.map +1 -1
  83. package/lib/chain/regen/queued.js +1 -4
  84. package/lib/chain/regen/queued.js.map +1 -1
  85. package/lib/chain/regen/regen.d.ts.map +1 -1
  86. package/lib/chain/regen/regen.js +1 -4
  87. package/lib/chain/regen/regen.js.map +1 -1
  88. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +21 -11
  89. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  90. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
  91. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  92. package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
  93. package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
  94. package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
  95. package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
  96. package/lib/chain/validation/executionPayloadBid.js +11 -8
  97. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  98. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  99. package/lib/chain/validation/proposerPreferences.js +39 -17
  100. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  101. package/lib/network/gossip/topic.d.ts +2 -0
  102. package/lib/network/gossip/topic.d.ts.map +1 -1
  103. package/lib/network/interface.d.ts +1 -0
  104. package/lib/network/interface.d.ts.map +1 -1
  105. package/lib/network/network.d.ts +1 -0
  106. package/lib/network/network.d.ts.map +1 -1
  107. package/lib/network/network.js +5 -0
  108. package/lib/network/network.js.map +1 -1
  109. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  110. package/lib/network/processor/gossipHandlers.js +28 -10
  111. package/lib/network/processor/gossipHandlers.js.map +1 -1
  112. package/lib/network/processor/index.js +5 -5
  113. package/lib/network/processor/index.js.map +1 -1
  114. package/lib/node/nodejs.js +2 -2
  115. package/lib/node/nodejs.js.map +1 -1
  116. package/lib/node/notifier.js +1 -7
  117. package/lib/node/notifier.js.map +1 -1
  118. package/lib/sync/constants.d.ts +3 -1
  119. package/lib/sync/constants.d.ts.map +1 -1
  120. package/lib/sync/constants.js +3 -4
  121. package/lib/sync/constants.js.map +1 -1
  122. package/lib/sync/range/batch.d.ts +23 -3
  123. package/lib/sync/range/batch.d.ts.map +1 -1
  124. package/lib/sync/range/batch.js +191 -36
  125. package/lib/sync/range/batch.js.map +1 -1
  126. package/lib/sync/range/chain.d.ts +13 -2
  127. package/lib/sync/range/chain.d.ts.map +1 -1
  128. package/lib/sync/range/chain.js +61 -9
  129. package/lib/sync/range/chain.js.map +1 -1
  130. package/lib/sync/range/range.d.ts.map +1 -1
  131. package/lib/sync/range/range.js +14 -3
  132. package/lib/sync/range/range.js.map +1 -1
  133. package/lib/sync/sync.d.ts.map +1 -1
  134. package/lib/sync/sync.js +13 -0
  135. package/lib/sync/sync.js.map +1 -1
  136. package/lib/sync/unknownBlock.d.ts +7 -2
  137. package/lib/sync/unknownBlock.d.ts.map +1 -1
  138. package/lib/sync/unknownBlock.js +138 -57
  139. package/lib/sync/unknownBlock.js.map +1 -1
  140. package/lib/sync/utils/downloadByRange.d.ts +29 -8
  141. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  142. package/lib/sync/utils/downloadByRange.js +104 -42
  143. package/lib/sync/utils/downloadByRange.js.map +1 -1
  144. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  145. package/lib/sync/utils/downloadByRoot.js +10 -0
  146. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  147. package/lib/util/sszBytes.d.ts.map +1 -1
  148. package/lib/util/sszBytes.js +8 -6
  149. package/lib/util/sszBytes.js.map +1 -1
  150. package/package.json +15 -15
  151. package/src/api/impl/beacon/blocks/index.ts +16 -3
  152. package/src/api/impl/beacon/pool/index.ts +83 -1
  153. package/src/api/impl/debug/index.ts +0 -1
  154. package/src/api/impl/validator/index.ts +82 -1
  155. package/src/chain/blocks/blockInput/blockInput.ts +4 -1
  156. package/src/chain/blocks/importBlock.ts +16 -50
  157. package/src/chain/blocks/importExecutionPayload.ts +51 -20
  158. package/src/chain/blocks/index.ts +32 -15
  159. package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
  160. package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
  161. package/src/chain/blocks/types.ts +2 -1
  162. package/src/chain/blocks/utils/chainSegment.ts +8 -0
  163. package/src/chain/blocks/verifyBlock.ts +45 -13
  164. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
  165. package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
  166. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
  167. package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
  168. package/src/chain/chain.ts +29 -7
  169. package/src/chain/emitter.ts +0 -11
  170. package/src/chain/errors/proposerPreferences.ts +9 -1
  171. package/src/chain/initState.ts +9 -1
  172. package/src/chain/opPools/payloadAttestationPool.ts +29 -8
  173. package/src/chain/prepareNextSlot.ts +21 -29
  174. package/src/chain/produceBlock/produceBlockBody.ts +45 -27
  175. package/src/chain/regen/queued.ts +2 -7
  176. package/src/chain/regen/regen.ts +2 -7
  177. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
  178. package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
  179. package/src/chain/validation/executionPayloadBid.ts +11 -8
  180. package/src/chain/validation/proposerPreferences.ts +37 -18
  181. package/src/network/interface.ts +1 -0
  182. package/src/network/network.ts +11 -0
  183. package/src/network/processor/gossipHandlers.ts +38 -11
  184. package/src/network/processor/index.ts +5 -5
  185. package/src/node/nodejs.ts +2 -2
  186. package/src/node/notifier.ts +1 -8
  187. package/src/sync/constants.ts +4 -4
  188. package/src/sync/range/batch.ts +240 -42
  189. package/src/sync/range/chain.ts +77 -10
  190. package/src/sync/range/range.ts +16 -3
  191. package/src/sync/sync.ts +13 -1
  192. package/src/sync/unknownBlock.ts +170 -60
  193. package/src/sync/utils/downloadByRange.ts +166 -44
  194. package/src/sync/utils/downloadByRoot.ts +12 -0
  195. package/src/util/sszBytes.ts +8 -6
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);
@@ -63,6 +63,27 @@ enum FetchResult {
63
63
  FailureMaxAttempts = "failure_max_attempts",
64
64
  }
65
65
 
66
+ class UnknownBlockRateLimitedError extends Error {
67
+ constructor(message: string) {
68
+ super(message);
69
+ this.name = "UnknownBlockRateLimitedError";
70
+ }
71
+ }
72
+
73
+ function getRateLimitedUntilMs(e: unknown): number | null {
74
+ if (!(e instanceof RequestError)) {
75
+ return null;
76
+ }
77
+
78
+ switch (e.type.code) {
79
+ case RequestErrorCode.RESP_RATE_LIMITED:
80
+ case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
81
+ return e.type.rateLimitedUntilMs ?? null;
82
+ default:
83
+ return null;
84
+ }
85
+ }
86
+
66
87
  /**
67
88
  * BlockInputSync is a class that handles ReqResp to find blocks and data related to a specific blockRoot. The
68
89
  * blockRoot may have been found via object gossip, or the API. Gossip objects that can trigger a search are block,
@@ -106,6 +127,7 @@ export class BlockInputSync {
106
127
  private readonly maxPendingBlocks;
107
128
  private subscribedToNetworkEvents = false;
108
129
  private peerBalancer: UnknownBlockPeerBalancer;
130
+ private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
109
131
 
110
132
  constructor(
111
133
  private readonly config: ChainForkConfig,
@@ -145,7 +167,6 @@ export class BlockInputSync {
145
167
  this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
146
168
  this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
147
169
  this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
148
- this.chain.emitter.on(ChainEvent.envelopeUnknownBlock, this.onEnvelopeUnknownBlock);
149
170
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
150
171
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
151
172
  this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -156,12 +177,12 @@ export class BlockInputSync {
156
177
 
157
178
  unsubscribeFromNetwork(): void {
158
179
  this.logger.verbose("BlockInputSync disabled.");
180
+ this.clearRateLimitBackoffTimer();
159
181
  this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
160
182
  this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
161
183
  this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
162
184
  this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
163
185
  this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
164
- this.chain.emitter.off(ChainEvent.envelopeUnknownBlock, this.onEnvelopeUnknownBlock);
165
186
  this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
166
187
  this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
167
188
  this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
@@ -263,19 +284,6 @@ export class BlockInputSync {
263
284
  }
264
285
  };
265
286
 
266
- private onEnvelopeUnknownBlock = (data: ChainEventData[ChainEvent.envelopeUnknownBlock]): void => {
267
- try {
268
- const blockRootHex = toRootHex(data.envelope.message.beaconBlockRoot);
269
- this.addByRootHex(blockRootHex, data.peer);
270
- this.addByPayloadEnvelope(data.envelope, data.peer);
271
- this.triggerUnknownBlockSearch();
272
- this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_DATA});
273
- this.metrics?.blockInputSync.source.inc({source: data.source});
274
- } catch (e) {
275
- this.logger.debug("Error handling envelopeUnknownBlock event", {}, e as Error);
276
- }
277
- };
278
-
279
287
  private onBlockImported = (): void => {
280
288
  if (this.pendingPayloads.size > 0) {
281
289
  this.triggerUnknownBlockSearch();
@@ -381,41 +389,6 @@ export class BlockInputSync {
381
389
  return added;
382
390
  };
383
391
 
384
- private addByPayloadEnvelope = (envelope: gloas.SignedExecutionPayloadEnvelope, peerIdStr?: PeerIdStr): void => {
385
- const rootHex = toRootHex(envelope.message.beaconBlockRoot);
386
- const existingPendingPayload = this.pendingPayloads.get(rootHex);
387
- let pendingPayload = this.pendingPayloads.get(rootHex);
388
- if (!pendingPayload || !isPendingPayloadEnvelope(pendingPayload)) {
389
- pendingPayload = {
390
- status: PendingPayloadInputStatus.waitingForBlock,
391
- envelope,
392
- peerIdStrings: new Set(existingPendingPayload?.peerIdStrings ?? []),
393
- timeAddedSec: existingPendingPayload?.timeAddedSec ?? Date.now() / 1000,
394
- };
395
- this.pendingPayloads.set(rootHex, pendingPayload);
396
-
397
- this.logger.verbose("Added payload envelope to BlockInputSync.pendingPayloads", {
398
- slot: envelope.message.payload.slotNumber,
399
- root: rootHex,
400
- });
401
- } else {
402
- this.logger.debug("Overwriting pending payload envelope for root already waiting for block", {
403
- slot: envelope.message.payload.slotNumber,
404
- root: rootHex,
405
- });
406
- pendingPayload.envelope = envelope;
407
- }
408
-
409
- if (peerIdStr) {
410
- pendingPayload.peerIdStrings.add(peerIdStr);
411
- }
412
-
413
- const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
414
- if (prunedItemCount > 0) {
415
- this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
416
- }
417
- };
418
-
419
392
  private addByPayloadInput = (
420
393
  payloadInput: PayloadEnvelopeInput,
421
394
  peerIdStr?: PeerIdStr,
@@ -452,6 +425,7 @@ export class BlockInputSync {
452
425
  private onPeerDisconnected = (data: NetworkEventData[NetworkEvent.peerDisconnected]): void => {
453
426
  const peerId = data.peer;
454
427
  this.peerBalancer.onPeerDisconnected(peerId);
428
+ this.scheduleRateLimitBackoffRetry();
455
429
  };
456
430
 
457
431
  /**
@@ -569,7 +543,7 @@ export class BlockInputSync {
569
543
  */
570
544
  private triggerUnknownBlockSearch = (): void => {
571
545
  // Cheap early stop to prevent calling the network.getConnectedPeers()
572
- if (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0) {
546
+ if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
573
547
  return;
574
548
  }
575
549
 
@@ -657,6 +631,36 @@ export class BlockInputSync {
657
631
  }
658
632
  };
659
633
 
634
+ private scheduleRateLimitBackoffRetry(): void {
635
+ this.clearRateLimitBackoffTimer();
636
+
637
+ if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
638
+ return;
639
+ }
640
+
641
+ const now = Date.now();
642
+ const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
643
+ if (retryAt === null) {
644
+ return;
645
+ }
646
+
647
+ this.rateLimitBackoffTimeout = setTimeout(
648
+ () => {
649
+ this.rateLimitBackoffTimeout = undefined;
650
+ this.triggerUnknownBlockSearch();
651
+ this.scheduleRateLimitBackoffRetry();
652
+ },
653
+ Math.max(0, retryAt - now)
654
+ );
655
+ }
656
+
657
+ private clearRateLimitBackoffTimer(): void {
658
+ if (this.rateLimitBackoffTimeout !== undefined) {
659
+ clearTimeout(this.rateLimitBackoffTimeout);
660
+ this.rateLimitBackoffTimeout = undefined;
661
+ }
662
+ }
663
+
660
664
  private async downloadBlock(block: BlockInputSyncCacheItem): Promise<void> {
661
665
  if (block.status !== PendingBlockInputStatus.pending) {
662
666
  return;
@@ -728,6 +732,16 @@ export class BlockInputSync {
728
732
  this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
729
733
  }
730
734
  } else {
735
+ if (res.err instanceof UnknownBlockRateLimitedError) {
736
+ const pendingBlock = this.pendingBlocks.get(rootHex);
737
+ if (pendingBlock) {
738
+ pendingBlock.status = PendingBlockInputStatus.pending;
739
+ }
740
+ this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
741
+ this.scheduleRateLimitBackoffRetry();
742
+ return;
743
+ }
744
+
731
745
  this.metrics?.blockInputSync.downloadedBlocksError.inc();
732
746
  this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
733
747
  this.removeAndDownScoreAllDescendants(block);
@@ -1044,12 +1058,19 @@ export class BlockInputSync {
1044
1058
  let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
1045
1059
 
1046
1060
  let i = 0;
1061
+ let deferredByRateLimit = false;
1047
1062
  while (i++ < this.getMaxDownloadAttempts()) {
1048
1063
  const pendingColumns = payloadInput?.hasAllData()
1049
1064
  ? new Set<number>()
1050
1065
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
1051
1066
  const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1052
1067
  if (peerMeta === null) {
1068
+ if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1069
+ throw new UnknownBlockRateLimitedError(
1070
+ `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
1071
+ );
1072
+ }
1073
+
1053
1074
  throw Error(
1054
1075
  `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
1055
1076
  );
@@ -1126,7 +1147,12 @@ export class BlockInputSync {
1126
1147
  e as Error
1127
1148
  );
1128
1149
 
1129
- if (e instanceof RequestError) {
1150
+ const rateLimitedUntilMs = getRateLimitedUntilMs(e);
1151
+ if (rateLimitedUntilMs !== null) {
1152
+ deferredByRateLimit = true;
1153
+ this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
1154
+ this.scheduleRateLimitBackoffRetry();
1155
+ } else if (e instanceof RequestError) {
1130
1156
  switch (e.type.code) {
1131
1157
  case RequestErrorCode.REQUEST_RATE_LIMITED:
1132
1158
  case RequestErrorCode.REQUEST_TIMEOUT:
@@ -1143,6 +1169,12 @@ export class BlockInputSync {
1143
1169
  }
1144
1170
  }
1145
1171
 
1172
+ if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
1173
+ throw new UnknownBlockRateLimitedError(
1174
+ `Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
1175
+ );
1176
+ }
1177
+
1146
1178
  throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
1147
1179
  }
1148
1180
 
@@ -1226,6 +1258,7 @@ export class BlockInputSync {
1226
1258
  }
1227
1259
 
1228
1260
  let i = 0;
1261
+ let deferredByRateLimit = false;
1229
1262
  while (i++ < this.getMaxDownloadAttempts()) {
1230
1263
  const pendingColumns =
1231
1264
  isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
@@ -1233,6 +1266,12 @@ export class BlockInputSync {
1233
1266
  : defaultPendingColumns;
1234
1267
  const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
1235
1268
  if (peerMeta === null) {
1269
+ if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1270
+ throw new UnknownBlockRateLimitedError(
1271
+ `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
1272
+ );
1273
+ }
1274
+
1236
1275
  // no more peer with needed columns to try, throw error
1237
1276
  const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
1238
1277
  this.metrics?.blockInputSync.fetchTimeSec.observe(
@@ -1288,14 +1327,23 @@ export class BlockInputSync {
1288
1327
  } else if (e instanceof RequestError) {
1289
1328
  // should look into req_resp metrics in this case
1290
1329
  downloadByRootMetrics?.error.inc({code: "req_resp", client: peerClient});
1291
- switch (e.type.code) {
1292
- case RequestErrorCode.REQUEST_RATE_LIMITED:
1293
- case RequestErrorCode.REQUEST_TIMEOUT:
1294
- // do not exclude peer for these errors
1295
- break;
1296
- default:
1297
- excludedPeers.add(peerId);
1298
- break;
1330
+ const rateLimitedUntilMs = getRateLimitedUntilMs(e);
1331
+ if (rateLimitedUntilMs !== null) {
1332
+ deferredByRateLimit = true;
1333
+ this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
1334
+ this.scheduleRateLimitBackoffRetry();
1335
+ } else {
1336
+ switch (e.type.code) {
1337
+ case RequestErrorCode.REQUEST_RATE_LIMITED:
1338
+ case RequestErrorCode.RESP_RATE_LIMITED:
1339
+ case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
1340
+ case RequestErrorCode.REQUEST_TIMEOUT:
1341
+ // do not exclude peer for these errors
1342
+ break;
1343
+ default:
1344
+ excludedPeers.add(peerId);
1345
+ break;
1346
+ }
1299
1347
  }
1300
1348
  } else {
1301
1349
  // investigate if this happens
@@ -1323,6 +1371,10 @@ export class BlockInputSync {
1323
1371
 
1324
1372
  const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
1325
1373
 
1374
+ if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
1375
+ throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
1376
+ }
1377
+
1326
1378
  if (!isPendingBlockInput(cacheItem)) {
1327
1379
  throw Error(`${message} No block and no data was found.`);
1328
1380
  }
@@ -1454,10 +1506,12 @@ export class BlockInputSync {
1454
1506
  export class UnknownBlockPeerBalancer {
1455
1507
  readonly peersMeta: Map<PeerIdStr, PeerSyncMeta>;
1456
1508
  readonly activeRequests: Map<PeerIdStr, number>;
1509
+ readonly rateLimitedUntilByPeer: Map<PeerIdStr, number>;
1457
1510
 
1458
1511
  constructor() {
1459
1512
  this.peersMeta = new Map();
1460
1513
  this.activeRequests = new Map();
1514
+ this.rateLimitedUntilByPeer = new Map();
1461
1515
  }
1462
1516
 
1463
1517
  /** Trigger on each peer re-status */
@@ -1472,6 +1526,41 @@ export class UnknownBlockPeerBalancer {
1472
1526
  onPeerDisconnected(peerId: PeerIdStr): void {
1473
1527
  this.peersMeta.delete(peerId);
1474
1528
  this.activeRequests.delete(peerId);
1529
+ this.rateLimitedUntilByPeer.delete(peerId);
1530
+ }
1531
+
1532
+ onRateLimited(peerId: PeerIdStr, rateLimitedUntilMs: number): void {
1533
+ this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
1534
+ }
1535
+
1536
+ getNextRateLimitRetryAt(pendingColumns?: Set<number>, excludedPeers?: Set<PeerIdStr>): number | null {
1537
+ const now = Date.now();
1538
+ let retryAt: number | null = null;
1539
+
1540
+ for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
1541
+ if (rateLimitedUntil <= now) {
1542
+ this.rateLimitedUntilByPeer.delete(peerId);
1543
+ continue;
1544
+ }
1545
+
1546
+ if (excludedPeers?.has(peerId)) {
1547
+ continue;
1548
+ }
1549
+
1550
+ const syncMeta = this.peersMeta.get(peerId);
1551
+ if (syncMeta === undefined) {
1552
+ this.rateLimitedUntilByPeer.delete(peerId);
1553
+ continue;
1554
+ }
1555
+
1556
+ if (pendingColumns !== undefined && !this.peerHasPendingColumns(syncMeta, pendingColumns)) {
1557
+ continue;
1558
+ }
1559
+
1560
+ retryAt = Math.min(retryAt ?? rateLimitedUntil, rateLimitedUntil);
1561
+ }
1562
+
1563
+ return retryAt;
1475
1564
  }
1476
1565
 
1477
1566
  /**
@@ -1520,6 +1609,7 @@ export class UnknownBlockPeerBalancer {
1520
1609
  }
1521
1610
 
1522
1611
  private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
1612
+ const now = Date.now();
1523
1613
  let maxColumnCount = 0;
1524
1614
  const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
1525
1615
  for (const [peerId, syncMeta] of this.peersMeta.entries()) {
@@ -1528,12 +1618,24 @@ export class UnknownBlockPeerBalancer {
1528
1618
  continue;
1529
1619
  }
1530
1620
 
1621
+ const rateLimitedUntil = this.rateLimitedUntilByPeer.get(peerId);
1622
+ if (rateLimitedUntil !== undefined) {
1623
+ if (now < rateLimitedUntil) {
1624
+ continue;
1625
+ }
1626
+ this.rateLimitedUntilByPeer.delete(peerId);
1627
+ }
1628
+
1531
1629
  const activeRequests = this.activeRequests.get(peerId) ?? 0;
1532
1630
  if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
1533
1631
  // should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
1534
1632
  continue;
1535
1633
  }
1536
1634
 
1635
+ if (!this.peerHasPendingColumns(syncMeta, pendingDataColumns)) {
1636
+ continue;
1637
+ }
1638
+
1537
1639
  if (pendingDataColumns.size === 0) {
1538
1640
  considerPeers.push({peerId, columnCount: 0});
1539
1641
  continue;
@@ -1567,4 +1669,12 @@ export class UnknownBlockPeerBalancer {
1567
1669
 
1568
1670
  return eligiblePeers;
1569
1671
  }
1672
+
1673
+ private peerHasPendingColumns(syncMeta: PeerSyncMeta, pendingDataColumns: Set<number>): boolean {
1674
+ if (pendingDataColumns.size === 0) {
1675
+ return true;
1676
+ }
1677
+
1678
+ return syncMeta.custodyColumns.some((column) => pendingDataColumns.has(column));
1679
+ }
1570
1680
  }