@lodestar/beacon-node 1.43.0-dev.87cbe69c66 → 1.43.0-dev.8ad5d8be4c
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/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 +0 -16
- 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 +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 +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/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 +25 -10
- package/lib/chain/chain.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/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 +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +6 -11
- package/lib/chain/seenCache/seenProposerPreferences.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 +45 -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 +7 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +7 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +32 -6
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +215 -41
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +16 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +77 -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 +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 -10
- 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 +89 -42
- 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/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +0 -18
- package/src/chain/blocks/importExecutionPayload.ts +18 -6
- package/src/chain/blocks/index.ts +5 -7
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +31 -2
- 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 +29 -9
- package/src/chain/initState.ts +9 -1
- package/src/chain/prepareNextSlot.ts +1 -1
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +66 -20
- package/src/chain/seenCache/seenProposerPreferences.ts +7 -12
- 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 +53 -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 +8 -4
- package/src/sync/range/batch.ts +256 -47
- package/src/sync/range/chain.ts +94 -5
- 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 -10
- package/src/sync/utils/downloadByRange.ts +156 -44
- package/src/sync/utils/rateLimit.ts +16 -0
package/src/sync/range/batch.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
2
|
import {ForkName, isForkPostDeneb, isForkPostFulu, isForkPostGloas} from "@lodestar/params";
|
|
3
|
-
import {Epoch, RootHex, Slot, phase0} from "@lodestar/types";
|
|
4
|
-
import {LodestarError, prettyPrintIndices} from "@lodestar/utils";
|
|
3
|
+
import {Epoch, RootHex, SignedBeaconBlock, Slot, gloas, phase0} from "@lodestar/types";
|
|
4
|
+
import {LodestarError, byteArrayEquals, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
5
5
|
import {isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
|
|
6
6
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
7
7
|
import {isDaOutOfRange} from "../../chain/blocks/blockInput/utils.js";
|
|
8
8
|
import {PayloadEnvelopeInput} from "../../chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
9
9
|
import {BlockError, BlockErrorCode} from "../../chain/errors/index.js";
|
|
10
|
+
import {ZERO_HASH} from "../../constants/constants.js";
|
|
10
11
|
import {PeerSyncMeta} from "../../network/peers/peersData.js";
|
|
11
12
|
import {IClock} from "../../util/clock.js";
|
|
12
13
|
import {CustodyConfig} from "../../util/dataColumns.js";
|
|
13
14
|
import {PeerIdStr} from "../../util/peerId.js";
|
|
14
15
|
import {MAX_BATCH_DOWNLOAD_ATTEMPTS, MAX_BATCH_PROCESSING_ATTEMPTS} from "../constants.js";
|
|
15
|
-
import {DownloadByRangeRequests} from "../utils/downloadByRange.js";
|
|
16
|
+
import {DownloadByRangeRequests, ParentPayloadCommitments} from "../utils/downloadByRange.js";
|
|
16
17
|
import {getBatchSlotRange, hashBlocks} from "./utils/index.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -44,6 +45,15 @@ export type Attempt = {
|
|
|
44
45
|
hash: RootHex;
|
|
45
46
|
};
|
|
46
47
|
|
|
48
|
+
type TrackedRequest = {
|
|
49
|
+
/** only happen for the 1st batch in checkpoint sync */
|
|
50
|
+
parentPayload: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* we always issue by_range before parent_payload, so we don't model this as null
|
|
53
|
+
*/
|
|
54
|
+
byRangeColumns: Set<number>;
|
|
55
|
+
};
|
|
56
|
+
|
|
47
57
|
export type AwaitingDownloadState = {
|
|
48
58
|
status: BatchStatus.AwaitingDownload;
|
|
49
59
|
blocks: IBlockInput[];
|
|
@@ -61,6 +71,7 @@ export type BatchState =
|
|
|
61
71
|
| {
|
|
62
72
|
status: BatchStatus.Downloading;
|
|
63
73
|
peer: PeerIdStr;
|
|
74
|
+
request: TrackedRequest;
|
|
64
75
|
blocks: IBlockInput[];
|
|
65
76
|
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
|
|
66
77
|
}
|
|
@@ -109,6 +120,13 @@ function formatColumnsReq(req: {startSlot: Slot; count: number; columns: number[
|
|
|
109
120
|
return `startSlot=${req.startSlot},count=${req.count},cols=${prettyPrintIndices(req.columns)}`;
|
|
110
121
|
}
|
|
111
122
|
|
|
123
|
+
function getTrackedRequest({parentPayloadRequest, columnsRequest}: DownloadByRangeRequests): TrackedRequest {
|
|
124
|
+
return {
|
|
125
|
+
parentPayload: parentPayloadRequest != null,
|
|
126
|
+
byRangeColumns: new Set(parentPayloadRequest == null ? (columnsRequest?.columns ?? []) : []),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
112
130
|
/**
|
|
113
131
|
* Batches are downloaded at the first block of the epoch.
|
|
114
132
|
*
|
|
@@ -130,8 +148,8 @@ export class Batch {
|
|
|
130
148
|
requests: DownloadByRangeRequests;
|
|
131
149
|
/** State of the batch. */
|
|
132
150
|
state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
133
|
-
/** Peers that provided good data */
|
|
134
|
-
|
|
151
|
+
/** Peers that provided good data, with column coverage for by_range requests */
|
|
152
|
+
private readonly successfulDownloads = new Map<PeerIdStr, TrackedRequest>();
|
|
135
153
|
/** The `Attempts` that have been made and failed to send us this batch. */
|
|
136
154
|
readonly failedProcessingAttempts: Attempt[] = [];
|
|
137
155
|
/** The `Attempts` that have been made and failed because of execution malfunction. */
|
|
@@ -141,8 +159,18 @@ export class Batch {
|
|
|
141
159
|
private readonly config: ChainForkConfig;
|
|
142
160
|
private readonly clock: IClock;
|
|
143
161
|
private readonly custodyConfig: CustodyConfig;
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
private readonly isFirstBatchInChain: boolean;
|
|
163
|
+
private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
|
|
164
|
+
|
|
165
|
+
constructor(
|
|
166
|
+
startEpoch: Epoch,
|
|
167
|
+
config: ChainForkConfig,
|
|
168
|
+
clock: IClock,
|
|
169
|
+
custodyConfig: CustodyConfig,
|
|
170
|
+
isFirstBatchInChain: boolean,
|
|
171
|
+
latestBid: gloas.ExecutionPayloadBid | undefined,
|
|
172
|
+
targetSlot: Slot
|
|
173
|
+
) {
|
|
146
174
|
this.config = config;
|
|
147
175
|
this.clock = clock;
|
|
148
176
|
this.custodyConfig = custodyConfig;
|
|
@@ -151,10 +179,40 @@ export class Batch {
|
|
|
151
179
|
this.forkName = this.config.getForkName(startSlot);
|
|
152
180
|
this.startEpoch = startEpoch;
|
|
153
181
|
this.startSlot = startSlot;
|
|
154
|
-
this.count = count;
|
|
182
|
+
this.count = Math.min(count, targetSlot - startSlot + 1);
|
|
183
|
+
this.isFirstBatchInChain = isFirstBatchInChain;
|
|
184
|
+
this.latestBid = latestBid;
|
|
155
185
|
this.requests = this.getRequests([]);
|
|
156
186
|
}
|
|
157
187
|
|
|
188
|
+
private shouldDownloadParentEnvelope(firstBlock?: SignedBeaconBlock): boolean {
|
|
189
|
+
if (!this.isFirstBatchInChain) return false;
|
|
190
|
+
|
|
191
|
+
if (this.startSlot === 0 || !isForkPostGloas(this.config.getForkName(this.startSlot - 1))) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// we only know if we should download parent envelope if firstBlock is downloaded
|
|
196
|
+
if (firstBlock === undefined) return false;
|
|
197
|
+
if (this.latestBid === undefined) return false;
|
|
198
|
+
const firstBlockBidParentHash = (firstBlock.message.body as gloas.BeaconBlockBody).signedExecutionPayloadBid.message
|
|
199
|
+
.parentBlockHash;
|
|
200
|
+
return byteArrayEquals(firstBlockBidParentHash, this.latestBid.blockHash);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
getParentPayloadCommitments(parentBlockRoot: Uint8Array): ParentPayloadCommitments {
|
|
204
|
+
if (this.latestBid === undefined) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Coding error: getParentPayloadCommitments called without latestBid for parentBlockRoot=${toRootHex(parentBlockRoot)}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
blockRoot: parentBlockRoot,
|
|
211
|
+
blockRootHex: toRootHex(parentBlockRoot),
|
|
212
|
+
kzgCommitments: this.latestBid.blobKzgCommitments,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
158
216
|
/**
|
|
159
217
|
* Builds ByRange requests for block, blobs and columns
|
|
160
218
|
*/
|
|
@@ -202,6 +260,7 @@ export class Batch {
|
|
|
202
260
|
const envelopesBySlot = this.state.payloadEnvelopes ?? new Map<Slot, PayloadEnvelopeInput>();
|
|
203
261
|
|
|
204
262
|
// ensure blocks are in slot-wise order
|
|
263
|
+
const isPostGloas = isForkPostGloas(this.forkName);
|
|
205
264
|
for (const blockInput of blocks) {
|
|
206
265
|
const blockSlot = blockInput.slot;
|
|
207
266
|
// check if block/data is present (hasBlock/hasAllData). If present then check if startSlot is the same as
|
|
@@ -217,21 +276,36 @@ export class Batch {
|
|
|
217
276
|
if (blockInput.hasBlock() && blockStartSlot === blockSlot) {
|
|
218
277
|
blockStartSlot = blockSlot + 1;
|
|
219
278
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
279
|
+
|
|
280
|
+
// Range sync uses hasComputedAllData (all sampled columns physically present), not hasAllData
|
|
281
|
+
// which flips at the reconstruction threshold. Sync never triggers reconstruction, so accepting
|
|
282
|
+
// a half-downloaded block here makes writeBlockInputToDb later block on waitForComputedAllData.
|
|
283
|
+
if (isPostGloas) {
|
|
284
|
+
// Post-Gloas: column data lives on PayloadEnvelopeInput, not on BlockInputNoData.
|
|
285
|
+
const payloadInput = envelopesBySlot.get(blockSlot);
|
|
286
|
+
if (blockInput.hasBlock() && envelopeStartSlot === blockSlot && payloadInput?.hasPayloadEnvelope()) {
|
|
287
|
+
envelopeStartSlot = blockSlot + 1;
|
|
288
|
+
}
|
|
289
|
+
if (payloadInput && !payloadInput.hasComputedAllData()) {
|
|
290
|
+
for (const index of payloadInput.getMissingSampledColumnMeta().missing) {
|
|
230
291
|
neededColumns.add(index);
|
|
231
292
|
}
|
|
293
|
+
} else if (payloadInput?.hasComputedAllData() && dataStartSlot === blockSlot) {
|
|
294
|
+
// Only advance dataStartSlot when we know columns for this slot are complete. If
|
|
295
|
+
// payloadInput is missing entirely we cannot tell, so stop here so the next round
|
|
296
|
+
// re-requests columns (and envelopes) starting at this slot.
|
|
297
|
+
dataStartSlot = blockSlot + 1;
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
if (isBlockInputColumns(blockInput) ? !blockInput.hasComputedAllData() : !blockInput.hasAllData()) {
|
|
301
|
+
if (isBlockInputColumns(blockInput)) {
|
|
302
|
+
for (const index of blockInput.getMissingSampledColumnMeta().missing) {
|
|
303
|
+
neededColumns.add(index);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} else if (dataStartSlot === blockSlot) {
|
|
307
|
+
dataStartSlot = blockSlot + 1;
|
|
232
308
|
}
|
|
233
|
-
} else if (dataStartSlot === blockSlot) {
|
|
234
|
-
dataStartSlot = blockSlot + 1;
|
|
235
309
|
}
|
|
236
310
|
}
|
|
237
311
|
|
|
@@ -251,11 +325,15 @@ export class Batch {
|
|
|
251
325
|
// range of 40 - 63, startSlot will be inclusive but subtraction will exclusive so need to + 1
|
|
252
326
|
const count = endSlot - dataStartSlot + 1;
|
|
253
327
|
if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
328
|
+
// Skip the column re-request when we have no specific column indices outstanding.
|
|
329
|
+
// Peer rejects an empty `columns` list
|
|
330
|
+
if (neededColumns.size > 0) {
|
|
331
|
+
requests.columnsRequest = {
|
|
332
|
+
count,
|
|
333
|
+
startSlot: dataStartSlot,
|
|
334
|
+
columns: Array.from(neededColumns),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
259
337
|
} else if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
|
|
260
338
|
requests.blobsRequest = {
|
|
261
339
|
count,
|
|
@@ -272,6 +350,36 @@ export class Batch {
|
|
|
272
350
|
};
|
|
273
351
|
}
|
|
274
352
|
|
|
353
|
+
// Only the first batch of a SyncChain may need the dangling-parent payload by-root.
|
|
354
|
+
if (blocks.length > 0 && this.shouldDownloadParentEnvelope(blocks[0].getBlock())) {
|
|
355
|
+
// shouldDownloadParentEnvelope() = true means there are at least 1 block
|
|
356
|
+
const parentRoot = blocks[0].getBlock().message.parentRoot;
|
|
357
|
+
if (!byteArrayEquals(parentRoot, ZERO_HASH)) {
|
|
358
|
+
const parentRootHex = toRootHex(parentRoot);
|
|
359
|
+
let parentPayloadInput: PayloadEnvelopeInput | undefined;
|
|
360
|
+
if (this.state.payloadEnvelopes) {
|
|
361
|
+
for (const pi of this.state.payloadEnvelopes.values()) {
|
|
362
|
+
if (pi.blockRootHex === parentRootHex) {
|
|
363
|
+
parentPayloadInput = pi;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const needsEnvelope = !parentPayloadInput?.hasPayloadEnvelope();
|
|
370
|
+
const missingColumns = parentPayloadInput
|
|
371
|
+
? parentPayloadInput.getMissingSampledColumnMeta().missing
|
|
372
|
+
: this.custodyConfig.sampledColumns;
|
|
373
|
+
|
|
374
|
+
if (needsEnvelope || missingColumns.length > 0) {
|
|
375
|
+
requests.parentPayloadRequest = {
|
|
376
|
+
...(needsEnvelope ? {envelopeBlockRoot: parentRoot} : {}),
|
|
377
|
+
...(missingColumns.length > 0 ? {blockRoot: parentRoot, columns: missingColumns} : {}),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
275
383
|
return requests;
|
|
276
384
|
}
|
|
277
385
|
|
|
@@ -283,24 +391,28 @@ export class Batch {
|
|
|
283
391
|
return this.requests;
|
|
284
392
|
}
|
|
285
393
|
|
|
286
|
-
// post-fulu we need to ensure that we only request columns that the peer has advertised
|
|
287
|
-
const {columnsRequest} = this.requests;
|
|
288
|
-
if (columnsRequest == null) {
|
|
289
|
-
return this.requests;
|
|
290
|
-
}
|
|
394
|
+
// post-fulu we need to ensure that we only request columns that the peer has advertised.
|
|
395
|
+
const {columnsRequest, parentPayloadRequest} = this.requests;
|
|
291
396
|
|
|
292
397
|
const peerColumns = new Set(peer.custodyColumns ?? []);
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
398
|
+
const filteredColumnsRequest =
|
|
399
|
+
columnsRequest != null ? columnsRequest.columns.filter((c) => peerColumns.has(c)) : null;
|
|
400
|
+
const parentColumns = parentPayloadRequest?.columns;
|
|
401
|
+
const filteredParentColumns = parentColumns != null ? parentColumns.filter((c) => peerColumns.has(c)) : null;
|
|
402
|
+
|
|
403
|
+
const updatedColumnRequest =
|
|
404
|
+
columnsRequest != null && filteredColumnsRequest != null
|
|
405
|
+
? {columnsRequest: {...columnsRequest, columns: filteredColumnsRequest}}
|
|
406
|
+
: {};
|
|
407
|
+
const updatedParentPayloadRequest =
|
|
408
|
+
parentPayloadRequest != null && filteredParentColumns != null
|
|
409
|
+
? {parentPayloadRequest: {...parentPayloadRequest, columns: filteredParentColumns}}
|
|
410
|
+
: {};
|
|
297
411
|
|
|
298
412
|
return {
|
|
299
413
|
...this.requests,
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
columns: requestedColumns,
|
|
303
|
-
},
|
|
414
|
+
...updatedColumnRequest,
|
|
415
|
+
...updatedParentPayloadRequest,
|
|
304
416
|
};
|
|
305
417
|
}
|
|
306
418
|
|
|
@@ -311,6 +423,35 @@ export class Batch {
|
|
|
311
423
|
return [...this.failedDownloadAttempts, ...this.failedProcessingAttempts.flatMap((a) => a.peers)];
|
|
312
424
|
}
|
|
313
425
|
|
|
426
|
+
/**
|
|
427
|
+
* True only if the peer has already returned a successful response for the current request.
|
|
428
|
+
* A by_range success may update `this.requests` to parent_payload, and the same peer is then
|
|
429
|
+
* still eligible for the newly discovered parent payload data.
|
|
430
|
+
* For by_range, a peer that previously succeeded with a superset of requested columns is skipped.
|
|
431
|
+
*/
|
|
432
|
+
hasPeerSucceededCurrentRequest(peer: PeerSyncMeta): boolean {
|
|
433
|
+
const successfulDownload = this.successfulDownloads.get(peer.peerId);
|
|
434
|
+
if (successfulDownload == null) return false;
|
|
435
|
+
|
|
436
|
+
const request = getTrackedRequest(this.getRequestsForPeer(peer));
|
|
437
|
+
if (request.parentPayload) return successfulDownload.parentPayload;
|
|
438
|
+
|
|
439
|
+
const requestByRangeColumns = request.byRangeColumns;
|
|
440
|
+
|
|
441
|
+
if (requestByRangeColumns.size === 0) {
|
|
442
|
+
// this means a download blocks/envelops by_range only
|
|
443
|
+
// don't do that again if we already did it
|
|
444
|
+
// see https://github.com/ChainSafe/lodestar/issues/9357
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return [...requestByRangeColumns].every((column) => successfulDownload.byRangeColumns.has(column));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private getSuccessfulPeers(): PeerIdStr[] {
|
|
452
|
+
return Array.from(this.successfulDownloads.keys());
|
|
453
|
+
}
|
|
454
|
+
|
|
314
455
|
getMetadata(): BatchMetadata {
|
|
315
456
|
const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
|
|
316
457
|
const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
|
|
@@ -345,14 +486,17 @@ export class Batch {
|
|
|
345
486
|
/**
|
|
346
487
|
* AwaitingDownload -> Downloading
|
|
347
488
|
*/
|
|
348
|
-
startDownloading(peer:
|
|
489
|
+
startDownloading(peer: PeerSyncMeta): void {
|
|
349
490
|
if (this.state.status !== BatchStatus.AwaitingDownload) {
|
|
350
491
|
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingDownload));
|
|
351
492
|
}
|
|
352
493
|
|
|
494
|
+
const request = getTrackedRequest(this.getRequestsForPeer(peer));
|
|
495
|
+
|
|
353
496
|
this.state = {
|
|
354
497
|
status: BatchStatus.Downloading,
|
|
355
|
-
peer,
|
|
498
|
+
peer: peer.peerId,
|
|
499
|
+
request,
|
|
356
500
|
blocks: this.state.blocks,
|
|
357
501
|
payloadEnvelopes: this.state.payloadEnvelopes,
|
|
358
502
|
};
|
|
@@ -373,13 +517,27 @@ export class Batch {
|
|
|
373
517
|
// ensure that blocks are always sorted before getting stored on the batch.state or being used to getRequests
|
|
374
518
|
blocks.sort((a, b) => a.slot - b.slot);
|
|
375
519
|
|
|
376
|
-
this.
|
|
520
|
+
const successfulDownload = this.successfulDownloads.get(peer) ?? {
|
|
521
|
+
parentPayload: false,
|
|
522
|
+
byRangeColumns: new Set<number>(),
|
|
523
|
+
};
|
|
524
|
+
successfulDownload.parentPayload ||= this.state.request.parentPayload;
|
|
525
|
+
if (!this.state.request.parentPayload) {
|
|
526
|
+
for (const column of this.state.request.byRangeColumns) {
|
|
527
|
+
successfulDownload.byRangeColumns.add(column);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
this.successfulDownloads.set(peer, successfulDownload);
|
|
377
531
|
|
|
378
532
|
let allComplete = true;
|
|
379
533
|
const slots = new Set<number>();
|
|
380
534
|
for (const block of blocks) {
|
|
381
535
|
slots.add(block.slot);
|
|
382
|
-
|
|
536
|
+
const dataComplete = isBlockInputColumns(block)
|
|
537
|
+
? // by_range needs to download all columns
|
|
538
|
+
block.hasBlock() && block.hasComputedAllData()
|
|
539
|
+
: block.hasBlockAndAllData();
|
|
540
|
+
if (!dataComplete) {
|
|
383
541
|
allComplete = false;
|
|
384
542
|
}
|
|
385
543
|
}
|
|
@@ -395,11 +553,46 @@ export class Batch {
|
|
|
395
553
|
}
|
|
396
554
|
const newPayloadEnvelopes = payloadEnvelopes ?? this.state.payloadEnvelopes;
|
|
397
555
|
|
|
556
|
+
if (allComplete && isForkPostGloas(this.forkName)) {
|
|
557
|
+
for (const block of blocks) {
|
|
558
|
+
const payloadInput = newPayloadEnvelopes?.get(block.slot);
|
|
559
|
+
// only need to make sure envelope has all columns, not all blocks have payload
|
|
560
|
+
// assertLinearChainSegment() was called before reaching this
|
|
561
|
+
if (payloadInput?.hasPayloadEnvelope() && !payloadInput.hasComputedAllData()) {
|
|
562
|
+
allComplete = false;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// First batch of a sync chain must additionally have the dangling-parent payload fully
|
|
569
|
+
// present, otherwise `processBlocks` will throw PARENT_PAYLOAD_UNKNOWN. The parent's
|
|
570
|
+
// `PayloadEnvelopeInput` is identified by `blockRootHex` matching `blocks[0].parentRoot`.
|
|
571
|
+
if (allComplete && blocks.length > 0 && this.shouldDownloadParentEnvelope(blocks[0].getBlock())) {
|
|
572
|
+
const parentRoot = blocks[0].getBlock().message.parentRoot;
|
|
573
|
+
// Genesis has no parent payload — nothing to wait for.
|
|
574
|
+
if (!byteArrayEquals(parentRoot, ZERO_HASH)) {
|
|
575
|
+
const parentRootHex = toRootHex(parentRoot);
|
|
576
|
+
let parentPayloadComplete = false;
|
|
577
|
+
if (newPayloadEnvelopes) {
|
|
578
|
+
for (const payloadInput of newPayloadEnvelopes.values()) {
|
|
579
|
+
if (payloadInput.blockRootHex === parentRootHex) {
|
|
580
|
+
parentPayloadComplete = payloadInput.hasPayloadEnvelope() && payloadInput.hasComputedAllData();
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (!parentPayloadComplete) {
|
|
586
|
+
allComplete = false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
398
591
|
if (allComplete) {
|
|
399
592
|
this.state = {status: BatchStatus.AwaitingProcessing, blocks, payloadEnvelopes: newPayloadEnvelopes};
|
|
400
593
|
} else {
|
|
401
|
-
this.requests = this.getRequests(blocks);
|
|
402
594
|
this.state = {status: BatchStatus.AwaitingDownload, blocks, payloadEnvelopes: newPayloadEnvelopes};
|
|
595
|
+
this.requests = this.getRequests(blocks);
|
|
403
596
|
}
|
|
404
597
|
|
|
405
598
|
return this.state as DownloadSuccessState;
|
|
@@ -425,6 +618,22 @@ export class Batch {
|
|
|
425
618
|
};
|
|
426
619
|
}
|
|
427
620
|
|
|
621
|
+
/**
|
|
622
|
+
* Downloading -> AwaitingDownload (without counting as a failed attempt).
|
|
623
|
+
* Used when the peer rate-limited us — the request was never actually served.
|
|
624
|
+
*/
|
|
625
|
+
downloadingRateLimited(): void {
|
|
626
|
+
if (this.state.status !== BatchStatus.Downloading) {
|
|
627
|
+
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this.state = {
|
|
631
|
+
status: BatchStatus.AwaitingDownload,
|
|
632
|
+
blocks: this.state.blocks,
|
|
633
|
+
payloadEnvelopes: this.state.payloadEnvelopes,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
428
637
|
/**
|
|
429
638
|
* AwaitingProcessing -> Processing
|
|
430
639
|
*/
|
|
@@ -440,10 +649,10 @@ export class Batch {
|
|
|
440
649
|
const blocks = this.state.blocks;
|
|
441
650
|
const payloadEnvelopes = this.state.payloadEnvelopes;
|
|
442
651
|
const hash = hashBlocks(blocks, this.config); // tracks blocks to report peer on processing error
|
|
443
|
-
// Reset
|
|
444
|
-
// that the data came from will be handled by the Attempt that goes for processing
|
|
445
|
-
const peers = this.
|
|
446
|
-
this.
|
|
652
|
+
// Reset successfulDownloads in case another download attempt needs to be made. When Attempt is successful or not
|
|
653
|
+
// the peers that the data came from will be handled by the Attempt that goes for processing.
|
|
654
|
+
const peers = this.getSuccessfulPeers();
|
|
655
|
+
this.successfulDownloads.clear();
|
|
447
656
|
this.state = {status: BatchStatus.Processing, blocks, payloadEnvelopes, attempt: {peers, hash}};
|
|
448
657
|
return {blocks, payloadEnvelopes, peers};
|
|
449
658
|
}
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {Epoch, Root, Slot} from "@lodestar/types";
|
|
2
|
+
import {Epoch, Root, Slot, gloas} from "@lodestar/types";
|
|
3
3
|
import {ErrorAborted, LodestarError, Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
4
4
|
import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
|
|
5
5
|
import {BlockInputErrorCode} from "../../chain/blocks/blockInput/errors.js";
|
|
@@ -17,6 +17,7 @@ import {PeerIdStr} from "../../util/peerId.js";
|
|
|
17
17
|
import {WarnResult, wrapError} from "../../util/wrapError.js";
|
|
18
18
|
import {BATCH_BUFFER_SIZE, EPOCHS_PER_BATCH, MAX_LOOK_AHEAD_EPOCHS} from "../constants.js";
|
|
19
19
|
import {DownloadByRangeError, DownloadByRangeErrorCode} from "../utils/downloadByRange.js";
|
|
20
|
+
import {getRateLimitedUntilMs} from "../utils/rateLimit.js";
|
|
20
21
|
import {RangeSyncType} from "../utils/remoteSyncType.js";
|
|
21
22
|
import {Batch, BatchError, BatchErrorCode, BatchMetadata, BatchStatus} from "./batch.js";
|
|
22
23
|
import {
|
|
@@ -139,20 +140,33 @@ export class SyncChain {
|
|
|
139
140
|
private readonly batchProcessor = new ItTrigger();
|
|
140
141
|
/** Sorted map of batches undergoing some kind of processing. */
|
|
141
142
|
private readonly batches = new Map<Epoch, Batch>();
|
|
143
|
+
/**
|
|
144
|
+
* `true` until the first `Batch` is constructed via `includeNextBatch`
|
|
145
|
+
*/
|
|
146
|
+
private isFirstBatch = true;
|
|
142
147
|
private readonly peerset = new Map<PeerIdStr, ChainTarget>();
|
|
148
|
+
/**
|
|
149
|
+
* Tracks peers that have rate-limited us, mapped to the timestamp (ms) until which we should avoid them.
|
|
150
|
+
* This is a sync-layer optimization to avoid assigning batches to backed-off peers.
|
|
151
|
+
* The reqresp SelfRateLimiter independently enforces backoff at the protocol level as a safety net.
|
|
152
|
+
*/
|
|
153
|
+
private readonly rateLimitedPeers = new Map<PeerIdStr, number>();
|
|
154
|
+
private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
|
|
143
155
|
|
|
144
156
|
private readonly logger: Logger;
|
|
145
157
|
private readonly config: ChainForkConfig;
|
|
146
158
|
private readonly clock: IClock;
|
|
147
159
|
private readonly metrics: Metrics | null;
|
|
148
160
|
private readonly custodyConfig: CustodyConfig;
|
|
161
|
+
private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
|
|
149
162
|
|
|
150
163
|
constructor(
|
|
151
164
|
initialBatchEpoch: Epoch,
|
|
152
165
|
initialTarget: ChainTarget,
|
|
153
166
|
syncType: RangeSyncType,
|
|
154
167
|
fns: SyncChainFns,
|
|
155
|
-
modules: SyncChainModules
|
|
168
|
+
modules: SyncChainModules,
|
|
169
|
+
latestBid: gloas.ExecutionPayloadBid | undefined
|
|
156
170
|
) {
|
|
157
171
|
const {config, clock, custodyConfig, logger, metrics} = modules;
|
|
158
172
|
this.firstBatchEpoch = initialBatchEpoch;
|
|
@@ -168,6 +182,7 @@ export class SyncChain {
|
|
|
168
182
|
this.clock = clock;
|
|
169
183
|
this.metrics = metrics;
|
|
170
184
|
this.custodyConfig = custodyConfig;
|
|
185
|
+
this.latestBid = latestBid;
|
|
171
186
|
this.logger = logger;
|
|
172
187
|
this.logId = `${syncType}-${nextChainId++}`;
|
|
173
188
|
|
|
@@ -222,6 +237,7 @@ export class SyncChain {
|
|
|
222
237
|
*/
|
|
223
238
|
stopSyncing(): void {
|
|
224
239
|
this.status = SyncChainStatus.Stopped;
|
|
240
|
+
this.clearRateLimitBackoffTimer();
|
|
225
241
|
this.logger.debug("SyncChain stopSyncing", {id: this.logId});
|
|
226
242
|
}
|
|
227
243
|
|
|
@@ -230,6 +246,7 @@ export class SyncChain {
|
|
|
230
246
|
*/
|
|
231
247
|
remove(): void {
|
|
232
248
|
this.logger.debug("SyncChain remove", {id: this.logId});
|
|
249
|
+
this.clearRateLimitBackoffTimer();
|
|
233
250
|
this.batchProcessor.end(new ErrorAborted("SyncChain"));
|
|
234
251
|
}
|
|
235
252
|
|
|
@@ -248,6 +265,7 @@ export class SyncChain {
|
|
|
248
265
|
*/
|
|
249
266
|
removePeer(peerId: PeerIdStr): boolean {
|
|
250
267
|
const deleted = this.peerset.delete(peerId);
|
|
268
|
+
this.rateLimitedPeers.delete(peerId);
|
|
251
269
|
this.computeTarget();
|
|
252
270
|
return deleted;
|
|
253
271
|
}
|
|
@@ -351,6 +369,8 @@ export class SyncChain {
|
|
|
351
369
|
}
|
|
352
370
|
|
|
353
371
|
throw e;
|
|
372
|
+
} finally {
|
|
373
|
+
this.clearRateLimitBackoffTimer();
|
|
354
374
|
}
|
|
355
375
|
}
|
|
356
376
|
|
|
@@ -374,6 +394,44 @@ export class SyncChain {
|
|
|
374
394
|
}
|
|
375
395
|
}
|
|
376
396
|
|
|
397
|
+
private scheduleRateLimitBackoffRetry(): void {
|
|
398
|
+
this.clearRateLimitBackoffTimer();
|
|
399
|
+
|
|
400
|
+
if (this.status !== SyncChainStatus.Syncing || this.rateLimitedPeers.size === 0) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const now = Date.now();
|
|
405
|
+
let retryAt: number | null = null;
|
|
406
|
+
for (const [peerId, rateLimitedUntil] of this.rateLimitedPeers.entries()) {
|
|
407
|
+
if (rateLimitedUntil <= now) {
|
|
408
|
+
this.rateLimitedPeers.delete(peerId);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
retryAt = Math.min(retryAt ?? rateLimitedUntil, rateLimitedUntil);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (retryAt === null) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this.rateLimitBackoffTimeout = setTimeout(
|
|
419
|
+
() => {
|
|
420
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
421
|
+
this.triggerBatchDownloader();
|
|
422
|
+
this.scheduleRateLimitBackoffRetry();
|
|
423
|
+
},
|
|
424
|
+
Math.max(0, retryAt - now)
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private clearRateLimitBackoffTimer(): void {
|
|
429
|
+
if (this.rateLimitBackoffTimeout !== undefined) {
|
|
430
|
+
clearTimeout(this.rateLimitBackoffTimeout);
|
|
431
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
377
435
|
/**
|
|
378
436
|
* Attempts to request the next required batches from the peer pool if the chain is syncing.
|
|
379
437
|
* It will exhaust the peer pool and left over batches until the batch buffer is reached.
|
|
@@ -383,8 +441,18 @@ export class SyncChain {
|
|
|
383
441
|
return;
|
|
384
442
|
}
|
|
385
443
|
|
|
444
|
+
const now = Date.now();
|
|
386
445
|
const peersSyncInfo: PeerSyncInfo[] = [];
|
|
387
446
|
for (const [peerId, target] of this.peerset.entries()) {
|
|
447
|
+
// Skip peers that are currently in rate-limit backoff
|
|
448
|
+
const rateLimitedUntil = this.rateLimitedPeers.get(peerId);
|
|
449
|
+
if (rateLimitedUntil !== undefined) {
|
|
450
|
+
if (now < rateLimitedUntil) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
this.rateLimitedPeers.delete(peerId);
|
|
454
|
+
}
|
|
455
|
+
|
|
388
456
|
try {
|
|
389
457
|
peersSyncInfo.push({...this.getConnectedPeerSyncMeta(peerId), target});
|
|
390
458
|
} catch (e) {
|
|
@@ -458,7 +526,17 @@ export class SyncChain {
|
|
|
458
526
|
return null;
|
|
459
527
|
}
|
|
460
528
|
|
|
461
|
-
const batch = new Batch(
|
|
529
|
+
const batch = new Batch(
|
|
530
|
+
startEpoch,
|
|
531
|
+
this.config,
|
|
532
|
+
this.clock,
|
|
533
|
+
this.custodyConfig,
|
|
534
|
+
this.isFirstBatch,
|
|
535
|
+
// `latestBid` is only meaningful for the first batch's parent-payload check
|
|
536
|
+
this.isFirstBatch ? this.latestBid : undefined,
|
|
537
|
+
this.target.slot
|
|
538
|
+
);
|
|
539
|
+
this.isFirstBatch = false;
|
|
462
540
|
this.batches.set(startEpoch, batch);
|
|
463
541
|
return batch;
|
|
464
542
|
}
|
|
@@ -473,7 +551,7 @@ export class SyncChain {
|
|
|
473
551
|
peer: prettyPrintPeerIdStr(peer.peerId),
|
|
474
552
|
});
|
|
475
553
|
try {
|
|
476
|
-
batch.startDownloading(peer
|
|
554
|
+
batch.startDownloading(peer);
|
|
477
555
|
|
|
478
556
|
// wrapError ensures to never call both batch success() and batch error()
|
|
479
557
|
const res = await wrapError(this.downloadByRange(peer, batch, this.syncType));
|
|
@@ -503,6 +581,8 @@ export class SyncChain {
|
|
|
503
581
|
case DownloadByRangeErrorCode.OUT_OF_ORDER_BLOCKS:
|
|
504
582
|
case DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS:
|
|
505
583
|
case DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH:
|
|
584
|
+
case DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT:
|
|
585
|
+
case DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT:
|
|
506
586
|
case BlobSidecarErrorCode.INCLUSION_PROOF_INVALID:
|
|
507
587
|
case BlobSidecarErrorCode.INVALID_KZG_PROOF_BATCH:
|
|
508
588
|
case DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT:
|
|
@@ -516,7 +596,16 @@ export class SyncChain {
|
|
|
516
596
|
{id: this.logId, ...batch.getMetadata(), peer: prettyPrintPeerIdStr(peer.peerId)},
|
|
517
597
|
res.err
|
|
518
598
|
);
|
|
519
|
-
|
|
599
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(res.err);
|
|
600
|
+
if (rateLimitedUntilMs !== null) {
|
|
601
|
+
// Peer rate-limited us — don't count as a failed download attempt and mark peer for backoff
|
|
602
|
+
this.rateLimitedPeers.set(peer.peerId, rateLimitedUntilMs);
|
|
603
|
+
this.scheduleRateLimitBackoffRetry();
|
|
604
|
+
batch.downloadingRateLimited();
|
|
605
|
+
this.triggerBatchDownloader();
|
|
606
|
+
} else {
|
|
607
|
+
batch.downloadingError(peer.peerId); // Throws after MAX_DOWNLOAD_ATTEMPTS
|
|
608
|
+
}
|
|
520
609
|
} else {
|
|
521
610
|
this.logger.verbose("Batch download success", {
|
|
522
611
|
id: this.logId,
|