@lodestar/beacon-node 1.43.0-dev.b05ea98d04 → 1.43.0-dev.b0e1143d06
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/chain/blocks/importExecutionPayload.d.ts +4 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +12 -1
- 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 +6 -7
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +8 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +19 -0
- 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/utils/chainSegment.d.ts +1 -3
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +29 -26
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +5 -4
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +15 -1
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +7 -6
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +21 -5
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +749 -2
- 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 +31 -1
- package/lib/network/processor/gossipHandlers.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/sync/constants.d.ts +5 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +5 -1
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +27 -6
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +149 -26
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +10 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +55 -7
- 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/range/utils/peerBalancer.d.ts +2 -1
- package/lib/sync/range/utils/peerBalancer.d.ts.map +1 -1
- package/lib/sync/range/utils/peerBalancer.js +8 -4
- package/lib/sync/range/utils/peerBalancer.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 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +127 -12
- 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 +87 -38
- package/lib/sync/utils/downloadByRange.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/package.json +16 -16
- package/src/chain/blocks/importExecutionPayload.ts +18 -6
- package/src/chain/blocks/index.ts +5 -7
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -1
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
- package/src/chain/blocks/utils/chainSegment.ts +30 -27
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +7 -4
- package/src/chain/chain.ts +17 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +23 -6
- package/src/metrics/metrics/lodestar.ts +6 -0
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +12 -0
- package/src/network/processor/gossipHandlers.ts +35 -1
- 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/sync/constants.ts +5 -1
- package/src/sync/range/batch.ts +188 -30
- package/src/sync/range/chain.ts +72 -13
- package/src/sync/range/range.ts +68 -5
- package/src/sync/range/utils/peerBalancer.ts +9 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +157 -12
- package/src/sync/utils/downloadByRange.ts +153 -39
- package/src/sync/utils/rateLimit.ts +16 -0
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
|
@@ -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,
|
|
@@ -155,6 +164,7 @@ export class BlockInputSync {
|
|
|
155
164
|
|
|
156
165
|
unsubscribeFromNetwork(): void {
|
|
157
166
|
this.logger.verbose("BlockInputSync disabled.");
|
|
167
|
+
this.clearRateLimitBackoffTimer();
|
|
158
168
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
159
169
|
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
160
170
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
@@ -402,6 +412,7 @@ export class BlockInputSync {
|
|
|
402
412
|
private onPeerDisconnected = (data: NetworkEventData[NetworkEvent.peerDisconnected]): void => {
|
|
403
413
|
const peerId = data.peer;
|
|
404
414
|
this.peerBalancer.onPeerDisconnected(peerId);
|
|
415
|
+
this.scheduleRateLimitBackoffRetry();
|
|
405
416
|
};
|
|
406
417
|
|
|
407
418
|
/**
|
|
@@ -519,7 +530,7 @@ export class BlockInputSync {
|
|
|
519
530
|
*/
|
|
520
531
|
private triggerUnknownBlockSearch = (): void => {
|
|
521
532
|
// Cheap early stop to prevent calling the network.getConnectedPeers()
|
|
522
|
-
if (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0) {
|
|
533
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
523
534
|
return;
|
|
524
535
|
}
|
|
525
536
|
|
|
@@ -607,6 +618,36 @@ export class BlockInputSync {
|
|
|
607
618
|
}
|
|
608
619
|
};
|
|
609
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
|
+
|
|
610
651
|
private async downloadBlock(block: BlockInputSyncCacheItem): Promise<void> {
|
|
611
652
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
612
653
|
return;
|
|
@@ -678,6 +719,16 @@ export class BlockInputSync {
|
|
|
678
719
|
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
|
|
679
720
|
}
|
|
680
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
|
+
|
|
681
732
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
682
733
|
this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
|
|
683
734
|
this.removeAndDownScoreAllDescendants(block);
|
|
@@ -994,12 +1045,19 @@ export class BlockInputSync {
|
|
|
994
1045
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
995
1046
|
|
|
996
1047
|
let i = 0;
|
|
1048
|
+
let deferredByRateLimit = false;
|
|
997
1049
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
998
1050
|
const pendingColumns = payloadInput?.hasAllData()
|
|
999
1051
|
? new Set<number>()
|
|
1000
1052
|
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
1001
1053
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1002
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
|
+
|
|
1003
1061
|
throw Error(
|
|
1004
1062
|
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1005
1063
|
);
|
|
@@ -1076,7 +1134,12 @@ export class BlockInputSync {
|
|
|
1076
1134
|
e as Error
|
|
1077
1135
|
);
|
|
1078
1136
|
|
|
1079
|
-
|
|
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) {
|
|
1080
1143
|
switch (e.type.code) {
|
|
1081
1144
|
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1082
1145
|
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
@@ -1093,6 +1156,12 @@ export class BlockInputSync {
|
|
|
1093
1156
|
}
|
|
1094
1157
|
}
|
|
1095
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
|
+
|
|
1096
1165
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
1097
1166
|
}
|
|
1098
1167
|
|
|
@@ -1176,6 +1245,7 @@ export class BlockInputSync {
|
|
|
1176
1245
|
}
|
|
1177
1246
|
|
|
1178
1247
|
let i = 0;
|
|
1248
|
+
let deferredByRateLimit = false;
|
|
1179
1249
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
1180
1250
|
const pendingColumns =
|
|
1181
1251
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
@@ -1183,6 +1253,12 @@ export class BlockInputSync {
|
|
|
1183
1253
|
: defaultPendingColumns;
|
|
1184
1254
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1185
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
|
+
|
|
1186
1262
|
// no more peer with needed columns to try, throw error
|
|
1187
1263
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
1188
1264
|
this.metrics?.blockInputSync.fetchTimeSec.observe(
|
|
@@ -1238,16 +1314,23 @@ export class BlockInputSync {
|
|
|
1238
1314
|
} else if (e instanceof RequestError) {
|
|
1239
1315
|
// should look into req_resp metrics in this case
|
|
1240
1316
|
downloadByRootMetrics?.error.inc({code: "req_resp", client: peerClient});
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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
|
+
}
|
|
1251
1334
|
}
|
|
1252
1335
|
} else {
|
|
1253
1336
|
// investigate if this happens
|
|
@@ -1275,6 +1358,10 @@ export class BlockInputSync {
|
|
|
1275
1358
|
|
|
1276
1359
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1277
1360
|
|
|
1361
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1362
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1278
1365
|
if (!isPendingBlockInput(cacheItem)) {
|
|
1279
1366
|
throw Error(`${message} No block and no data was found.`);
|
|
1280
1367
|
}
|
|
@@ -1406,10 +1493,12 @@ export class BlockInputSync {
|
|
|
1406
1493
|
export class UnknownBlockPeerBalancer {
|
|
1407
1494
|
readonly peersMeta: Map<PeerIdStr, PeerSyncMeta>;
|
|
1408
1495
|
readonly activeRequests: Map<PeerIdStr, number>;
|
|
1496
|
+
readonly rateLimitedUntilByPeer: Map<PeerIdStr, number>;
|
|
1409
1497
|
|
|
1410
1498
|
constructor() {
|
|
1411
1499
|
this.peersMeta = new Map();
|
|
1412
1500
|
this.activeRequests = new Map();
|
|
1501
|
+
this.rateLimitedUntilByPeer = new Map();
|
|
1413
1502
|
}
|
|
1414
1503
|
|
|
1415
1504
|
/** Trigger on each peer re-status */
|
|
@@ -1424,6 +1513,41 @@ export class UnknownBlockPeerBalancer {
|
|
|
1424
1513
|
onPeerDisconnected(peerId: PeerIdStr): void {
|
|
1425
1514
|
this.peersMeta.delete(peerId);
|
|
1426
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;
|
|
1427
1551
|
}
|
|
1428
1552
|
|
|
1429
1553
|
/**
|
|
@@ -1472,6 +1596,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
1472
1596
|
}
|
|
1473
1597
|
|
|
1474
1598
|
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
|
|
1599
|
+
const now = Date.now();
|
|
1475
1600
|
let maxColumnCount = 0;
|
|
1476
1601
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
1477
1602
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -1480,12 +1605,24 @@ export class UnknownBlockPeerBalancer {
|
|
|
1480
1605
|
continue;
|
|
1481
1606
|
}
|
|
1482
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
|
+
|
|
1483
1616
|
const activeRequests = this.activeRequests.get(peerId) ?? 0;
|
|
1484
1617
|
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
1485
1618
|
// should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
|
|
1486
1619
|
continue;
|
|
1487
1620
|
}
|
|
1488
1621
|
|
|
1622
|
+
if (!this.peerHasPendingColumns(syncMeta, pendingDataColumns)) {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1489
1626
|
if (pendingDataColumns.size === 0) {
|
|
1490
1627
|
considerPeers.push({peerId, columnCount: 0});
|
|
1491
1628
|
continue;
|
|
@@ -1519,4 +1656,12 @@ export class UnknownBlockPeerBalancer {
|
|
|
1519
1656
|
|
|
1520
1657
|
return eligiblePeers;
|
|
1521
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
|
+
}
|
|
1522
1667
|
}
|