@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.
- package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +13 -3
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +45 -2
- package/lib/api/impl/beacon/pool/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 +68 -2
- 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 -31
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +9 -3
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +37 -15
- 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 +35 -21
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -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.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +8 -0
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +2 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +30 -12
- 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/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +25 -8
- 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/opPools/payloadAttestationPool.d.ts +3 -2
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +26 -4
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +16 -18
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +34 -22
- 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 +21 -11
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
- 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 +5 -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 +28 -10
- 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/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 +3 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +3 -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 +13 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +61 -9
- 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 +14 -3
- 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 +138 -57
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +29 -8
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +104 -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/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 +15 -15
- package/src/api/impl/beacon/blocks/index.ts +16 -3
- package/src/api/impl/beacon/pool/index.ts +83 -1
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/validator/index.ts +82 -1
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +16 -50
- package/src/chain/blocks/importExecutionPayload.ts +51 -20
- package/src/chain/blocks/index.ts +32 -15
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
- package/src/chain/blocks/types.ts +2 -1
- package/src/chain/blocks/utils/chainSegment.ts +8 -0
- package/src/chain/blocks/verifyBlock.ts +45 -13
- 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/blocks/verifyPayloadsDataAvailability.ts +7 -4
- package/src/chain/chain.ts +29 -7
- 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/opPools/payloadAttestationPool.ts +29 -8
- package/src/chain/prepareNextSlot.ts +21 -29
- package/src/chain/produceBlock/produceBlockBody.ts +45 -27
- package/src/chain/regen/queued.ts +2 -7
- package/src/chain/regen/regen.ts +2 -7
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
- 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 +11 -0
- package/src/network/processor/gossipHandlers.ts +38 -11
- package/src/network/processor/index.ts +5 -5
- package/src/node/nodejs.ts +2 -2
- package/src/node/notifier.ts +1 -8
- package/src/sync/constants.ts +4 -4
- package/src/sync/range/batch.ts +240 -42
- package/src/sync/range/chain.ts +77 -10
- package/src/sync/range/range.ts +16 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +170 -60
- package/src/sync/utils/downloadByRange.ts +166 -44
- package/src/sync/utils/downloadByRoot.ts +12 -0
- 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);
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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
|
}
|