@lodestar/beacon-node 1.43.0-dev.a45ba75824 → 1.43.0-dev.a58ad80359
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/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 +14 -18
- 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 +23 -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.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.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/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 +51 -17
- 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/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +14 -0
- package/lib/network/processor/gossipHandlers.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 +12 -3
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +163 -33
- 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 +38 -4
- 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 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +138 -10
- 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 +87 -31
- package/lib/sync/utils/downloadByRange.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 +16 -16
- 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 +14 -21
- package/src/chain/blocks/importExecutionPayload.ts +21 -11
- package/src/chain/blocks/index.ts +18 -9
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +31 -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 +8 -0
- 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/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 +66 -20
- 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/processor/gossipHandlers.ts +18 -0
- 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 +187 -38
- package/src/sync/range/chain.ts +52 -5
- package/src/sync/range/range.ts +16 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +170 -10
- package/src/sync/utils/downloadByRange.ts +149 -33
- package/src/util/sszBytes.ts +8 -6
package/src/sync/range/range.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {EventEmitter} from "node:events";
|
|
2
2
|
import {StrictEventEmitter} from "strict-event-emitter-types";
|
|
3
3
|
import {BeaconConfig} from "@lodestar/config";
|
|
4
|
-
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
|
+
import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "@lodestar/state-transition";
|
|
5
5
|
import {Epoch, Status, fulu} from "@lodestar/types";
|
|
6
6
|
import {Logger, toRootHex} from "@lodestar/utils";
|
|
7
7
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
@@ -206,14 +206,18 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
206
206
|
|
|
207
207
|
private downloadByRange: SyncChainFns["downloadByRange"] = async (peer, batch) => {
|
|
208
208
|
const batchBlocks = batch.getBlocks();
|
|
209
|
+
const requests = batch.getRequestsForPeer(peer);
|
|
210
|
+
const parentRoot = requests.parentPayloadRequest?.envelopeBlockRoot ?? requests.parentPayloadRequest?.blockRoot;
|
|
211
|
+
const parentPayloadCommitments = parentRoot ? batch.getParentPayloadCommitments(parentRoot) : undefined;
|
|
209
212
|
const {result, warnings} = await downloadByRange({
|
|
210
213
|
config: this.config,
|
|
211
214
|
network: this.network,
|
|
212
215
|
logger: this.logger,
|
|
213
216
|
peerIdStr: peer.peerId,
|
|
214
217
|
batchBlocks,
|
|
218
|
+
parentPayloadCommitments,
|
|
215
219
|
peerDasMetrics: this.chain.metrics?.peerDas,
|
|
216
|
-
...
|
|
220
|
+
...requests,
|
|
217
221
|
});
|
|
218
222
|
const {responses, payloadEnvelopes: downloadedPayloadEnvelopes} = result;
|
|
219
223
|
const {blocks, payloadEnvelopes} = cacheByRangeResponses({
|
|
@@ -258,6 +262,14 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
258
262
|
private addPeerOrCreateChain(startEpoch: Epoch, target: ChainTarget, peer: PeerIdStr, syncType: RangeSyncType): void {
|
|
259
263
|
let syncChain = this.chains.get(syncType);
|
|
260
264
|
if (!syncChain) {
|
|
265
|
+
// The first batch of a new sync chain may need to detect whether the parent block was an
|
|
266
|
+
// gloas "empty" block (no envelope produced). It does so by comparing the first
|
|
267
|
+
// downloaded block's `bid.parentBlockHash` against the head state's `latestExecutionPayloadBid.blockHash`.
|
|
268
|
+
const headState = this.chain.getHeadState();
|
|
269
|
+
const latestBid = isStatePostGloas(headState)
|
|
270
|
+
? (headState as IBeaconStateViewGloas).latestExecutionPayloadBid
|
|
271
|
+
: undefined;
|
|
272
|
+
|
|
261
273
|
syncChain = new SyncChain(
|
|
262
274
|
startEpoch,
|
|
263
275
|
target,
|
|
@@ -276,7 +288,8 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
276
288
|
logger: this.logger,
|
|
277
289
|
custodyConfig: this.chain.custodyConfig,
|
|
278
290
|
metrics: this.metrics,
|
|
279
|
-
}
|
|
291
|
+
},
|
|
292
|
+
latestBid
|
|
280
293
|
);
|
|
281
294
|
this.chains.set(syncType, syncChain);
|
|
282
295
|
|
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,
|
|
@@ -155,6 +177,7 @@ export class BlockInputSync {
|
|
|
155
177
|
|
|
156
178
|
unsubscribeFromNetwork(): void {
|
|
157
179
|
this.logger.verbose("BlockInputSync disabled.");
|
|
180
|
+
this.clearRateLimitBackoffTimer();
|
|
158
181
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
159
182
|
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
160
183
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
@@ -402,6 +425,7 @@ export class BlockInputSync {
|
|
|
402
425
|
private onPeerDisconnected = (data: NetworkEventData[NetworkEvent.peerDisconnected]): void => {
|
|
403
426
|
const peerId = data.peer;
|
|
404
427
|
this.peerBalancer.onPeerDisconnected(peerId);
|
|
428
|
+
this.scheduleRateLimitBackoffRetry();
|
|
405
429
|
};
|
|
406
430
|
|
|
407
431
|
/**
|
|
@@ -519,7 +543,7 @@ export class BlockInputSync {
|
|
|
519
543
|
*/
|
|
520
544
|
private triggerUnknownBlockSearch = (): void => {
|
|
521
545
|
// Cheap early stop to prevent calling the network.getConnectedPeers()
|
|
522
|
-
if (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0) {
|
|
546
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
523
547
|
return;
|
|
524
548
|
}
|
|
525
549
|
|
|
@@ -607,6 +631,36 @@ export class BlockInputSync {
|
|
|
607
631
|
}
|
|
608
632
|
};
|
|
609
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
|
+
|
|
610
664
|
private async downloadBlock(block: BlockInputSyncCacheItem): Promise<void> {
|
|
611
665
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
612
666
|
return;
|
|
@@ -678,6 +732,16 @@ export class BlockInputSync {
|
|
|
678
732
|
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
|
|
679
733
|
}
|
|
680
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
|
+
|
|
681
745
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
682
746
|
this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
|
|
683
747
|
this.removeAndDownScoreAllDescendants(block);
|
|
@@ -994,12 +1058,19 @@ export class BlockInputSync {
|
|
|
994
1058
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
995
1059
|
|
|
996
1060
|
let i = 0;
|
|
1061
|
+
let deferredByRateLimit = false;
|
|
997
1062
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
998
1063
|
const pendingColumns = payloadInput?.hasAllData()
|
|
999
1064
|
? new Set<number>()
|
|
1000
1065
|
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
1001
1066
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1002
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
|
+
|
|
1003
1074
|
throw Error(
|
|
1004
1075
|
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1005
1076
|
);
|
|
@@ -1076,7 +1147,12 @@ export class BlockInputSync {
|
|
|
1076
1147
|
e as Error
|
|
1077
1148
|
);
|
|
1078
1149
|
|
|
1079
|
-
|
|
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) {
|
|
1080
1156
|
switch (e.type.code) {
|
|
1081
1157
|
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1082
1158
|
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
@@ -1093,6 +1169,12 @@ export class BlockInputSync {
|
|
|
1093
1169
|
}
|
|
1094
1170
|
}
|
|
1095
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
|
+
|
|
1096
1178
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
1097
1179
|
}
|
|
1098
1180
|
|
|
@@ -1176,6 +1258,7 @@ export class BlockInputSync {
|
|
|
1176
1258
|
}
|
|
1177
1259
|
|
|
1178
1260
|
let i = 0;
|
|
1261
|
+
let deferredByRateLimit = false;
|
|
1179
1262
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
1180
1263
|
const pendingColumns =
|
|
1181
1264
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
@@ -1183,6 +1266,12 @@ export class BlockInputSync {
|
|
|
1183
1266
|
: defaultPendingColumns;
|
|
1184
1267
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
1185
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
|
+
|
|
1186
1275
|
// no more peer with needed columns to try, throw error
|
|
1187
1276
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
1188
1277
|
this.metrics?.blockInputSync.fetchTimeSec.observe(
|
|
@@ -1238,14 +1327,23 @@ export class BlockInputSync {
|
|
|
1238
1327
|
} else if (e instanceof RequestError) {
|
|
1239
1328
|
// should look into req_resp metrics in this case
|
|
1240
1329
|
downloadByRootMetrics?.error.inc({code: "req_resp", client: peerClient});
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
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
|
+
}
|
|
1249
1347
|
}
|
|
1250
1348
|
} else {
|
|
1251
1349
|
// investigate if this happens
|
|
@@ -1273,6 +1371,10 @@ export class BlockInputSync {
|
|
|
1273
1371
|
|
|
1274
1372
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1275
1373
|
|
|
1374
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1375
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1276
1378
|
if (!isPendingBlockInput(cacheItem)) {
|
|
1277
1379
|
throw Error(`${message} No block and no data was found.`);
|
|
1278
1380
|
}
|
|
@@ -1404,10 +1506,12 @@ export class BlockInputSync {
|
|
|
1404
1506
|
export class UnknownBlockPeerBalancer {
|
|
1405
1507
|
readonly peersMeta: Map<PeerIdStr, PeerSyncMeta>;
|
|
1406
1508
|
readonly activeRequests: Map<PeerIdStr, number>;
|
|
1509
|
+
readonly rateLimitedUntilByPeer: Map<PeerIdStr, number>;
|
|
1407
1510
|
|
|
1408
1511
|
constructor() {
|
|
1409
1512
|
this.peersMeta = new Map();
|
|
1410
1513
|
this.activeRequests = new Map();
|
|
1514
|
+
this.rateLimitedUntilByPeer = new Map();
|
|
1411
1515
|
}
|
|
1412
1516
|
|
|
1413
1517
|
/** Trigger on each peer re-status */
|
|
@@ -1422,6 +1526,41 @@ export class UnknownBlockPeerBalancer {
|
|
|
1422
1526
|
onPeerDisconnected(peerId: PeerIdStr): void {
|
|
1423
1527
|
this.peersMeta.delete(peerId);
|
|
1424
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;
|
|
1425
1564
|
}
|
|
1426
1565
|
|
|
1427
1566
|
/**
|
|
@@ -1470,6 +1609,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
1470
1609
|
}
|
|
1471
1610
|
|
|
1472
1611
|
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
|
|
1612
|
+
const now = Date.now();
|
|
1473
1613
|
let maxColumnCount = 0;
|
|
1474
1614
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
1475
1615
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -1478,12 +1618,24 @@ export class UnknownBlockPeerBalancer {
|
|
|
1478
1618
|
continue;
|
|
1479
1619
|
}
|
|
1480
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
|
+
|
|
1481
1629
|
const activeRequests = this.activeRequests.get(peerId) ?? 0;
|
|
1482
1630
|
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
1483
1631
|
// should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
|
|
1484
1632
|
continue;
|
|
1485
1633
|
}
|
|
1486
1634
|
|
|
1635
|
+
if (!this.peerHasPendingColumns(syncMeta, pendingDataColumns)) {
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1487
1639
|
if (pendingDataColumns.size === 0) {
|
|
1488
1640
|
considerPeers.push({peerId, columnCount: 0});
|
|
1489
1641
|
continue;
|
|
@@ -1517,4 +1669,12 @@ export class UnknownBlockPeerBalancer {
|
|
|
1517
1669
|
|
|
1518
1670
|
return eligiblePeers;
|
|
1519
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
|
+
}
|
|
1520
1680
|
}
|