@lodestar/beacon-node 1.43.0-dev.b05ea98d04 → 1.43.0-dev.b0e1143d06
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +12 -1
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +6 -7
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +8 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +19 -0
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +1 -3
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +29 -26
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +5 -4
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +15 -1
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +7 -6
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +21 -5
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +4 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +749 -2
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +3 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +31 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js +1 -1
- package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts +9 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.d.ts.map +1 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js +61 -0
- package/lib/network/reqresp/handlers/beaconBlocksByHead.js.map +1 -0
- package/lib/network/reqresp/handlers/index.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/index.js +5 -0
- package/lib/network/reqresp/handlers/index.js.map +1 -1
- package/lib/network/reqresp/interface.d.ts +1 -1
- package/lib/network/reqresp/interface.js +1 -1
- package/lib/network/reqresp/protocols.d.ts +1 -0
- package/lib/network/reqresp/protocols.d.ts.map +1 -1
- package/lib/network/reqresp/protocols.js +5 -0
- package/lib/network/reqresp/protocols.js.map +1 -1
- package/lib/network/reqresp/rateLimit.d.ts.map +1 -1
- package/lib/network/reqresp/rateLimit.js +4 -0
- package/lib/network/reqresp/rateLimit.js.map +1 -1
- package/lib/network/reqresp/score.d.ts.map +1 -1
- package/lib/network/reqresp/score.js +1 -0
- package/lib/network/reqresp/score.js.map +1 -1
- package/lib/network/reqresp/types.d.ts +3 -0
- package/lib/network/reqresp/types.d.ts.map +1 -1
- package/lib/network/reqresp/types.js +3 -0
- package/lib/network/reqresp/types.js.map +1 -1
- package/lib/sync/constants.d.ts +5 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +5 -1
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +27 -6
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +149 -26
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +10 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +55 -7
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +54 -5
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/range/utils/peerBalancer.d.ts +2 -1
- package/lib/sync/range/utils/peerBalancer.d.ts.map +1 -1
- package/lib/sync/range/utils/peerBalancer.js +8 -4
- package/lib/sync/range/utils/peerBalancer.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +7 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +127 -12
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +36 -9
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +87 -38
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/rateLimit.d.ts +2 -0
- package/lib/sync/utils/rateLimit.d.ts.map +1 -0
- package/lib/sync/utils/rateLimit.js +15 -0
- package/lib/sync/utils/rateLimit.js.map +1 -0
- package/package.json +16 -16
- package/src/chain/blocks/importExecutionPayload.ts +18 -6
- package/src/chain/blocks/index.ts +5 -7
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +27 -1
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
- package/src/chain/blocks/utils/chainSegment.ts +30 -27
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +7 -4
- package/src/chain/chain.ts +17 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +23 -6
- package/src/metrics/metrics/lodestar.ts +6 -0
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +12 -0
- package/src/network/processor/gossipHandlers.ts +35 -1
- package/src/network/reqresp/ReqRespBeaconNode.ts +1 -0
- package/src/network/reqresp/handlers/beaconBlocksByHead.ts +91 -0
- package/src/network/reqresp/handlers/index.ts +5 -0
- package/src/network/reqresp/interface.ts +1 -1
- package/src/network/reqresp/protocols.ts +6 -0
- package/src/network/reqresp/rateLimit.ts +4 -0
- package/src/network/reqresp/score.ts +1 -0
- package/src/network/reqresp/types.ts +5 -0
- package/src/sync/constants.ts +5 -1
- package/src/sync/range/batch.ts +188 -30
- package/src/sync/range/chain.ts +72 -13
- package/src/sync/range/range.ts +68 -5
- package/src/sync/range/utils/peerBalancer.ts +9 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +157 -12
- package/src/sync/utils/downloadByRange.ts +153 -39
- package/src/sync/utils/rateLimit.ts +16 -0
package/src/sync/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
|
*/
|
|
@@ -292,6 +350,36 @@ export class Batch {
|
|
|
292
350
|
};
|
|
293
351
|
}
|
|
294
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
|
+
|
|
295
383
|
return requests;
|
|
296
384
|
}
|
|
297
385
|
|
|
@@ -303,24 +391,28 @@ export class Batch {
|
|
|
303
391
|
return this.requests;
|
|
304
392
|
}
|
|
305
393
|
|
|
306
|
-
// post-fulu we need to ensure that we only request columns that the peer has advertised
|
|
307
|
-
const {columnsRequest} = this.requests;
|
|
308
|
-
if (columnsRequest == null) {
|
|
309
|
-
return this.requests;
|
|
310
|
-
}
|
|
394
|
+
// post-fulu we need to ensure that we only request columns that the peer has advertised.
|
|
395
|
+
const {columnsRequest, parentPayloadRequest} = this.requests;
|
|
311
396
|
|
|
312
397
|
const peerColumns = new Set(peer.custodyColumns ?? []);
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
+
: {};
|
|
317
411
|
|
|
318
412
|
return {
|
|
319
413
|
...this.requests,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
columns: requestedColumns,
|
|
323
|
-
},
|
|
414
|
+
...updatedColumnRequest,
|
|
415
|
+
...updatedParentPayloadRequest,
|
|
324
416
|
};
|
|
325
417
|
}
|
|
326
418
|
|
|
@@ -331,6 +423,35 @@ export class Batch {
|
|
|
331
423
|
return [...this.failedDownloadAttempts, ...this.failedProcessingAttempts.flatMap((a) => a.peers)];
|
|
332
424
|
}
|
|
333
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
|
+
|
|
334
455
|
getMetadata(): BatchMetadata {
|
|
335
456
|
const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
|
|
336
457
|
const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
|
|
@@ -365,14 +486,17 @@ export class Batch {
|
|
|
365
486
|
/**
|
|
366
487
|
* AwaitingDownload -> Downloading
|
|
367
488
|
*/
|
|
368
|
-
startDownloading(peer:
|
|
489
|
+
startDownloading(peer: PeerSyncMeta): void {
|
|
369
490
|
if (this.state.status !== BatchStatus.AwaitingDownload) {
|
|
370
491
|
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingDownload));
|
|
371
492
|
}
|
|
372
493
|
|
|
494
|
+
const request = getTrackedRequest(this.getRequestsForPeer(peer));
|
|
495
|
+
|
|
373
496
|
this.state = {
|
|
374
497
|
status: BatchStatus.Downloading,
|
|
375
|
-
peer,
|
|
498
|
+
peer: peer.peerId,
|
|
499
|
+
request,
|
|
376
500
|
blocks: this.state.blocks,
|
|
377
501
|
payloadEnvelopes: this.state.payloadEnvelopes,
|
|
378
502
|
};
|
|
@@ -393,7 +517,17 @@ export class Batch {
|
|
|
393
517
|
// ensure that blocks are always sorted before getting stored on the batch.state or being used to getRequests
|
|
394
518
|
blocks.sort((a, b) => a.slot - b.slot);
|
|
395
519
|
|
|
396
|
-
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);
|
|
397
531
|
|
|
398
532
|
let allComplete = true;
|
|
399
533
|
const slots = new Set<number>();
|
|
@@ -422,14 +556,38 @@ export class Batch {
|
|
|
422
556
|
if (allComplete && isForkPostGloas(this.forkName)) {
|
|
423
557
|
for (const block of blocks) {
|
|
424
558
|
const payloadInput = newPayloadEnvelopes?.get(block.slot);
|
|
425
|
-
//
|
|
426
|
-
|
|
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()) {
|
|
427
562
|
allComplete = false;
|
|
428
563
|
break;
|
|
429
564
|
}
|
|
430
565
|
}
|
|
431
566
|
}
|
|
432
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
|
+
|
|
433
591
|
if (allComplete) {
|
|
434
592
|
this.state = {status: BatchStatus.AwaitingProcessing, blocks, payloadEnvelopes: newPayloadEnvelopes};
|
|
435
593
|
} else {
|
|
@@ -491,10 +649,10 @@ export class Batch {
|
|
|
491
649
|
const blocks = this.state.blocks;
|
|
492
650
|
const payloadEnvelopes = this.state.payloadEnvelopes;
|
|
493
651
|
const hash = hashBlocks(blocks, this.config); // tracks blocks to report peer on processing error
|
|
494
|
-
// Reset
|
|
495
|
-
// that the data came from will be handled by the Attempt that goes for processing
|
|
496
|
-
const peers = this.
|
|
497
|
-
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();
|
|
498
656
|
this.state = {status: BatchStatus.Processing, blocks, payloadEnvelopes, attempt: {peers, hash}};
|
|
499
657
|
return {blocks, payloadEnvelopes, peers};
|
|
500
658
|
}
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {
|
|
3
|
-
import {Epoch, Root, Slot} from "@lodestar/types";
|
|
2
|
+
import {Epoch, Root, Slot, gloas} from "@lodestar/types";
|
|
4
3
|
import {ErrorAborted, LodestarError, Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
5
4
|
import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
|
|
6
5
|
import {BlockInputErrorCode} from "../../chain/blocks/blockInput/errors.js";
|
|
@@ -16,13 +15,9 @@ import {CustodyConfig} from "../../util/dataColumns.js";
|
|
|
16
15
|
import {ItTrigger} from "../../util/itTrigger.js";
|
|
17
16
|
import {PeerIdStr} from "../../util/peerId.js";
|
|
18
17
|
import {WarnResult, wrapError} from "../../util/wrapError.js";
|
|
19
|
-
import {
|
|
20
|
-
BATCH_BUFFER_SIZE,
|
|
21
|
-
EPOCHS_PER_BATCH,
|
|
22
|
-
MAX_LOOK_AHEAD_EPOCHS,
|
|
23
|
-
RATE_LIMITED_PEER_BACKOFF_MS,
|
|
24
|
-
} from "../constants.js";
|
|
18
|
+
import {BATCH_BUFFER_SIZE, EPOCHS_PER_BATCH, MAX_LOOK_AHEAD_EPOCHS} from "../constants.js";
|
|
25
19
|
import {DownloadByRangeError, DownloadByRangeErrorCode} from "../utils/downloadByRange.js";
|
|
20
|
+
import {getRateLimitedUntilMs} from "../utils/rateLimit.js";
|
|
26
21
|
import {RangeSyncType} from "../utils/remoteSyncType.js";
|
|
27
22
|
import {Batch, BatchError, BatchErrorCode, BatchMetadata, BatchStatus} from "./batch.js";
|
|
28
23
|
import {
|
|
@@ -145,6 +140,10 @@ export class SyncChain {
|
|
|
145
140
|
private readonly batchProcessor = new ItTrigger();
|
|
146
141
|
/** Sorted map of batches undergoing some kind of processing. */
|
|
147
142
|
private readonly batches = new Map<Epoch, Batch>();
|
|
143
|
+
/**
|
|
144
|
+
* `true` until the first `Batch` is constructed via `includeNextBatch`
|
|
145
|
+
*/
|
|
146
|
+
private isFirstBatch = true;
|
|
148
147
|
private readonly peerset = new Map<PeerIdStr, ChainTarget>();
|
|
149
148
|
/**
|
|
150
149
|
* Tracks peers that have rate-limited us, mapped to the timestamp (ms) until which we should avoid them.
|
|
@@ -152,19 +151,22 @@ export class SyncChain {
|
|
|
152
151
|
* The reqresp SelfRateLimiter independently enforces backoff at the protocol level as a safety net.
|
|
153
152
|
*/
|
|
154
153
|
private readonly rateLimitedPeers = new Map<PeerIdStr, number>();
|
|
154
|
+
private rateLimitBackoffTimeout: NodeJS.Timeout | undefined;
|
|
155
155
|
|
|
156
156
|
private readonly logger: Logger;
|
|
157
157
|
private readonly config: ChainForkConfig;
|
|
158
158
|
private readonly clock: IClock;
|
|
159
159
|
private readonly metrics: Metrics | null;
|
|
160
160
|
private readonly custodyConfig: CustodyConfig;
|
|
161
|
+
private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
|
|
161
162
|
|
|
162
163
|
constructor(
|
|
163
164
|
initialBatchEpoch: Epoch,
|
|
164
165
|
initialTarget: ChainTarget,
|
|
165
166
|
syncType: RangeSyncType,
|
|
166
167
|
fns: SyncChainFns,
|
|
167
|
-
modules: SyncChainModules
|
|
168
|
+
modules: SyncChainModules,
|
|
169
|
+
latestBid: gloas.ExecutionPayloadBid | undefined
|
|
168
170
|
) {
|
|
169
171
|
const {config, clock, custodyConfig, logger, metrics} = modules;
|
|
170
172
|
this.firstBatchEpoch = initialBatchEpoch;
|
|
@@ -180,6 +182,7 @@ export class SyncChain {
|
|
|
180
182
|
this.clock = clock;
|
|
181
183
|
this.metrics = metrics;
|
|
182
184
|
this.custodyConfig = custodyConfig;
|
|
185
|
+
this.latestBid = latestBid;
|
|
183
186
|
this.logger = logger;
|
|
184
187
|
this.logId = `${syncType}-${nextChainId++}`;
|
|
185
188
|
|
|
@@ -234,6 +237,7 @@ export class SyncChain {
|
|
|
234
237
|
*/
|
|
235
238
|
stopSyncing(): void {
|
|
236
239
|
this.status = SyncChainStatus.Stopped;
|
|
240
|
+
this.clearRateLimitBackoffTimer();
|
|
237
241
|
this.logger.debug("SyncChain stopSyncing", {id: this.logId});
|
|
238
242
|
}
|
|
239
243
|
|
|
@@ -242,6 +246,7 @@ export class SyncChain {
|
|
|
242
246
|
*/
|
|
243
247
|
remove(): void {
|
|
244
248
|
this.logger.debug("SyncChain remove", {id: this.logId});
|
|
249
|
+
this.clearRateLimitBackoffTimer();
|
|
245
250
|
this.batchProcessor.end(new ErrorAborted("SyncChain"));
|
|
246
251
|
}
|
|
247
252
|
|
|
@@ -364,6 +369,8 @@ export class SyncChain {
|
|
|
364
369
|
}
|
|
365
370
|
|
|
366
371
|
throw e;
|
|
372
|
+
} finally {
|
|
373
|
+
this.clearRateLimitBackoffTimer();
|
|
367
374
|
}
|
|
368
375
|
}
|
|
369
376
|
|
|
@@ -387,6 +394,44 @@ export class SyncChain {
|
|
|
387
394
|
}
|
|
388
395
|
}
|
|
389
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
|
+
|
|
390
435
|
/**
|
|
391
436
|
* Attempts to request the next required batches from the peer pool if the chain is syncing.
|
|
392
437
|
* It will exhaust the peer pool and left over batches until the batch buffer is reached.
|
|
@@ -481,7 +526,17 @@ export class SyncChain {
|
|
|
481
526
|
return null;
|
|
482
527
|
}
|
|
483
528
|
|
|
484
|
-
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;
|
|
485
540
|
this.batches.set(startEpoch, batch);
|
|
486
541
|
return batch;
|
|
487
542
|
}
|
|
@@ -496,7 +551,7 @@ export class SyncChain {
|
|
|
496
551
|
peer: prettyPrintPeerIdStr(peer.peerId),
|
|
497
552
|
});
|
|
498
553
|
try {
|
|
499
|
-
batch.startDownloading(peer
|
|
554
|
+
batch.startDownloading(peer);
|
|
500
555
|
|
|
501
556
|
// wrapError ensures to never call both batch success() and batch error()
|
|
502
557
|
const res = await wrapError(this.downloadByRange(peer, batch, this.syncType));
|
|
@@ -526,6 +581,8 @@ export class SyncChain {
|
|
|
526
581
|
case DownloadByRangeErrorCode.OUT_OF_ORDER_BLOCKS:
|
|
527
582
|
case DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS:
|
|
528
583
|
case DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH:
|
|
584
|
+
case DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT:
|
|
585
|
+
case DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT:
|
|
529
586
|
case BlobSidecarErrorCode.INCLUSION_PROOF_INVALID:
|
|
530
587
|
case BlobSidecarErrorCode.INVALID_KZG_PROOF_BATCH:
|
|
531
588
|
case DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT:
|
|
@@ -539,9 +596,11 @@ export class SyncChain {
|
|
|
539
596
|
{id: this.logId, ...batch.getMetadata(), peer: prettyPrintPeerIdStr(peer.peerId)},
|
|
540
597
|
res.err
|
|
541
598
|
);
|
|
542
|
-
|
|
599
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(res.err);
|
|
600
|
+
if (rateLimitedUntilMs !== null) {
|
|
543
601
|
// Peer rate-limited us — don't count as a failed download attempt and mark peer for backoff
|
|
544
|
-
this.rateLimitedPeers.set(peer.peerId,
|
|
602
|
+
this.rateLimitedPeers.set(peer.peerId, rateLimitedUntilMs);
|
|
603
|
+
this.scheduleRateLimitBackoffRetry();
|
|
545
604
|
batch.downloadingRateLimited();
|
|
546
605
|
this.triggerBatchDownloader();
|
|
547
606
|
} else {
|
package/src/sync/range/range.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
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
|
-
import {Logger, toRootHex} from "@lodestar/utils";
|
|
6
|
+
import {Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
7
7
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
8
8
|
import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
|
|
9
|
+
import {assertLinearChainSegment} from "../../chain/blocks/utils/chainSegment.js";
|
|
10
|
+
import {BlockError} from "../../chain/errors/index.js";
|
|
9
11
|
import {IBeaconChain} from "../../chain/index.js";
|
|
10
12
|
import {Metrics} from "../../metrics/index.js";
|
|
11
13
|
import {INetwork} from "../../network/index.js";
|
|
12
14
|
import {PeerIdStr} from "../../util/peerId.js";
|
|
13
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
DownloadByRangeError,
|
|
17
|
+
DownloadByRangeErrorCode,
|
|
18
|
+
cacheByRangeResponses,
|
|
19
|
+
downloadByRange,
|
|
20
|
+
} from "../utils/downloadByRange.js";
|
|
14
21
|
import {RangeSyncType, getRangeSyncTarget, rangeSyncTypes} from "../utils/remoteSyncType.js";
|
|
15
22
|
import {ChainTarget, SyncChain, SyncChainDebugState, SyncChainFns} from "./chain.js";
|
|
16
23
|
import {updateChains} from "./utils/index.js";
|
|
@@ -206,14 +213,18 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
206
213
|
|
|
207
214
|
private downloadByRange: SyncChainFns["downloadByRange"] = async (peer, batch) => {
|
|
208
215
|
const batchBlocks = batch.getBlocks();
|
|
216
|
+
const requests = batch.getRequestsForPeer(peer);
|
|
217
|
+
const parentRoot = requests.parentPayloadRequest?.envelopeBlockRoot ?? requests.parentPayloadRequest?.blockRoot;
|
|
218
|
+
const parentPayloadCommitments = parentRoot ? batch.getParentPayloadCommitments(parentRoot) : undefined;
|
|
209
219
|
const {result, warnings} = await downloadByRange({
|
|
210
220
|
config: this.config,
|
|
211
221
|
network: this.network,
|
|
212
222
|
logger: this.logger,
|
|
213
223
|
peerIdStr: peer.peerId,
|
|
214
224
|
batchBlocks,
|
|
225
|
+
parentPayloadCommitments,
|
|
215
226
|
peerDasMetrics: this.chain.metrics?.peerDas,
|
|
216
|
-
...
|
|
227
|
+
...requests,
|
|
217
228
|
});
|
|
218
229
|
const {responses, payloadEnvelopes: downloadedPayloadEnvelopes} = result;
|
|
219
230
|
const {blocks, payloadEnvelopes} = cacheByRangeResponses({
|
|
@@ -227,6 +238,49 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
227
238
|
custodyConfig: this.chain.custodyConfig,
|
|
228
239
|
seenTimestampSec: Date.now() / 1000,
|
|
229
240
|
});
|
|
241
|
+
|
|
242
|
+
const segmentBlocks = blocks.filter((b) => b.hasBlock()).sort((a, b) => a.slot - b.slot);
|
|
243
|
+
const envelopeSlots = payloadEnvelopes
|
|
244
|
+
? Array.from(payloadEnvelopes.entries())
|
|
245
|
+
.filter(([, pi]) => pi.hasPayloadEnvelope())
|
|
246
|
+
.map(([slot]) => slot)
|
|
247
|
+
.sort((a, b) => a - b)
|
|
248
|
+
: [];
|
|
249
|
+
this.logger.verbose("downloadByRange batch ready", {
|
|
250
|
+
peer: peer.peerId,
|
|
251
|
+
blockSlots: prettyPrintIndices(segmentBlocks.map((b) => b.slot)),
|
|
252
|
+
envelopeSlots: prettyPrintIndices(envelopeSlots),
|
|
253
|
+
...batch.getMetadata(),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (segmentBlocks.length > 1) {
|
|
257
|
+
try {
|
|
258
|
+
assertLinearChainSegment(this.config, segmentBlocks, payloadEnvelopes, null);
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (err instanceof BlockError) {
|
|
261
|
+
this.logger.debug(
|
|
262
|
+
"downloadByRange segment validation failed",
|
|
263
|
+
{
|
|
264
|
+
peer: peer.peerId,
|
|
265
|
+
reason: err.type.code,
|
|
266
|
+
slot: err.signedBlock.message.slot,
|
|
267
|
+
detail: JSON.stringify(err.type),
|
|
268
|
+
...batch.getMetadata(),
|
|
269
|
+
},
|
|
270
|
+
err
|
|
271
|
+
);
|
|
272
|
+
// with this error, the peer will be penalized inside SyncChain
|
|
273
|
+
throw new DownloadByRangeError({
|
|
274
|
+
code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT,
|
|
275
|
+
slot: err.signedBlock.message.slot,
|
|
276
|
+
reason: err.type.code,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
throw err;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
230
284
|
return {result: {blocks, payloadEnvelopes}, warnings};
|
|
231
285
|
};
|
|
232
286
|
|
|
@@ -258,6 +312,14 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
258
312
|
private addPeerOrCreateChain(startEpoch: Epoch, target: ChainTarget, peer: PeerIdStr, syncType: RangeSyncType): void {
|
|
259
313
|
let syncChain = this.chains.get(syncType);
|
|
260
314
|
if (!syncChain) {
|
|
315
|
+
// The first batch of a new sync chain may need to detect whether the parent block was an
|
|
316
|
+
// gloas "empty" block (no envelope produced). It does so by comparing the first
|
|
317
|
+
// downloaded block's `bid.parentBlockHash` against the head state's `latestExecutionPayloadBid.blockHash`.
|
|
318
|
+
const headState = this.chain.getHeadState();
|
|
319
|
+
const latestBid = isStatePostGloas(headState)
|
|
320
|
+
? (headState as IBeaconStateViewGloas).latestExecutionPayloadBid
|
|
321
|
+
: undefined;
|
|
322
|
+
|
|
261
323
|
syncChain = new SyncChain(
|
|
262
324
|
startEpoch,
|
|
263
325
|
target,
|
|
@@ -276,7 +338,8 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
276
338
|
logger: this.logger,
|
|
277
339
|
custodyConfig: this.chain.custodyConfig,
|
|
278
340
|
metrics: this.metrics,
|
|
279
|
-
}
|
|
341
|
+
},
|
|
342
|
+
latestBid
|
|
280
343
|
);
|
|
281
344
|
this.chains.set(syncType, syncChain);
|
|
282
345
|
|
|
@@ -52,7 +52,8 @@ export class ChainPeersBalancer {
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Return the most suitable peer to retry
|
|
55
|
-
* Sort peers by (1)
|
|
55
|
+
* Sort peers by (1) less active requests (2) most columns we need.
|
|
56
|
+
* Peers that failed this batch or already succeeded for the same request are excluded inside `filterPeers`.
|
|
56
57
|
*/
|
|
57
58
|
bestPeerToRetryBatch(batch: Batch): PeerSyncMeta | undefined {
|
|
58
59
|
if (batch.state.status !== BatchStatus.AwaitingDownload) {
|
|
@@ -63,10 +64,8 @@ export class ChainPeersBalancer {
|
|
|
63
64
|
const pendingDataColumns = columnsRequest?.columns ?? this.custodyConfig.sampledColumns;
|
|
64
65
|
const eligiblePeers = this.filterPeers(batch, pendingDataColumns, false);
|
|
65
66
|
|
|
66
|
-
const failedPeers = new Set(batch.getFailedPeers());
|
|
67
67
|
const sortedBestPeers = sortBy(
|
|
68
68
|
eligiblePeers,
|
|
69
|
-
({syncInfo}) => (failedPeers.has(syncInfo.peerId) ? 1 : 0), // prefer peers without failed requests
|
|
70
69
|
({syncInfo}) => this.activeRequestsByPeer.get(syncInfo.peerId) ?? 0, // prefer peers with least active req
|
|
71
70
|
({columns}) => -1 * columns // prefer peers with the most columns
|
|
72
71
|
);
|
|
@@ -117,9 +116,16 @@ export class ChainPeersBalancer {
|
|
|
117
116
|
return eligiblePeers;
|
|
118
117
|
}
|
|
119
118
|
|
|
119
|
+
// Skip peers that failed this batch, or that already returned the exact current request shape.
|
|
120
|
+
const failedPeers = new Set<PeerIdStr>(batch.getFailedPeers());
|
|
121
|
+
|
|
120
122
|
for (const peer of this.peers) {
|
|
121
123
|
const {earliestAvailableSlot, target, peerId} = peer;
|
|
122
124
|
|
|
125
|
+
if (failedPeers.has(peerId) || batch.hasPeerSucceededCurrentRequest(peer)) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
123
129
|
const activeRequest = this.activeRequestsByPeer.get(peerId) ?? 0;
|
|
124
130
|
if (noActiveRequest && activeRequest > 0) {
|
|
125
131
|
// consumer wants to find peer with no active request, but this peer has active request
|