@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.
- package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +10 -0
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +0 -1
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +2 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +4 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +16 -29
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +15 -6
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +29 -17
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +24 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/types.d.ts +2 -1
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +34 -23
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +5 -6
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +25 -10
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +0 -11
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +0 -4
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +8 -1
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/errors/proposerPreferences.js +1 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -1
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +6 -1
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/prepareNextSlot.js +1 -1
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -0
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +2 -7
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +1 -4
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +1 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +63 -19
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -8
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +39 -17
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +2 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +3 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +24 -7
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.js +5 -5
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +5 -0
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/interface.d.ts +1 -1
- package/lib/network/reqresp/interface.js +1 -1
- package/lib/network/reqresp/protocols.d.ts +1 -0
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +5 -0
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
- package/lib/network/reqresp/rateLimit.js +4 -0
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/score.d.ts.map +1 -1
- package/lib/network/reqresp/score.js +1 -0
- package/lib/network/reqresp/score.js.map +1 -1
- package/lib/network/reqresp/types.d.ts +3 -0
- package/lib/network/reqresp/types.d.ts.map +1 -1
- package/lib/network/reqresp/types.js +3 -0
- package/lib/network/reqresp/types.js.map +1 -1
- package/lib/node/nodejs.js +2 -2
- package/lib/node/nodejs.js.map +1 -1
- package/lib/node/notifier.js +1 -7
- package/lib/node/notifier.js.map +1 -1
- package/lib/sync/constants.d.ts +7 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +7 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +23 -3
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +191 -36
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +16 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +99 -8
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +54 -5
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +7 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +127 -57
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +36 -9
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +106 -42
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +10 -0
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/lib/sync/utils/rateLimit.d.ts +2 -0
- package/lib/sync/utils/rateLimit.d.ts.map +1 -0
- package/lib/sync/utils/rateLimit.js +15 -0
- package/lib/sync/utils/rateLimit.js.map +1 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +8 -6
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +16 -16
- package/src/api/impl/beacon/blocks/index.ts +13 -0
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/validator/index.ts +2 -1
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +16 -49
- package/src/chain/blocks/importExecutionPayload.ts +21 -11
- package/src/chain/blocks/index.ts +18 -9
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +32 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
- package/src/chain/blocks/types.ts +2 -1
- package/src/chain/blocks/utils/chainSegment.ts +35 -24
- package/src/chain/blocks/verifyBlock.ts +7 -5
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
- package/src/chain/chain.ts +29 -9
- package/src/chain/emitter.ts +0 -11
- package/src/chain/errors/proposerPreferences.ts +9 -1
- package/src/chain/initState.ts +9 -1
- package/src/chain/prepareNextSlot.ts +1 -1
- package/src/chain/produceBlock/produceBlockBody.ts +3 -7
- package/src/chain/regen/queued.ts +2 -7
- package/src/chain/regen/regen.ts +2 -7
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +78 -22
- package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
- package/src/chain/validation/executionPayloadBid.ts +11 -8
- package/src/chain/validation/proposerPreferences.ts +37 -18
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +12 -0
- package/src/network/processor/gossipHandlers.ts +30 -7
- package/src/network/processor/index.ts +5 -5
- package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
- package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
- package/src/network/reqresp/handlers/index.ts +5 -0
- package/src/network/reqresp/interface.ts +1 -1
- package/src/network/reqresp/protocols.ts +6 -0
- package/src/network/reqresp/rateLimit.ts +4 -0
- package/src/network/reqresp/score.ts +1 -0
- package/src/network/reqresp/types.ts +5 -0
- package/src/node/nodejs.ts +2 -2
- package/src/node/notifier.ts +1 -8
- package/src/sync/constants.ts +8 -4
- package/src/sync/range/batch.ts +240 -42
- package/src/sync/range/chain.ts +118 -9
- package/src/sync/range/range.ts +68 -5
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +157 -60
- package/src/sync/utils/downloadByRange.ts +174 -44
- package/src/sync/utils/downloadByRoot.ts +12 -0
- package/src/sync/utils/rateLimit.ts +16 -0
- package/src/util/sszBytes.ts +8 -6
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "./types.js";
|
|
43
43
|
import {DownloadByRootError, downloadByRoot} from "./utils/downloadByRoot.js";
|
|
44
44
|
import {getAllDescendantBlocks, getUnknownAndAncestorBlocks} from "./utils/pendingBlocksTree.js";
|
|
45
|
+
import {getRateLimitedUntilMs} from "./utils/rateLimit.js";
|
|
45
46
|
|
|
46
47
|
const MAX_ATTEMPTS_PER_BLOCK = 5;
|
|
47
48
|
const MAX_KNOWN_BAD_BLOCKS = 500;
|
|
@@ -63,6 +64,13 @@ enum FetchResult {
|
|
|
63
64
|
FailureMaxAttempts = "failure_max_attempts",
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
class UnknownBlockRateLimitedError extends Error {
|
|
68
|
+
constructor(message: string) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "UnknownBlockRateLimitedError";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
66
74
|
/**
|
|
67
75
|
* BlockInputSync is a class that handles ReqResp to find blocks and data related to a specific blockRoot. The
|
|
68
76
|
* blockRoot may have been found via object gossip, or the API. Gossip objects that can trigger a search are block,
|
|
@@ -106,6 +114,7 @@ export class BlockInputSync {
|
|
|
106
114
|
private readonly maxPendingBlocks;
|
|
107
115
|
private subscribedToNetworkEvents = false;
|
|
108
116
|
private peerBalancer: UnknownBlockPeerBalancer;
|
|
117
|
+
private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
|
|
109
118
|
|
|
110
119
|
constructor(
|
|
111
120
|
private readonly config: ChainForkConfig,
|
|
@@ -145,7 +154,6 @@ export class BlockInputSync {
|
|
|
145
154
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
146
155
|
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
147
156
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
148
|
-
this.chain.emitter.on(ChainEvent.envelopeUnknownBlock, this.onEnvelopeUnknownBlock);
|
|
149
157
|
this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
|
|
150
158
|
this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
151
159
|
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
@@ -156,12 +164,12 @@ export class BlockInputSync {
|
|
|
156
164
|
|
|
157
165
|
unsubscribeFromNetwork(): void {
|
|
158
166
|
this.logger.verbose("BlockInputSync disabled.");
|
|
167
|
+
this.clearRateLimitBackoffTimer();
|
|
159
168
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
160
169
|
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
161
170
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
162
171
|
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
163
172
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
164
|
-
this.chain.emitter.off(ChainEvent.envelopeUnknownBlock, this.onEnvelopeUnknownBlock);
|
|
165
173
|
this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
|
|
166
174
|
this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
167
175
|
this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
@@ -263,19 +271,6 @@ export class BlockInputSync {
|
|
|
263
271
|
}
|
|
264
272
|
};
|
|
265
273
|
|
|
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
274
|
private onBlockImported = (): void => {
|
|
280
275
|
if (this.pendingPayloads.size > 0) {
|
|
281
276
|
this.triggerUnknownBlockSearch();
|
|
@@ -381,41 +376,6 @@ export class BlockInputSync {
|
|
|
381
376
|
return added;
|
|
382
377
|
};
|
|
383
378
|
|
|
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
379
|
private addByPayloadInput = (
|
|
420
380
|
payloadInput: PayloadEnvelopeInput,
|
|
421
381
|
peerIdStr?: PeerIdStr,
|
|
@@ -452,6 +412,7 @@ export class BlockInputSync {
|
|
|
452
412
|
private onPeerDisconnected = (data: NetworkEventData[NetworkEvent.peerDisconnected]): void => {
|
|
453
413
|
const peerId = data.peer;
|
|
454
414
|
this.peerBalancer.onPeerDisconnected(peerId);
|
|
415
|
+
this.scheduleRateLimitBackoffRetry();
|
|
455
416
|
};
|
|
456
417
|
|
|
457
418
|
/**
|
|
@@ -569,7 +530,7 @@ export class BlockInputSync {
|
|
|
569
530
|
*/
|
|
570
531
|
private triggerUnknownBlockSearch = (): void => {
|
|
571
532
|
// Cheap early stop to prevent calling the network.getConnectedPeers()
|
|
572
|
-
if (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0) {
|
|
533
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
573
534
|
return;
|
|
574
535
|
}
|
|
575
536
|
|
|
@@ -657,6 +618,36 @@ export class BlockInputSync {
|
|
|
657
618
|
}
|
|
658
619
|
};
|
|
659
620
|
|
|
621
|
+
private scheduleRateLimitBackoffRetry(): void {
|
|
622
|
+
this.clearRateLimitBackoffTimer();
|
|
623
|
+
|
|
624
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const now = Date.now();
|
|
629
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
|
|
630
|
+
if (retryAt === null) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
this.rateLimitBackoffTimeout = setTimeout(
|
|
635
|
+
() => {
|
|
636
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
637
|
+
this.triggerUnknownBlockSearch();
|
|
638
|
+
this.scheduleRateLimitBackoffRetry();
|
|
639
|
+
},
|
|
640
|
+
Math.max(0, retryAt - now)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private clearRateLimitBackoffTimer(): void {
|
|
645
|
+
if (this.rateLimitBackoffTimeout !== undefined) {
|
|
646
|
+
clearTimeout(this.rateLimitBackoffTimeout);
|
|
647
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
660
651
|
private async downloadBlock(block: BlockInputSyncCacheItem): Promise<void> {
|
|
661
652
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
662
653
|
return;
|
|
@@ -728,6 +719,16 @@ export class BlockInputSync {
|
|
|
728
719
|
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
|
|
729
720
|
}
|
|
730
721
|
} else {
|
|
722
|
+
if (res.err instanceof UnknownBlockRateLimitedError) {
|
|
723
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
724
|
+
if (pendingBlock) {
|
|
725
|
+
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
726
|
+
}
|
|
727
|
+
this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
|
|
728
|
+
this.scheduleRateLimitBackoffRetry();
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
|
|
731
732
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
732
733
|
this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
|
|
733
734
|
this.removeAndDownScoreAllDescendants(block);
|
|
@@ -1044,12 +1045,19 @@ export class BlockInputSync {
|
|
|
1044
1045
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1045
1046
|
|
|
1046
1047
|
let i = 0;
|
|
1048
|
+
let deferredByRateLimit = false;
|
|
1047
1049
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
1048
1050
|
const pendingColumns = payloadInput?.hasAllData()
|
|
1049
1051
|
? new Set<number>()
|
|
1050
1052
|
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
1051
1053
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1052
1054
|
if (peerMeta === null) {
|
|
1055
|
+
if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
|
|
1056
|
+
throw new UnknownBlockRateLimitedError(
|
|
1057
|
+
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1053
1061
|
throw Error(
|
|
1054
1062
|
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1055
1063
|
);
|
|
@@ -1126,7 +1134,12 @@ export class BlockInputSync {
|
|
|
1126
1134
|
e as Error
|
|
1127
1135
|
);
|
|
1128
1136
|
|
|
1129
|
-
|
|
1137
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(e);
|
|
1138
|
+
if (rateLimitedUntilMs !== null) {
|
|
1139
|
+
deferredByRateLimit = true;
|
|
1140
|
+
this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
|
|
1141
|
+
this.scheduleRateLimitBackoffRetry();
|
|
1142
|
+
} else if (e instanceof RequestError) {
|
|
1130
1143
|
switch (e.type.code) {
|
|
1131
1144
|
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1132
1145
|
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
@@ -1143,6 +1156,12 @@ export class BlockInputSync {
|
|
|
1143
1156
|
}
|
|
1144
1157
|
}
|
|
1145
1158
|
|
|
1159
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1160
|
+
throw new UnknownBlockRateLimitedError(
|
|
1161
|
+
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1146
1165
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
1147
1166
|
}
|
|
1148
1167
|
|
|
@@ -1226,6 +1245,7 @@ export class BlockInputSync {
|
|
|
1226
1245
|
}
|
|
1227
1246
|
|
|
1228
1247
|
let i = 0;
|
|
1248
|
+
let deferredByRateLimit = false;
|
|
1229
1249
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
1230
1250
|
const pendingColumns =
|
|
1231
1251
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
@@ -1233,6 +1253,12 @@ export class BlockInputSync {
|
|
|
1233
1253
|
: defaultPendingColumns;
|
|
1234
1254
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1235
1255
|
if (peerMeta === null) {
|
|
1256
|
+
if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
|
|
1257
|
+
throw new UnknownBlockRateLimitedError(
|
|
1258
|
+
`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1236
1262
|
// no more peer with needed columns to try, throw error
|
|
1237
1263
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
1238
1264
|
this.metrics?.blockInputSync.fetchTimeSec.observe(
|
|
@@ -1288,14 +1314,23 @@ export class BlockInputSync {
|
|
|
1288
1314
|
} else if (e instanceof RequestError) {
|
|
1289
1315
|
// should look into req_resp metrics in this case
|
|
1290
1316
|
downloadByRootMetrics?.error.inc({code: "req_resp", client: peerClient});
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1317
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(e);
|
|
1318
|
+
if (rateLimitedUntilMs !== null) {
|
|
1319
|
+
deferredByRateLimit = true;
|
|
1320
|
+
this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
|
|
1321
|
+
this.scheduleRateLimitBackoffRetry();
|
|
1322
|
+
} else {
|
|
1323
|
+
switch (e.type.code) {
|
|
1324
|
+
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1325
|
+
case RequestErrorCode.RESP_RATE_LIMITED:
|
|
1326
|
+
case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
|
|
1327
|
+
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
1328
|
+
// do not exclude peer for these errors
|
|
1329
|
+
break;
|
|
1330
|
+
default:
|
|
1331
|
+
excludedPeers.add(peerId);
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1299
1334
|
}
|
|
1300
1335
|
} else {
|
|
1301
1336
|
// investigate if this happens
|
|
@@ -1323,6 +1358,10 @@ export class BlockInputSync {
|
|
|
1323
1358
|
|
|
1324
1359
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1325
1360
|
|
|
1361
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1362
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1326
1365
|
if (!isPendingBlockInput(cacheItem)) {
|
|
1327
1366
|
throw Error(`${message} No block and no data was found.`);
|
|
1328
1367
|
}
|
|
@@ -1454,10 +1493,12 @@ export class BlockInputSync {
|
|
|
1454
1493
|
export class UnknownBlockPeerBalancer {
|
|
1455
1494
|
readonly peersMeta: Map<PeerIdStr, PeerSyncMeta>;
|
|
1456
1495
|
readonly activeRequests: Map<PeerIdStr, number>;
|
|
1496
|
+
readonly rateLimitedUntilByPeer: Map<PeerIdStr, number>;
|
|
1457
1497
|
|
|
1458
1498
|
constructor() {
|
|
1459
1499
|
this.peersMeta = new Map();
|
|
1460
1500
|
this.activeRequests = new Map();
|
|
1501
|
+
this.rateLimitedUntilByPeer = new Map();
|
|
1461
1502
|
}
|
|
1462
1503
|
|
|
1463
1504
|
/** Trigger on each peer re-status */
|
|
@@ -1472,6 +1513,41 @@ export class UnknownBlockPeerBalancer {
|
|
|
1472
1513
|
onPeerDisconnected(peerId: PeerIdStr): void {
|
|
1473
1514
|
this.peersMeta.delete(peerId);
|
|
1474
1515
|
this.activeRequests.delete(peerId);
|
|
1516
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
onRateLimited(peerId: PeerIdStr, rateLimitedUntilMs: number): void {
|
|
1520
|
+
this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
getNextRateLimitRetryAt(pendingColumns?: Set<number>, excludedPeers?: Set<PeerIdStr>): number | null {
|
|
1524
|
+
const now = Date.now();
|
|
1525
|
+
let retryAt: number | null = null;
|
|
1526
|
+
|
|
1527
|
+
for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
|
|
1528
|
+
if (rateLimitedUntil <= now) {
|
|
1529
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (excludedPeers?.has(peerId)) {
|
|
1534
|
+
continue;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
const syncMeta = this.peersMeta.get(peerId);
|
|
1538
|
+
if (syncMeta === undefined) {
|
|
1539
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
if (pendingColumns !== undefined && !this.peerHasPendingColumns(syncMeta, pendingColumns)) {
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
retryAt = Math.min(retryAt ?? rateLimitedUntil, rateLimitedUntil);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
return retryAt;
|
|
1475
1551
|
}
|
|
1476
1552
|
|
|
1477
1553
|
/**
|
|
@@ -1520,6 +1596,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
1520
1596
|
}
|
|
1521
1597
|
|
|
1522
1598
|
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
|
|
1599
|
+
const now = Date.now();
|
|
1523
1600
|
let maxColumnCount = 0;
|
|
1524
1601
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
1525
1602
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -1528,12 +1605,24 @@ export class UnknownBlockPeerBalancer {
|
|
|
1528
1605
|
continue;
|
|
1529
1606
|
}
|
|
1530
1607
|
|
|
1608
|
+
const rateLimitedUntil = this.rateLimitedUntilByPeer.get(peerId);
|
|
1609
|
+
if (rateLimitedUntil !== undefined) {
|
|
1610
|
+
if (now < rateLimitedUntil) {
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1531
1616
|
const activeRequests = this.activeRequests.get(peerId) ?? 0;
|
|
1532
1617
|
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
1533
1618
|
// should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
|
|
1534
1619
|
continue;
|
|
1535
1620
|
}
|
|
1536
1621
|
|
|
1622
|
+
if (!this.peerHasPendingColumns(syncMeta, pendingDataColumns)) {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1537
1626
|
if (pendingDataColumns.size === 0) {
|
|
1538
1627
|
considerPeers.push({peerId, columnCount: 0});
|
|
1539
1628
|
continue;
|
|
@@ -1567,4 +1656,12 @@ export class UnknownBlockPeerBalancer {
|
|
|
1567
1656
|
|
|
1568
1657
|
return eligiblePeers;
|
|
1569
1658
|
}
|
|
1659
|
+
|
|
1660
|
+
private peerHasPendingColumns(syncMeta: PeerSyncMeta, pendingDataColumns: Set<number>): boolean {
|
|
1661
|
+
if (pendingDataColumns.size === 0) {
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
return syncMeta.custodyColumns.some((column) => pendingDataColumns.has(column));
|
|
1666
|
+
}
|
|
1570
1667
|
}
|