@lodestar/beacon-node 1.49.0-dev.4d35c8b82e → 1.49.0-dev.535f5dd45f
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.js +2 -2
- 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 +4 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +9 -7
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +1 -2
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +0 -5
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.js +5 -6
- package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +14 -3
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +13 -0
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.js +2 -2
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/emitter.d.ts +10 -0
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +4 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/attesterSlashingError.d.ts +4 -1
- package/lib/chain/errors/attesterSlashingError.d.ts.map +1 -1
- package/lib/chain/errors/attesterSlashingError.js +1 -0
- package/lib/chain/errors/attesterSlashingError.js.map +1 -1
- package/lib/chain/errors/payloadAttestation.d.ts +4 -0
- package/lib/chain/errors/payloadAttestation.d.ts.map +1 -1
- package/lib/chain/errors/payloadAttestation.js +1 -0
- package/lib/chain/errors/payloadAttestation.js.map +1 -1
- package/lib/chain/errors/proposerSlashingError.d.ts +4 -1
- package/lib/chain/errors/proposerSlashingError.d.ts.map +1 -1
- package/lib/chain/errors/proposerSlashingError.js +1 -0
- package/lib/chain/errors/proposerSlashingError.js.map +1 -1
- package/lib/chain/options.d.ts +0 -4
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +0 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +4 -2
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -6
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +2 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +3 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +1 -2
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +8 -1
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +1 -2
- package/lib/chain/validation/proposerSlashing.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +2 -2
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts +1 -2
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +1 -3
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/buckets.d.ts +0 -1
- package/lib/db/buckets.d.ts.map +1 -1
- package/lib/db/buckets.js +1 -1
- package/lib/db/buckets.js.map +1 -1
- package/lib/db/interface.d.ts +1 -2
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.d.ts +5 -0
- package/lib/db/repositories/blockArchive.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +36 -1
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/index.d.ts +0 -1
- package/lib/db/repositories/index.d.ts.map +1 -1
- package/lib/db/repositories/index.js +0 -1
- package/lib/db/repositories/index.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +35 -14
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +76 -24
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/metrics/metrics.d.ts +3 -1
- package/lib/metrics/metrics.d.ts.map +1 -1
- package/lib/metrics/metrics.js +2 -2
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +1 -2
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js +5 -10
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +16 -1
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +9 -6
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +23 -8
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +16 -5
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +2 -0
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +88 -7
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +6 -2
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +62 -24
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +20 -4
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +19 -5
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/node/nodejs.d.ts +2 -7
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +2 -22
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/interface.d.ts +1 -2
- package/lib/sync/interface.d.ts.map +1 -1
- package/lib/sync/options.d.ts +0 -6
- package/lib/sync/options.d.ts.map +1 -1
- package/lib/sync/options.js +0 -2
- package/lib/sync/options.js.map +1 -1
- package/lib/sync/types.d.ts +39 -2
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +42 -2
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +20 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +231 -26
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/shuffle.d.ts +0 -4
- package/lib/util/shuffle.d.ts.map +1 -1
- package/lib/util/shuffle.js +0 -8
- package/lib/util/shuffle.js.map +1 -1
- package/lib/util/sszBytes.d.ts +5 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +13 -1
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +16 -16
- package/src/api/impl/beacon/blocks/index.ts +2 -2
- package/src/api/impl/beacon/pool/index.ts +6 -2
- package/src/api/impl/validator/index.ts +9 -7
- package/src/chain/archiveStore/archiveStore.ts +0 -6
- package/src/chain/archiveStore/historicalState/metrics.ts +5 -7
- package/src/chain/archiveStore/utils/pruneHistory.ts +14 -4
- package/src/chain/blocks/importExecutionPayload.ts +14 -0
- package/src/chain/blocks/index.ts +2 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -1
- package/src/chain/emitter.ts +7 -2
- package/src/chain/errors/attesterSlashingError.ts +3 -1
- package/src/chain/errors/payloadAttestation.ts +2 -0
- package/src/chain/errors/proposerSlashingError.ts +3 -1
- package/src/chain/options.ts +0 -5
- package/src/chain/produceBlock/computeNewStateRoot.ts +9 -2
- package/src/chain/produceBlock/produceBlockBody.ts +17 -6
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +3 -2
- package/src/chain/validation/attesterSlashing.ts +1 -2
- package/src/chain/validation/payloadAttestationMessage.ts +9 -0
- package/src/chain/validation/proposerSlashing.ts +1 -2
- package/src/chain/validatorMonitor.ts +12 -2
- package/src/db/beacon.ts +0 -5
- package/src/db/buckets.ts +1 -1
- package/src/db/interface.ts +0 -3
- package/src/db/repositories/blockArchive.ts +38 -1
- package/src/db/repositories/index.ts +0 -1
- package/src/metrics/metrics/lodestar.ts +87 -28
- package/src/metrics/metrics.ts +7 -2
- package/src/network/gossip/encoding.ts +5 -8
- package/src/network/gossip/gossipsub.ts +19 -2
- package/src/network/gossip/topic.ts +25 -7
- package/src/network/processor/gossipHandlers.ts +18 -5
- package/src/network/processor/gossipValidatorFn.ts +111 -7
- package/src/network/processor/index.ts +81 -24
- package/src/network/reqresp/handlers/index.ts +49 -5
- package/src/network/reqresp/protocols.ts +31 -6
- package/src/node/nodejs.ts +0 -27
- package/src/sync/interface.ts +1 -2
- package/src/sync/options.ts +0 -8
- package/src/sync/types.ts +44 -5
- package/src/sync/unknownBlock.ts +254 -26
- package/src/util/shuffle.ts +0 -9
- package/src/util/sszBytes.ts +18 -0
- package/lib/chain/archiveStore/utils/updateBackfillRange.d.ts +0 -20
- package/lib/chain/archiveStore/utils/updateBackfillRange.d.ts.map +0 -1
- package/lib/chain/archiveStore/utils/updateBackfillRange.js +0 -40
- package/lib/chain/archiveStore/utils/updateBackfillRange.js.map +0 -1
- package/lib/db/repositories/backfilledRanges.d.ts +0 -18
- package/lib/db/repositories/backfilledRanges.d.ts.map +0 -1
- package/lib/db/repositories/backfilledRanges.js +0 -26
- package/lib/db/repositories/backfilledRanges.js.map +0 -1
- package/lib/sync/backfill/backfill.d.ts +0 -169
- package/lib/sync/backfill/backfill.d.ts.map +0 -1
- package/lib/sync/backfill/backfill.js +0 -686
- package/lib/sync/backfill/backfill.js.map +0 -1
- package/lib/sync/backfill/errors.d.ts +0 -30
- package/lib/sync/backfill/errors.d.ts.map +0 -1
- package/lib/sync/backfill/errors.js +0 -15
- package/lib/sync/backfill/errors.js.map +0 -1
- package/lib/sync/backfill/index.d.ts +0 -2
- package/lib/sync/backfill/index.d.ts.map +0 -1
- package/lib/sync/backfill/index.js +0 -2
- package/lib/sync/backfill/index.js.map +0 -1
- package/lib/sync/backfill/verify.d.ts +0 -18
- package/lib/sync/backfill/verify.d.ts.map +0 -1
- package/lib/sync/backfill/verify.js +0 -36
- package/lib/sync/backfill/verify.js.map +0 -1
- package/src/chain/archiveStore/utils/updateBackfillRange.ts +0 -50
- package/src/db/repositories/backfilledRanges.ts +0 -29
- package/src/sync/backfill/backfill.ts +0 -916
- package/src/sync/backfill/errors.ts +0 -23
- package/src/sync/backfill/index.ts +0 -1
- package/src/sync/backfill/verify.ts +0 -58
package/lib/sync/unknownBlock.js
CHANGED
|
@@ -2,7 +2,7 @@ import { routes } from "@lodestar/api";
|
|
|
2
2
|
import { ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas } from "@lodestar/params";
|
|
3
3
|
import { RequestError, RequestErrorCode } from "@lodestar/reqresp";
|
|
4
4
|
import { computeTimeAtSlot } from "@lodestar/state-transition";
|
|
5
|
-
import { fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
|
|
5
|
+
import { fromHex, isErrorAborted, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
|
|
6
6
|
import { isBlockInputBlobs, isBlockInputColumns } from "../chain/blocks/blockInput/blockInput.js";
|
|
7
7
|
import { BlockInputSource } from "../chain/blocks/blockInput/types.js";
|
|
8
8
|
import { PayloadError, PayloadErrorCode } from "../chain/blocks/importExecutionPayload.js";
|
|
@@ -13,11 +13,12 @@ import { validateGloasBlockDataColumnSidecars } from "../chain/validation/dataCo
|
|
|
13
13
|
import { validateGossipExecutionPayloadEnvelope } from "../chain/validation/executionPayloadEnvelope.js";
|
|
14
14
|
import { NetworkEvent, prettyPrintPeerIdStr } from "../network/index.js";
|
|
15
15
|
import { MAX_PEERS_PER_ROOT } from "../network/processor/constants.js";
|
|
16
|
+
import { ClockEvent } from "../util/clock.js";
|
|
16
17
|
import { shuffle } from "../util/shuffle.js";
|
|
17
18
|
import { sortBy } from "../util/sortBy.js";
|
|
18
19
|
import { wrapError } from "../util/wrapError.js";
|
|
19
20
|
import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
|
|
20
|
-
import { DroppedItemReason, PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
|
|
21
|
+
import { DeferredPayloadResult, DownloadResult, DroppedItemReason, FetchResult, PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
|
|
21
22
|
import { DownloadByRootError, downloadByRoot } from "./utils/downloadByRoot.js";
|
|
22
23
|
import { getAllDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
|
|
23
24
|
import { getRateLimitedUntilMs } from "./utils/rateLimit.js";
|
|
@@ -28,14 +29,10 @@ const MAX_PENDING_BLOCKS = 100;
|
|
|
28
29
|
* A pending payload without slot can only stay at most 2 epochs.
|
|
29
30
|
*/
|
|
30
31
|
const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
FetchResult["SuccessLate"] = "success_late";
|
|
36
|
-
FetchResult["FailureTriedAllPeers"] = "failure_tried_all_peers";
|
|
37
|
-
FetchResult["FailureMaxAttempts"] = "failure_max_attempts";
|
|
38
|
-
})(FetchResult || (FetchResult = {}));
|
|
32
|
+
/**
|
|
33
|
+
* Poll pending payloads of the current slot this often, between PAYLOAD_DUE and the end of the slot.
|
|
34
|
+
*/
|
|
35
|
+
export const PAYLOAD_POLL_INTERVAL_MS = 1_000;
|
|
39
36
|
class UnknownBlockRateLimitedError extends Error {
|
|
40
37
|
constructor(message) {
|
|
41
38
|
super(message);
|
|
@@ -92,6 +89,11 @@ export class BlockInputSync {
|
|
|
92
89
|
subscribedToNetworkEvents = false;
|
|
93
90
|
peerBalancer;
|
|
94
91
|
rateLimitBackoffTimeout;
|
|
92
|
+
/**
|
|
93
|
+
* Search unknown payload starting at PAYLOAD_DUE_BPS.
|
|
94
|
+
*/
|
|
95
|
+
deferredPayloadRoots = new Map();
|
|
96
|
+
payloadPollAbortController;
|
|
95
97
|
constructor(config, network, chain, logger, metrics, opts) {
|
|
96
98
|
this.config = config;
|
|
97
99
|
this.network = network;
|
|
@@ -117,6 +119,7 @@ export class BlockInputSync {
|
|
|
117
119
|
this.logger.verbose("BlockInputSync enabled.");
|
|
118
120
|
this.chain.emitter.on(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
119
121
|
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
122
|
+
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRootSlot, this.onUnknownEnvelopeBlockRootSlot);
|
|
120
123
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
121
124
|
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
122
125
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
@@ -125,6 +128,7 @@ export class BlockInputSync {
|
|
|
125
128
|
this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
126
129
|
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
127
130
|
this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
131
|
+
this.chain.clock.on(ClockEvent.slot, this.startPayloadPollAtSlot);
|
|
128
132
|
// Seed the balancer with peers that connected before we subscribed
|
|
129
133
|
for (const peerId of this.network.getConnectedPeers()) {
|
|
130
134
|
try {
|
|
@@ -135,13 +139,21 @@ export class BlockInputSync {
|
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
141
|
this.subscribedToNetworkEvents = true;
|
|
142
|
+
// we subscribe mid-slot, and a root can be deferred at any point in the slot, so start this
|
|
143
|
+
// slot's loop now instead of waiting for the next ClockEvent.slot
|
|
144
|
+
this.startPayloadPollAtSlot(this.chain.clock.currentSlot);
|
|
138
145
|
}
|
|
139
146
|
}
|
|
140
147
|
unsubscribeFromNetwork() {
|
|
141
148
|
this.logger.verbose("BlockInputSync disabled.");
|
|
142
149
|
this.clearRateLimitBackoffTimer();
|
|
150
|
+
this.chain.clock.off(ClockEvent.slot, this.startPayloadPollAtSlot);
|
|
151
|
+
this.payloadPollAbortController?.abort();
|
|
152
|
+
this.payloadPollAbortController = undefined;
|
|
153
|
+
this.deferredPayloadRoots.clear();
|
|
143
154
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
144
155
|
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
156
|
+
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRootSlot, this.onUnknownEnvelopeBlockRootSlot);
|
|
145
157
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
146
158
|
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
147
159
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
@@ -165,8 +177,11 @@ export class BlockInputSync {
|
|
|
165
177
|
onUnknownBlockRoot = (data) => {
|
|
166
178
|
try {
|
|
167
179
|
const isNewRoot = this.addByRootHex(data.rootHex, data.peer);
|
|
168
|
-
|
|
180
|
+
// A retained entry (an earlier download failed, #10018) is not new but still needs a retry pass.
|
|
181
|
+
if (this.pendingBlocks.get(data.rootHex)?.status === PendingBlockInputStatus.pending) {
|
|
169
182
|
this.triggerUnknownBlockSearch();
|
|
183
|
+
}
|
|
184
|
+
if (isNewRoot) {
|
|
170
185
|
this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_BLOCK_ROOT });
|
|
171
186
|
this.metrics?.blockInputSync.source.inc({ source: data.source });
|
|
172
187
|
}
|
|
@@ -202,6 +217,33 @@ export class BlockInputSync {
|
|
|
202
217
|
this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e);
|
|
203
218
|
}
|
|
204
219
|
};
|
|
220
|
+
onUnknownEnvelopeBlockRootSlot = (data) => {
|
|
221
|
+
try {
|
|
222
|
+
if (data.slot === this.chain.clock.currentSlot) {
|
|
223
|
+
// we receive the optimistic search, the poll loop searches it at PAYLOAD_DUE or on its next
|
|
224
|
+
// tick. If that loop already returned - every root it watched was imported - this root waits
|
|
225
|
+
// for the next slot, where startPayloadPollAtSlot moves it to pendingPayloads. That is not the normal
|
|
226
|
+
// scenario, and in a forking condition it only delays the search of the extra root
|
|
227
|
+
const deferred = this.deferredPayloadRoots.get(data.rootHex);
|
|
228
|
+
if (deferred === undefined) {
|
|
229
|
+
this.deferredPayloadRoots.set(data.rootHex, { slot: data.slot, source: data.source, peer: data.peer });
|
|
230
|
+
}
|
|
231
|
+
else if (deferred.peer === undefined) {
|
|
232
|
+
deferred.peer = data.peer;
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const isNewRoot = this.addByPayloadRootHex(data.rootHex, data.peer, data.slot);
|
|
237
|
+
if (isNewRoot) {
|
|
238
|
+
this.triggerUnknownBlockSearch();
|
|
239
|
+
this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT_SLOT });
|
|
240
|
+
this.metrics?.blockInputSync.payloadSource.inc({ source: data.source });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (e) {
|
|
244
|
+
this.logger.debug("Error handling unknownEnvelopeBlockRootSlot event", {}, e);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
205
247
|
onIncompletePayloadEnvelope = (data) => {
|
|
206
248
|
try {
|
|
207
249
|
this.addByPayloadInput(data.payloadInput, data.peer);
|
|
@@ -296,12 +338,21 @@ export class BlockInputSync {
|
|
|
296
338
|
belowFinalizedPayloads++;
|
|
297
339
|
}
|
|
298
340
|
}
|
|
299
|
-
let
|
|
341
|
+
let belowFinalizedBlocks = 0;
|
|
342
|
+
let agedOutBlocks = 0;
|
|
300
343
|
for (const [rootHex, block] of this.pendingBlocks) {
|
|
301
344
|
const slot = getBlockInputSyncCacheItemSlot(block);
|
|
302
|
-
if (typeof slot
|
|
345
|
+
if (typeof slot !== "number") {
|
|
346
|
+
// we may not be able to download a root, hence there is no slot for it
|
|
347
|
+
if (nowSec - block.timeAddedSec > maxSecInCache) {
|
|
348
|
+
this.pendingBlocks.delete(rootHex);
|
|
349
|
+
agedOutBlocks++;
|
|
350
|
+
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (slot < finalizedSlot) {
|
|
303
354
|
this.pendingBlocks.delete(rootHex);
|
|
304
|
-
|
|
355
|
+
belowFinalizedBlocks++;
|
|
305
356
|
}
|
|
306
357
|
}
|
|
307
358
|
if (belowFinalizedPayloads > 0) {
|
|
@@ -310,15 +361,19 @@ export class BlockInputSync {
|
|
|
310
361
|
if (agedOutPayloads > 0) {
|
|
311
362
|
this.metrics?.blockInputSync.removedPayloads.inc({ reason: DroppedItemReason.agedOut }, agedOutPayloads);
|
|
312
363
|
}
|
|
313
|
-
if (
|
|
314
|
-
this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.belowFinalized },
|
|
364
|
+
if (belowFinalizedBlocks > 0) {
|
|
365
|
+
this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.belowFinalized }, belowFinalizedBlocks);
|
|
315
366
|
}
|
|
316
|
-
|
|
367
|
+
if (agedOutBlocks > 0) {
|
|
368
|
+
this.metrics?.blockInputSync.removedBlocks.inc({ reason: DroppedItemReason.agedOut }, agedOutBlocks);
|
|
369
|
+
}
|
|
370
|
+
this.logger.debug("BlockInputSync.pruneFinalized dropped stale pending items", {
|
|
317
371
|
finalizedSlot,
|
|
318
372
|
finalizedRoot: checkpoint.rootHex,
|
|
319
373
|
belowFinalizedPayloads,
|
|
320
374
|
agedOutPayloads,
|
|
321
|
-
|
|
375
|
+
belowFinalizedBlocks,
|
|
376
|
+
agedOutBlocks,
|
|
322
377
|
unresolvedSlotPayloads,
|
|
323
378
|
});
|
|
324
379
|
};
|
|
@@ -660,6 +715,115 @@ export class BlockInputSync {
|
|
|
660
715
|
this.rateLimitBackoffTimeout = undefined;
|
|
661
716
|
}
|
|
662
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Start this slot's payload poll: end the previous slot's loop, move roots it never got to
|
|
720
|
+
* into pendingPayloads, then poll from PAYLOAD_DUE to the end of the slot.
|
|
721
|
+
*/
|
|
722
|
+
startPayloadPollAtSlot = (slot) => {
|
|
723
|
+
if (!isForkPostGloas(this.config.getForkName(slot))) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
// end the previous slot's loop, wherever it is: its sleep is bound to this same controller
|
|
727
|
+
this.payloadPollAbortController?.abort();
|
|
728
|
+
this.payloadPollAbortController = new AbortController();
|
|
729
|
+
// entries from an earlier slot is not polled
|
|
730
|
+
for (const [rootHex, { slot: deferredSlot, source, peer }] of this.deferredPayloadRoots) {
|
|
731
|
+
if (deferredSlot < slot) {
|
|
732
|
+
if (!this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
733
|
+
this.addDeferredPayloadRoot(rootHex, deferredSlot, source, peer);
|
|
734
|
+
}
|
|
735
|
+
this.deferredPayloadRoots.delete(rootHex);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
this.pollPayloadsAtSlot(slot, this.payloadPollAbortController.signal).catch((e) => {
|
|
739
|
+
if (!isErrorAborted(e)) {
|
|
740
|
+
this.logger.debug("Error polling payloads", { slot }, e);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
};
|
|
744
|
+
/**
|
|
745
|
+
* Poll unknown payloads of the current slot.
|
|
746
|
+
*/
|
|
747
|
+
async pollPayloadsAtSlot(slot, signal) {
|
|
748
|
+
// fixed tick count so the loop cannot outlive its slot: for mainnet, it's 6 ticks
|
|
749
|
+
const tickCount = Math.floor((this.config.SLOT_DURATION_MS - this.config.getPayloadDueMs()) / PAYLOAD_POLL_INTERVAL_MS);
|
|
750
|
+
// a root still missing on several ticks is searched on each of them, recorded once
|
|
751
|
+
const searchedRoots = new Set();
|
|
752
|
+
let polls = 0;
|
|
753
|
+
try {
|
|
754
|
+
await sleep(Math.max(0, this.config.getPayloadDueMs() - this.chain.clock.msFromSlot(slot)), signal);
|
|
755
|
+
for (polls = 0; polls < tickCount; polls++) {
|
|
756
|
+
let importedCount = 0;
|
|
757
|
+
for (const [rootHex, { slot: deferredSlot, source, peer }] of this.deferredPayloadRoots) {
|
|
758
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
759
|
+
importedCount++;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
// copy to the regular pendingPayloads so that we can search
|
|
763
|
+
this.addDeferredPayloadRoot(rootHex, deferredSlot, source, peer);
|
|
764
|
+
searchedRoots.add(rootHex);
|
|
765
|
+
}
|
|
766
|
+
// an empty deferredPayloadRoots means there is no optimistic search comes before PAYLOAD_DUE
|
|
767
|
+
// we have to poll until the payload of current slot is imported or slot end
|
|
768
|
+
if (this.deferredPayloadRoots.size > 0 && importedCount === this.deferredPayloadRoots.size) {
|
|
769
|
+
if (searchedRoots.size > 0) {
|
|
770
|
+
// the backup search was needed
|
|
771
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({
|
|
772
|
+
result: DeferredPayloadResult.ImportedAfterSearch,
|
|
773
|
+
});
|
|
774
|
+
this.metrics?.blockInputSync.deferredPayloadPolls.observe(polls);
|
|
775
|
+
this.logger.verbose("Payload imported after unknown sync search", {
|
|
776
|
+
slot,
|
|
777
|
+
roots: Array.from(searchedRoots).join(","),
|
|
778
|
+
polls,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
// this happens most of the time on devnet
|
|
783
|
+
// gossip delivered everything before PAYLOAD_DUE
|
|
784
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({
|
|
785
|
+
result: DeferredPayloadResult.ImportedBeforeSearch,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
this.triggerUnknownBlockSearch();
|
|
791
|
+
if (polls < tickCount - 1) {
|
|
792
|
+
// no sleep after the last tick: the loop would otherwise end right at the slot boundary,
|
|
793
|
+
// where startPayloadPollAtSlot aborts it
|
|
794
|
+
await sleep(PAYLOAD_POLL_INTERVAL_MS, signal);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// reaching here means the early return never fired
|
|
798
|
+
if (this.deferredPayloadRoots.size > 0) {
|
|
799
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({ result: DeferredPayloadResult.Unresolved });
|
|
800
|
+
this.logger.debug("Deferred payloads still missing at end of slot", {
|
|
801
|
+
slot,
|
|
802
|
+
roots: Array.from(this.deferredPayloadRoots.keys()).join(","),
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
// no optimistic search and no payload import ended the loop, eg a skipped slot
|
|
807
|
+
this.metrics?.blockInputSync.deferredPayloadResult.inc({ result: DeferredPayloadResult.ResolvedNoRoot });
|
|
808
|
+
this.logger.debug("No deferred payloads at slot", {
|
|
809
|
+
slot,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch (e) {
|
|
814
|
+
if (!isErrorAborted(e)) {
|
|
815
|
+
throw e;
|
|
816
|
+
}
|
|
817
|
+
this.logger.verbose("Payload poll aborted", { slot, polls, msIntoSlot: this.chain.clock.msFromSlot(slot) });
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
addDeferredPayloadRoot(rootHex, slot, source, peer) {
|
|
821
|
+
const isNewRoot = this.addByPayloadRootHex(rootHex, peer, slot);
|
|
822
|
+
if (isNewRoot) {
|
|
823
|
+
this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.DEFERRED_PAYLOAD_BLOCK_ROOT });
|
|
824
|
+
this.metrics?.blockInputSync.payloadSource.inc({ source });
|
|
825
|
+
}
|
|
826
|
+
}
|
|
663
827
|
async downloadBlock(block) {
|
|
664
828
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
665
829
|
return;
|
|
@@ -678,7 +842,12 @@ export class BlockInputSync {
|
|
|
678
842
|
if (!res.err) {
|
|
679
843
|
this.metrics?.blockInputSync.downloadedBlocksSuccess.inc();
|
|
680
844
|
const pending = res.result;
|
|
681
|
-
this
|
|
845
|
+
// pruning may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
|
|
846
|
+
if (!this.pendingBlocks.has(rootHex)) {
|
|
847
|
+
this.logger.verbose("Dropping downloaded block, entry pruned during fetch", logCtx);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
this.pendingBlocks.set(rootHex, pending);
|
|
682
851
|
const blockSlot = pending.blockInput.slot;
|
|
683
852
|
const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
|
|
684
853
|
const delaySec = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.chain.genesisTime);
|
|
@@ -738,8 +907,11 @@ export class BlockInputSync {
|
|
|
738
907
|
return;
|
|
739
908
|
}
|
|
740
909
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
741
|
-
this.logger.debug("
|
|
742
|
-
this.
|
|
910
|
+
this.logger.debug("Failed to download unknown block root, retained for retry", logCtx, res.err);
|
|
911
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
912
|
+
if (pendingBlock && pendingBlock.status === PendingBlockInputStatus.fetching) {
|
|
913
|
+
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
914
|
+
}
|
|
743
915
|
}
|
|
744
916
|
}
|
|
745
917
|
/**
|
|
@@ -946,15 +1118,23 @@ export class BlockInputSync {
|
|
|
946
1118
|
return;
|
|
947
1119
|
}
|
|
948
1120
|
this.pendingPayloads.set(resultRootHex, pendingPayload);
|
|
1121
|
+
const result = isPendingPayloadEnvelope(pendingPayload)
|
|
1122
|
+
? DownloadResult.WaitingForBlock
|
|
1123
|
+
: this.chain.forkChoice.hasPayloadHexUnsafe(resultRootHex)
|
|
1124
|
+
? DownloadResult.Late
|
|
1125
|
+
: DownloadResult.Resolved;
|
|
1126
|
+
this.metrics?.blockInputSync.downloadedPayloadsResult.inc({ result });
|
|
949
1127
|
if (isPendingPayloadEnvelope(pendingPayload)) {
|
|
950
1128
|
await this.reconcilePayloadEnvelope(pendingPayload);
|
|
951
1129
|
}
|
|
952
1130
|
else if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1131
|
+
this.metrics?.blockInputSync.elapsedTimeTillPayloadReceived.observe(Date.now() / 1000 - computeTimeAtSlot(this.config, pendingPayload.payloadInput.slot, this.chain.genesisTime));
|
|
953
1132
|
await this.processPayload(pendingPayload);
|
|
954
1133
|
}
|
|
955
1134
|
return;
|
|
956
1135
|
}
|
|
957
|
-
this.
|
|
1136
|
+
this.metrics?.blockInputSync.downloadedPayloadsResult.inc({ result: DownloadResult.Failed });
|
|
1137
|
+
this.logger.debug("Failed to download unknown payload root, retained for retry", logCtx, res.err);
|
|
958
1138
|
if (!isPendingPayloadEnvelope(payload)) {
|
|
959
1139
|
payload.status = PendingPayloadInputStatus.pending;
|
|
960
1140
|
}
|
|
@@ -1050,6 +1230,10 @@ export class BlockInputSync {
|
|
|
1050
1230
|
? cacheItem.payloadInput
|
|
1051
1231
|
: await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1052
1232
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1233
|
+
const fetchStartSec = Date.now() / 1000;
|
|
1234
|
+
if (typeof slot === "number") {
|
|
1235
|
+
this.metrics?.blockInputSync.payloadFetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1236
|
+
}
|
|
1053
1237
|
let i = 0;
|
|
1054
1238
|
let deferredByRateLimit = false;
|
|
1055
1239
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
@@ -1065,6 +1249,8 @@ export class BlockInputSync {
|
|
|
1065
1249
|
const reason = pendingColumns.size > 0
|
|
1066
1250
|
? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1067
1251
|
: "no peer available to download pending payload";
|
|
1252
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe({ result: FetchResult.FailureTriedAllPeers }, Date.now() / 1000 - fetchStartSec);
|
|
1253
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({ result: FetchResult.FailureTriedAllPeers }, i);
|
|
1068
1254
|
throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
|
|
1069
1255
|
}
|
|
1070
1256
|
const { peerId, client: peerClient } = peerMeta;
|
|
@@ -1072,7 +1258,12 @@ export class BlockInputSync {
|
|
|
1072
1258
|
try {
|
|
1073
1259
|
if (!envelope) {
|
|
1074
1260
|
envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
|
|
1261
|
+
const slotWasUnknown = typeof slot !== "number";
|
|
1075
1262
|
slot = envelope.message.payload.slotNumber;
|
|
1263
|
+
if (slotWasUnknown) {
|
|
1264
|
+
// we were not able to observe the time into slot when starting the fetch, do it now
|
|
1265
|
+
this.metrics?.blockInputSync.payloadFetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
1266
|
+
}
|
|
1076
1267
|
}
|
|
1077
1268
|
payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1078
1269
|
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
@@ -1080,6 +1271,8 @@ export class BlockInputSync {
|
|
|
1080
1271
|
// envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
|
|
1081
1272
|
// payloadInput may be seeded from the block body during download, so a non-null payloadInput does not
|
|
1082
1273
|
// imply the block is imported.
|
|
1274
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe({ result: FetchResult.SuccessWaitingForBlock }, Date.now() / 1000 - fetchStartSec);
|
|
1275
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({ result: FetchResult.SuccessWaitingForBlock }, i);
|
|
1083
1276
|
return {
|
|
1084
1277
|
status: PendingPayloadInputStatus.waitingForBlock,
|
|
1085
1278
|
envelope,
|
|
@@ -1124,6 +1317,12 @@ export class BlockInputSync {
|
|
|
1124
1317
|
hasAllData: pendingPayload.payloadInput.hasAllData(),
|
|
1125
1318
|
});
|
|
1126
1319
|
if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
1320
|
+
// late = the payload got imported (via gossip) while we were downloading, same as the block path
|
|
1321
|
+
const result = this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)
|
|
1322
|
+
? FetchResult.SuccessLate
|
|
1323
|
+
: FetchResult.SuccessResolved;
|
|
1324
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe({ result }, Date.now() / 1000 - fetchStartSec);
|
|
1325
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({ result }, i);
|
|
1127
1326
|
return pendingPayload;
|
|
1128
1327
|
}
|
|
1129
1328
|
cacheItem = pendingPayload;
|
|
@@ -1155,6 +1354,8 @@ export class BlockInputSync {
|
|
|
1155
1354
|
this.peerBalancer.onRequestCompleted(peerId);
|
|
1156
1355
|
}
|
|
1157
1356
|
}
|
|
1357
|
+
this.metrics?.blockInputSync.payloadFetchTime.observe({ result: FetchResult.FailureMaxAttempts }, Date.now() / 1000 - fetchStartSec);
|
|
1358
|
+
this.metrics?.blockInputSync.payloadFetchPeers.set({ result: FetchResult.FailureMaxAttempts }, i - 1);
|
|
1158
1359
|
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1159
1360
|
throw new UnknownBlockRateLimitedError(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`);
|
|
1160
1361
|
}
|
|
@@ -1227,7 +1428,7 @@ export class BlockInputSync {
|
|
|
1227
1428
|
}
|
|
1228
1429
|
// no more peer with needed columns to try, throw error
|
|
1229
1430
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
1230
|
-
this.metrics?.blockInputSync.
|
|
1431
|
+
this.metrics?.blockInputSync.fetchTime.observe({ result: FetchResult.FailureTriedAllPeers }, Date.now() / 1000 - fetchStartSec);
|
|
1231
1432
|
this.metrics?.blockInputSync.fetchPeers.set({ result: FetchResult.FailureTriedAllPeers }, i);
|
|
1232
1433
|
throw Error(message);
|
|
1233
1434
|
}
|
|
@@ -1301,7 +1502,11 @@ export class BlockInputSync {
|
|
|
1301
1502
|
finally {
|
|
1302
1503
|
this.peerBalancer.onRequestCompleted(peerId);
|
|
1303
1504
|
}
|
|
1304
|
-
|
|
1505
|
+
const cacheItemRootHex = getBlockInputSyncCacheItemRootHex(cacheItem);
|
|
1506
|
+
// pruning may have dropped it mid-fetch
|
|
1507
|
+
if (this.pendingBlocks.has(cacheItemRootHex)) {
|
|
1508
|
+
this.pendingBlocks.set(cacheItemRootHex, cacheItem);
|
|
1509
|
+
}
|
|
1305
1510
|
if (cacheItem.status === PendingBlockInputStatus.downloaded) {
|
|
1306
1511
|
// download was successful, no need to go with another peer, return
|
|
1307
1512
|
const result = this.chain.forkChoice.hasBlockHex(cacheItem.blockInput.blockRootHex)
|
|
@@ -1309,7 +1514,7 @@ export class BlockInputSync {
|
|
|
1309
1514
|
: this.chain.forkChoice.hasBlockHex(cacheItem.blockInput.parentRootHex)
|
|
1310
1515
|
? FetchResult.SuccessResolved
|
|
1311
1516
|
: FetchResult.SuccessMissingParent;
|
|
1312
|
-
this.metrics?.blockInputSync.
|
|
1517
|
+
this.metrics?.blockInputSync.fetchTime.observe({ result }, Date.now() / 1000 - fetchStartSec);
|
|
1313
1518
|
this.metrics?.blockInputSync.fetchPeers.set({ result }, i);
|
|
1314
1519
|
return cacheItem;
|
|
1315
1520
|
}
|
|
@@ -1336,7 +1541,7 @@ export class BlockInputSync {
|
|
|
1336
1541
|
throw new Error(`${message} Missing column indices=${prettyPrintIndices(missing)}.`);
|
|
1337
1542
|
}
|
|
1338
1543
|
}
|
|
1339
|
-
this.metrics?.blockInputSync.
|
|
1544
|
+
this.metrics?.blockInputSync.fetchTime.observe({ result: FetchResult.FailureMaxAttempts }, Date.now() / 1000 - fetchStartSec);
|
|
1340
1545
|
this.metrics?.blockInputSync.fetchPeers.set({ result: FetchResult.FailureMaxAttempts }, i - 1);
|
|
1341
1546
|
throw Error(message);
|
|
1342
1547
|
}
|