@lodestar/beacon-node 1.43.0-dev.549a5b8115 → 1.43.0-dev.5adb1cab7b
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.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +10 -0
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +0 -1
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +2 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +4 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +16 -29
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +4 -0
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +15 -6
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +29 -17
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +11 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +24 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +16 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/types.d.ts +2 -1
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +8 -0
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +5 -6
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +16 -7
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +10 -6
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +25 -10
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +0 -11
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +0 -4
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +8 -1
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/errors/proposerPreferences.js +1 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -1
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +6 -1
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +16 -18
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +12 -3
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +33 -20
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +1 -4
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +1 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +14 -10
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +63 -19
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +8 -7
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.js +11 -10
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +11 -8
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +39 -17
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +2 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +24 -7
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.js +5 -5
- package/lib/network/processor/index.js.map +1 -1
- package/lib/node/nodejs.js +2 -2
- package/lib/node/nodejs.js.map +1 -1
- package/lib/node/notifier.js +1 -7
- package/lib/node/notifier.js.map +1 -1
- package/lib/sync/constants.d.ts +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 +23 -3
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +191 -36
- 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 +97 -8
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +14 -3
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +7 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +127 -57
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +29 -8
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +104 -42
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRoot.js +10 -0
- package/lib/sync/utils/downloadByRoot.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/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +8 -6
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +16 -16
- package/src/api/impl/beacon/blocks/index.ts +13 -0
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/validator/index.ts +2 -1
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +16 -49
- package/src/chain/blocks/importExecutionPayload.ts +21 -11
- package/src/chain/blocks/index.ts +18 -9
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +32 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -0
- package/src/chain/blocks/types.ts +2 -1
- package/src/chain/blocks/utils/chainSegment.ts +8 -0
- package/src/chain/blocks/verifyBlock.ts +7 -5
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -6
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +14 -6
- package/src/chain/chain.ts +29 -9
- package/src/chain/emitter.ts +0 -11
- package/src/chain/errors/proposerPreferences.ts +9 -1
- package/src/chain/initState.ts +9 -1
- package/src/chain/prepareNextSlot.ts +21 -29
- package/src/chain/produceBlock/produceBlockBody.ts +41 -25
- package/src/chain/regen/queued.ts +2 -7
- package/src/chain/regen/regen.ts +2 -7
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +78 -22
- package/src/chain/seenCache/seenProposerPreferences.ts +14 -11
- package/src/chain/validation/executionPayloadBid.ts +11 -8
- package/src/chain/validation/proposerPreferences.ts +37 -18
- package/src/network/processor/gossipHandlers.ts +30 -7
- package/src/network/processor/index.ts +5 -5
- package/src/node/nodejs.ts +2 -2
- package/src/node/notifier.ts +1 -8
- package/src/sync/constants.ts +8 -4
- package/src/sync/range/batch.ts +240 -42
- package/src/sync/range/chain.ts +116 -9
- package/src/sync/range/range.ts +16 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +157 -60
- package/src/sync/utils/downloadByRange.ts +166 -44
- package/src/sync/utils/downloadByRoot.ts +12 -0
- package/src/sync/utils/rateLimit.ts +16 -0
- package/src/util/sszBytes.ts +8 -6
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} 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
|
/**
|
|
@@ -79,10 +80,36 @@ export type BatchState =
|
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
export type BatchMetadata = {
|
|
83
|
+
// Batch-level slot window (always present)
|
|
82
84
|
startEpoch: Epoch;
|
|
85
|
+
startSlot: Slot;
|
|
86
|
+
count: number;
|
|
83
87
|
status: BatchStatus;
|
|
88
|
+
|
|
89
|
+
// Per-type outstanding request shapes; only present when that sub-request exists.
|
|
90
|
+
// Format: "startSlot=<n>,count=<n>" (plus ",cols=<indices>" for columns).
|
|
91
|
+
blocksReq?: string;
|
|
92
|
+
blobsReq?: string;
|
|
93
|
+
columnsReq?: string;
|
|
94
|
+
envelopesReq?: string;
|
|
95
|
+
|
|
96
|
+
// Retry counters
|
|
97
|
+
downloadAttempts: number;
|
|
98
|
+
processingAttempts: number;
|
|
99
|
+
|
|
100
|
+
// Cumulative peer attribution for failed attempts (only present when non-empty)
|
|
101
|
+
failedDownloadPeers?: string;
|
|
102
|
+
failedProcessingPeers?: string;
|
|
84
103
|
};
|
|
85
104
|
|
|
105
|
+
function formatRangeReq(req: {startSlot: Slot; count: number}): string {
|
|
106
|
+
return `startSlot=${req.startSlot},count=${req.count}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatColumnsReq(req: {startSlot: Slot; count: number; columns: number[]}): string {
|
|
110
|
+
return `startSlot=${req.startSlot},count=${req.count},cols=${prettyPrintIndices(req.columns)}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
/**
|
|
87
114
|
* Batches are downloaded at the first block of the epoch.
|
|
88
115
|
*
|
|
@@ -115,8 +142,18 @@ export class Batch {
|
|
|
115
142
|
private readonly config: ChainForkConfig;
|
|
116
143
|
private readonly clock: IClock;
|
|
117
144
|
private readonly custodyConfig: CustodyConfig;
|
|
118
|
-
|
|
119
|
-
|
|
145
|
+
private readonly isFirstBatchInChain: boolean;
|
|
146
|
+
private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
|
|
147
|
+
|
|
148
|
+
constructor(
|
|
149
|
+
startEpoch: Epoch,
|
|
150
|
+
config: ChainForkConfig,
|
|
151
|
+
clock: IClock,
|
|
152
|
+
custodyConfig: CustodyConfig,
|
|
153
|
+
isFirstBatchInChain: boolean,
|
|
154
|
+
latestBid: gloas.ExecutionPayloadBid | undefined,
|
|
155
|
+
targetSlot: Slot
|
|
156
|
+
) {
|
|
120
157
|
this.config = config;
|
|
121
158
|
this.clock = clock;
|
|
122
159
|
this.custodyConfig = custodyConfig;
|
|
@@ -125,10 +162,40 @@ export class Batch {
|
|
|
125
162
|
this.forkName = this.config.getForkName(startSlot);
|
|
126
163
|
this.startEpoch = startEpoch;
|
|
127
164
|
this.startSlot = startSlot;
|
|
128
|
-
this.count = count;
|
|
165
|
+
this.count = Math.min(count, targetSlot - startSlot + 1);
|
|
166
|
+
this.isFirstBatchInChain = isFirstBatchInChain;
|
|
167
|
+
this.latestBid = latestBid;
|
|
129
168
|
this.requests = this.getRequests([]);
|
|
130
169
|
}
|
|
131
170
|
|
|
171
|
+
private shouldDownloadParentEnvelope(firstBlock?: SignedBeaconBlock): boolean {
|
|
172
|
+
if (!this.isFirstBatchInChain) return false;
|
|
173
|
+
|
|
174
|
+
if (this.startSlot === 0 || !isForkPostGloas(this.config.getForkName(this.startSlot - 1))) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// we only know if we should download parent envelope if firstBlock is downloaded
|
|
179
|
+
if (firstBlock === undefined) return false;
|
|
180
|
+
if (this.latestBid === undefined) return false;
|
|
181
|
+
const firstBlockBidParentHash = (firstBlock.message.body as gloas.BeaconBlockBody).signedExecutionPayloadBid.message
|
|
182
|
+
.parentBlockHash;
|
|
183
|
+
return byteArrayEquals(firstBlockBidParentHash, this.latestBid.blockHash);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getParentPayloadCommitments(parentBlockRoot: Uint8Array): ParentPayloadCommitments {
|
|
187
|
+
if (this.latestBid === undefined) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Coding error: getParentPayloadCommitments called without latestBid for parentBlockRoot=${toRootHex(parentBlockRoot)}`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
blockRoot: parentBlockRoot,
|
|
194
|
+
blockRootHex: toRootHex(parentBlockRoot),
|
|
195
|
+
kzgCommitments: this.latestBid.blobKzgCommitments,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
132
199
|
/**
|
|
133
200
|
* Builds ByRange requests for block, blobs and columns
|
|
134
201
|
*/
|
|
@@ -176,6 +243,7 @@ export class Batch {
|
|
|
176
243
|
const envelopesBySlot = this.state.payloadEnvelopes ?? new Map<Slot, PayloadEnvelopeInput>();
|
|
177
244
|
|
|
178
245
|
// ensure blocks are in slot-wise order
|
|
246
|
+
const isPostGloas = isForkPostGloas(this.forkName);
|
|
179
247
|
for (const blockInput of blocks) {
|
|
180
248
|
const blockSlot = blockInput.slot;
|
|
181
249
|
// check if block/data is present (hasBlock/hasAllData). If present then check if startSlot is the same as
|
|
@@ -191,21 +259,36 @@ export class Batch {
|
|
|
191
259
|
if (blockInput.hasBlock() && blockStartSlot === blockSlot) {
|
|
192
260
|
blockStartSlot = blockSlot + 1;
|
|
193
261
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
262
|
+
|
|
263
|
+
// Range sync uses hasComputedAllData (all sampled columns physically present), not hasAllData
|
|
264
|
+
// which flips at the reconstruction threshold. Sync never triggers reconstruction, so accepting
|
|
265
|
+
// a half-downloaded block here makes writeBlockInputToDb later block on waitForComputedAllData.
|
|
266
|
+
if (isPostGloas) {
|
|
267
|
+
// Post-Gloas: column data lives on PayloadEnvelopeInput, not on BlockInputNoData.
|
|
268
|
+
const payloadInput = envelopesBySlot.get(blockSlot);
|
|
269
|
+
if (blockInput.hasBlock() && envelopeStartSlot === blockSlot && payloadInput?.hasPayloadEnvelope()) {
|
|
270
|
+
envelopeStartSlot = blockSlot + 1;
|
|
271
|
+
}
|
|
272
|
+
if (payloadInput && !payloadInput.hasComputedAllData()) {
|
|
273
|
+
for (const index of payloadInput.getMissingSampledColumnMeta().missing) {
|
|
204
274
|
neededColumns.add(index);
|
|
205
275
|
}
|
|
276
|
+
} else if (payloadInput?.hasComputedAllData() && dataStartSlot === blockSlot) {
|
|
277
|
+
// Only advance dataStartSlot when we know columns for this slot are complete. If
|
|
278
|
+
// payloadInput is missing entirely we cannot tell, so stop here so the next round
|
|
279
|
+
// re-requests columns (and envelopes) starting at this slot.
|
|
280
|
+
dataStartSlot = blockSlot + 1;
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
if (isBlockInputColumns(blockInput) ? !blockInput.hasComputedAllData() : !blockInput.hasAllData()) {
|
|
284
|
+
if (isBlockInputColumns(blockInput)) {
|
|
285
|
+
for (const index of blockInput.getMissingSampledColumnMeta().missing) {
|
|
286
|
+
neededColumns.add(index);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} else if (dataStartSlot === blockSlot) {
|
|
290
|
+
dataStartSlot = blockSlot + 1;
|
|
206
291
|
}
|
|
207
|
-
} else if (dataStartSlot === blockSlot) {
|
|
208
|
-
dataStartSlot = blockSlot + 1;
|
|
209
292
|
}
|
|
210
293
|
}
|
|
211
294
|
|
|
@@ -225,11 +308,15 @@ export class Batch {
|
|
|
225
308
|
// range of 40 - 63, startSlot will be inclusive but subtraction will exclusive so need to + 1
|
|
226
309
|
const count = endSlot - dataStartSlot + 1;
|
|
227
310
|
if (isForkPostFulu(this.forkName) && withinValidRequestWindow) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
311
|
+
// Skip the column re-request when we have no specific column indices outstanding.
|
|
312
|
+
// Peer rejects an empty `columns` list
|
|
313
|
+
if (neededColumns.size > 0) {
|
|
314
|
+
requests.columnsRequest = {
|
|
315
|
+
count,
|
|
316
|
+
startSlot: dataStartSlot,
|
|
317
|
+
columns: Array.from(neededColumns),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
233
320
|
} else if (isForkPostDeneb(this.forkName) && withinValidRequestWindow) {
|
|
234
321
|
requests.blobsRequest = {
|
|
235
322
|
count,
|
|
@@ -246,6 +333,36 @@ export class Batch {
|
|
|
246
333
|
};
|
|
247
334
|
}
|
|
248
335
|
|
|
336
|
+
// Only the first batch of a SyncChain may need the dangling-parent payload by-root.
|
|
337
|
+
if (blocks.length > 0 && this.shouldDownloadParentEnvelope(blocks[0].getBlock())) {
|
|
338
|
+
// shouldDownloadParentEnvelope() = true means there are at least 1 block
|
|
339
|
+
const parentRoot = blocks[0].getBlock().message.parentRoot;
|
|
340
|
+
if (!byteArrayEquals(parentRoot, ZERO_HASH)) {
|
|
341
|
+
const parentRootHex = toRootHex(parentRoot);
|
|
342
|
+
let parentPayloadInput: PayloadEnvelopeInput | undefined;
|
|
343
|
+
if (this.state.payloadEnvelopes) {
|
|
344
|
+
for (const pi of this.state.payloadEnvelopes.values()) {
|
|
345
|
+
if (pi.blockRootHex === parentRootHex) {
|
|
346
|
+
parentPayloadInput = pi;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const needsEnvelope = !parentPayloadInput?.hasPayloadEnvelope();
|
|
353
|
+
const missingColumns = parentPayloadInput
|
|
354
|
+
? parentPayloadInput.getMissingSampledColumnMeta().missing
|
|
355
|
+
: this.custodyConfig.sampledColumns;
|
|
356
|
+
|
|
357
|
+
if (needsEnvelope || missingColumns.length > 0) {
|
|
358
|
+
requests.parentPayloadRequest = {
|
|
359
|
+
...(needsEnvelope ? {envelopeBlockRoot: parentRoot} : {}),
|
|
360
|
+
...(missingColumns.length > 0 ? {blockRoot: parentRoot, columns: missingColumns} : {}),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
249
366
|
return requests;
|
|
250
367
|
}
|
|
251
368
|
|
|
@@ -257,24 +374,28 @@ export class Batch {
|
|
|
257
374
|
return this.requests;
|
|
258
375
|
}
|
|
259
376
|
|
|
260
|
-
// post-fulu we need to ensure that we only request columns that the peer has advertised
|
|
261
|
-
const {columnsRequest} = this.requests;
|
|
262
|
-
if (columnsRequest == null) {
|
|
263
|
-
return this.requests;
|
|
264
|
-
}
|
|
377
|
+
// post-fulu we need to ensure that we only request columns that the peer has advertised.
|
|
378
|
+
const {columnsRequest, parentPayloadRequest} = this.requests;
|
|
265
379
|
|
|
266
380
|
const peerColumns = new Set(peer.custodyColumns ?? []);
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
381
|
+
const filteredColumnsRequest =
|
|
382
|
+
columnsRequest != null ? columnsRequest.columns.filter((c) => peerColumns.has(c)) : null;
|
|
383
|
+
const parentColumns = parentPayloadRequest?.columns;
|
|
384
|
+
const filteredParentColumns = parentColumns != null ? parentColumns.filter((c) => peerColumns.has(c)) : null;
|
|
385
|
+
|
|
386
|
+
const updatedColumnRequest =
|
|
387
|
+
columnsRequest != null && filteredColumnsRequest != null
|
|
388
|
+
? {columnsRequest: {...columnsRequest, columns: filteredColumnsRequest}}
|
|
389
|
+
: {};
|
|
390
|
+
const updatedParentPayloadRequest =
|
|
391
|
+
parentPayloadRequest != null && filteredParentColumns != null
|
|
392
|
+
? {parentPayloadRequest: {...parentPayloadRequest, columns: filteredParentColumns}}
|
|
393
|
+
: {};
|
|
271
394
|
|
|
272
395
|
return {
|
|
273
396
|
...this.requests,
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
columns: requestedColumns,
|
|
277
|
-
},
|
|
397
|
+
...updatedColumnRequest,
|
|
398
|
+
...updatedParentPayloadRequest,
|
|
278
399
|
};
|
|
279
400
|
}
|
|
280
401
|
|
|
@@ -286,7 +407,26 @@ export class Batch {
|
|
|
286
407
|
}
|
|
287
408
|
|
|
288
409
|
getMetadata(): BatchMetadata {
|
|
289
|
-
|
|
410
|
+
const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
|
|
411
|
+
const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
|
|
412
|
+
return {
|
|
413
|
+
startEpoch: this.startEpoch,
|
|
414
|
+
startSlot: this.startSlot,
|
|
415
|
+
count: this.count,
|
|
416
|
+
status: this.state.status,
|
|
417
|
+
...(blocksRequest && {blocksReq: formatRangeReq(blocksRequest)}),
|
|
418
|
+
...(blobsRequest && {blobsReq: formatRangeReq(blobsRequest)}),
|
|
419
|
+
...(columnsRequest && {columnsReq: formatColumnsReq(columnsRequest)}),
|
|
420
|
+
...(envelopesRequest && {envelopesReq: formatRangeReq(envelopesRequest)}),
|
|
421
|
+
downloadAttempts: this.failedDownloadAttempts.length,
|
|
422
|
+
processingAttempts: this.failedProcessingAttempts.length,
|
|
423
|
+
...(this.failedDownloadAttempts.length > 0 && {
|
|
424
|
+
failedDownloadPeers: this.failedDownloadAttempts.join(","),
|
|
425
|
+
}),
|
|
426
|
+
...(failedProcessingPeerList.length > 0 && {
|
|
427
|
+
failedProcessingPeers: failedProcessingPeerList.join(","),
|
|
428
|
+
}),
|
|
429
|
+
};
|
|
290
430
|
}
|
|
291
431
|
|
|
292
432
|
getBlocks(): IBlockInput[] {
|
|
@@ -334,7 +474,11 @@ export class Batch {
|
|
|
334
474
|
const slots = new Set<number>();
|
|
335
475
|
for (const block of blocks) {
|
|
336
476
|
slots.add(block.slot);
|
|
337
|
-
|
|
477
|
+
const dataComplete = isBlockInputColumns(block)
|
|
478
|
+
? // by_range needs to download all columns
|
|
479
|
+
block.hasBlock() && block.hasComputedAllData()
|
|
480
|
+
: block.hasBlockAndAllData();
|
|
481
|
+
if (!dataComplete) {
|
|
338
482
|
allComplete = false;
|
|
339
483
|
}
|
|
340
484
|
}
|
|
@@ -350,11 +494,45 @@ export class Batch {
|
|
|
350
494
|
}
|
|
351
495
|
const newPayloadEnvelopes = payloadEnvelopes ?? this.state.payloadEnvelopes;
|
|
352
496
|
|
|
497
|
+
if (allComplete && isForkPostGloas(this.forkName)) {
|
|
498
|
+
for (const block of blocks) {
|
|
499
|
+
const payloadInput = newPayloadEnvelopes?.get(block.slot);
|
|
500
|
+
// by_range needs every block's envelope and all sampled columns.
|
|
501
|
+
if (!payloadInput?.hasPayloadEnvelope() || !payloadInput.hasComputedAllData()) {
|
|
502
|
+
allComplete = false;
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// First batch of a sync chain must additionally have the dangling-parent payload fully
|
|
509
|
+
// present, otherwise `processBlocks` will throw PARENT_PAYLOAD_UNKNOWN. The parent's
|
|
510
|
+
// `PayloadEnvelopeInput` is identified by `blockRootHex` matching `blocks[0].parentRoot`.
|
|
511
|
+
if (allComplete && blocks.length > 0 && this.shouldDownloadParentEnvelope(blocks[0].getBlock())) {
|
|
512
|
+
const parentRoot = blocks[0].getBlock().message.parentRoot;
|
|
513
|
+
// Genesis has no parent payload — nothing to wait for.
|
|
514
|
+
if (!byteArrayEquals(parentRoot, ZERO_HASH)) {
|
|
515
|
+
const parentRootHex = toRootHex(parentRoot);
|
|
516
|
+
let parentPayloadComplete = false;
|
|
517
|
+
if (newPayloadEnvelopes) {
|
|
518
|
+
for (const payloadInput of newPayloadEnvelopes.values()) {
|
|
519
|
+
if (payloadInput.blockRootHex === parentRootHex) {
|
|
520
|
+
parentPayloadComplete = payloadInput.hasPayloadEnvelope() && payloadInput.hasComputedAllData();
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (!parentPayloadComplete) {
|
|
526
|
+
allComplete = false;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
353
531
|
if (allComplete) {
|
|
354
532
|
this.state = {status: BatchStatus.AwaitingProcessing, blocks, payloadEnvelopes: newPayloadEnvelopes};
|
|
355
533
|
} else {
|
|
356
|
-
this.requests = this.getRequests(blocks);
|
|
357
534
|
this.state = {status: BatchStatus.AwaitingDownload, blocks, payloadEnvelopes: newPayloadEnvelopes};
|
|
535
|
+
this.requests = this.getRequests(blocks);
|
|
358
536
|
}
|
|
359
537
|
|
|
360
538
|
return this.state as DownloadSuccessState;
|
|
@@ -380,10 +558,30 @@ export class Batch {
|
|
|
380
558
|
};
|
|
381
559
|
}
|
|
382
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Downloading -> AwaitingDownload (without counting as a failed attempt).
|
|
563
|
+
* Used when the peer rate-limited us — the request was never actually served.
|
|
564
|
+
*/
|
|
565
|
+
downloadingRateLimited(): void {
|
|
566
|
+
if (this.state.status !== BatchStatus.Downloading) {
|
|
567
|
+
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Downloading));
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.state = {
|
|
571
|
+
status: BatchStatus.AwaitingDownload,
|
|
572
|
+
blocks: this.state.blocks,
|
|
573
|
+
payloadEnvelopes: this.state.payloadEnvelopes,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
383
577
|
/**
|
|
384
578
|
* AwaitingProcessing -> Processing
|
|
385
579
|
*/
|
|
386
|
-
startProcessing(): {
|
|
580
|
+
startProcessing(): {
|
|
581
|
+
blocks: IBlockInput[];
|
|
582
|
+
payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
|
|
583
|
+
peers: PeerIdStr[];
|
|
584
|
+
} {
|
|
387
585
|
if (this.state.status !== BatchStatus.AwaitingProcessing) {
|
|
388
586
|
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingProcessing));
|
|
389
587
|
}
|
|
@@ -396,7 +594,7 @@ export class Batch {
|
|
|
396
594
|
const peers = this.goodPeers;
|
|
397
595
|
this.goodPeers = [];
|
|
398
596
|
this.state = {status: BatchStatus.Processing, blocks, payloadEnvelopes, attempt: {peers, hash}};
|
|
399
|
-
return {blocks, payloadEnvelopes};
|
|
597
|
+
return {blocks, payloadEnvelopes, peers};
|
|
400
598
|
}
|
|
401
599
|
|
|
402
600
|
/**
|
|
@@ -479,7 +677,7 @@ export class Batch {
|
|
|
479
677
|
|
|
480
678
|
/** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
|
|
481
679
|
private errorType(type: BatchErrorType): BatchErrorType & BatchErrorMetadata {
|
|
482
|
-
return {...type,
|
|
680
|
+
return {...type, startEpoch: this.startEpoch, status: this.state.status};
|
|
483
681
|
}
|
|
484
682
|
|
|
485
683
|
private wrongStatusErrorType(expectedStatus: BatchStatus): BatchErrorType & BatchErrorMetadata {
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {Epoch, Root, Slot} from "@lodestar/types";
|
|
3
|
-
import {ErrorAborted, LodestarError, Logger, toRootHex} from "@lodestar/utils";
|
|
2
|
+
import {Epoch, Root, Slot, gloas} from "@lodestar/types";
|
|
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";
|
|
6
6
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.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,12 +237,16 @@ export class SyncChain {
|
|
|
222
237
|
*/
|
|
223
238
|
stopSyncing(): void {
|
|
224
239
|
this.status = SyncChainStatus.Stopped;
|
|
240
|
+
this.clearRateLimitBackoffTimer();
|
|
241
|
+
this.logger.debug("SyncChain stopSyncing", {id: this.logId});
|
|
225
242
|
}
|
|
226
243
|
|
|
227
244
|
/**
|
|
228
245
|
* Permanently remove this chain. Throws the main AsyncIterable
|
|
229
246
|
*/
|
|
230
247
|
remove(): void {
|
|
248
|
+
this.logger.debug("SyncChain remove", {id: this.logId});
|
|
249
|
+
this.clearRateLimitBackoffTimer();
|
|
231
250
|
this.batchProcessor.end(new ErrorAborted("SyncChain"));
|
|
232
251
|
}
|
|
233
252
|
|
|
@@ -246,6 +265,7 @@ export class SyncChain {
|
|
|
246
265
|
*/
|
|
247
266
|
removePeer(peerId: PeerIdStr): boolean {
|
|
248
267
|
const deleted = this.peerset.delete(peerId);
|
|
268
|
+
this.rateLimitedPeers.delete(peerId);
|
|
249
269
|
this.computeTarget();
|
|
250
270
|
return deleted;
|
|
251
271
|
}
|
|
@@ -349,6 +369,8 @@ export class SyncChain {
|
|
|
349
369
|
}
|
|
350
370
|
|
|
351
371
|
throw e;
|
|
372
|
+
} finally {
|
|
373
|
+
this.clearRateLimitBackoffTimer();
|
|
352
374
|
}
|
|
353
375
|
}
|
|
354
376
|
|
|
@@ -372,6 +394,44 @@ export class SyncChain {
|
|
|
372
394
|
}
|
|
373
395
|
}
|
|
374
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
|
+
|
|
375
435
|
/**
|
|
376
436
|
* Attempts to request the next required batches from the peer pool if the chain is syncing.
|
|
377
437
|
* It will exhaust the peer pool and left over batches until the batch buffer is reached.
|
|
@@ -381,8 +441,18 @@ export class SyncChain {
|
|
|
381
441
|
return;
|
|
382
442
|
}
|
|
383
443
|
|
|
444
|
+
const now = Date.now();
|
|
384
445
|
const peersSyncInfo: PeerSyncInfo[] = [];
|
|
385
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
|
+
|
|
386
456
|
try {
|
|
387
457
|
peersSyncInfo.push({...this.getConnectedPeerSyncMeta(peerId), target});
|
|
388
458
|
} catch (e) {
|
|
@@ -456,7 +526,17 @@ export class SyncChain {
|
|
|
456
526
|
return null;
|
|
457
527
|
}
|
|
458
528
|
|
|
459
|
-
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;
|
|
460
540
|
this.batches.set(startEpoch, batch);
|
|
461
541
|
return batch;
|
|
462
542
|
}
|
|
@@ -514,7 +594,16 @@ export class SyncChain {
|
|
|
514
594
|
{id: this.logId, ...batch.getMetadata(), peer: prettyPrintPeerIdStr(peer.peerId)},
|
|
515
595
|
res.err
|
|
516
596
|
);
|
|
517
|
-
|
|
597
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(res.err);
|
|
598
|
+
if (rateLimitedUntilMs !== null) {
|
|
599
|
+
// Peer rate-limited us — don't count as a failed download attempt and mark peer for backoff
|
|
600
|
+
this.rateLimitedPeers.set(peer.peerId, rateLimitedUntilMs);
|
|
601
|
+
this.scheduleRateLimitBackoffRetry();
|
|
602
|
+
batch.downloadingRateLimited();
|
|
603
|
+
this.triggerBatchDownloader();
|
|
604
|
+
} else {
|
|
605
|
+
batch.downloadingError(peer.peerId); // Throws after MAX_DOWNLOAD_ATTEMPTS
|
|
606
|
+
}
|
|
518
607
|
} else {
|
|
519
608
|
this.logger.verbose("Batch download success", {
|
|
520
609
|
id: this.logId,
|
|
@@ -534,7 +623,7 @@ export class SyncChain {
|
|
|
534
623
|
this.metrics?.syncRange.downloadByRange.warn.inc({client: peer.client, code: warning.type.code});
|
|
535
624
|
this.logger.debug(
|
|
536
625
|
"Batch downloaded with warning",
|
|
537
|
-
{id: this.logId,
|
|
626
|
+
{id: this.logId, ...batch.getMetadata(), ...logMeta, peer: prettyPrintPeerIdStr(peer.peerId)},
|
|
538
627
|
warning
|
|
539
628
|
);
|
|
540
629
|
}
|
|
@@ -560,10 +649,17 @@ export class SyncChain {
|
|
|
560
649
|
// the flow will continue to call triggerBatchDownloader() below
|
|
561
650
|
}
|
|
562
651
|
|
|
652
|
+
const blockSlots = downloadSuccessOutput.blocks.map((b) => b.slot);
|
|
653
|
+
const envelopeSlots = downloadSuccessOutput.payloadEnvelopes
|
|
654
|
+
? Array.from(downloadSuccessOutput.payloadEnvelopes.keys())
|
|
655
|
+
: null;
|
|
656
|
+
|
|
563
657
|
this.logger.debug(logMessage, {
|
|
564
658
|
id: this.logId,
|
|
565
|
-
|
|
659
|
+
...batch.getMetadata(),
|
|
566
660
|
...logMeta,
|
|
661
|
+
blockSlots: prettyPrintIndices(blockSlots),
|
|
662
|
+
...(envelopeSlots ? {envelopeSlots: prettyPrintIndices(envelopeSlots)} : {}),
|
|
567
663
|
peer: prettyPrintPeerIdStr(peer.peerId),
|
|
568
664
|
});
|
|
569
665
|
}
|
|
@@ -586,13 +682,24 @@ export class SyncChain {
|
|
|
586
682
|
* Sends `batch` to the processor. Note: batch may be empty
|
|
587
683
|
*/
|
|
588
684
|
private async processBatch(batch: Batch): Promise<void> {
|
|
589
|
-
const {blocks, payloadEnvelopes} = batch.startProcessing();
|
|
685
|
+
const {blocks, payloadEnvelopes, peers} = batch.startProcessing();
|
|
686
|
+
|
|
687
|
+
const logCtx = {
|
|
688
|
+
id: this.logId,
|
|
689
|
+
...batch.getMetadata(),
|
|
690
|
+
blockCount: blocks.length,
|
|
691
|
+
blockSlots: prettyPrintIndices(blocks.map((b) => b.slot)),
|
|
692
|
+
...(payloadEnvelopes ? {envelopeSlots: prettyPrintIndices(Array.from(payloadEnvelopes.keys()))} : {}),
|
|
693
|
+
peers: peers.map(prettyPrintPeerIdStr).join(","),
|
|
694
|
+
};
|
|
695
|
+
this.logger.verbose("Processing batch", logCtx);
|
|
590
696
|
|
|
591
697
|
// wrapError ensures to never call both batch success() and batch error()
|
|
592
698
|
const res = await wrapError(this.processChainSegment(blocks, payloadEnvelopes, this.syncType));
|
|
593
699
|
|
|
594
700
|
if (!res.err) {
|
|
595
701
|
batch.processingSuccess();
|
|
702
|
+
this.logger.verbose("Processed batch", {...logCtx, ...batch.getMetadata()});
|
|
596
703
|
|
|
597
704
|
// If the processed batch is not empty, validate previous AwaitingValidation blocks.
|
|
598
705
|
if (blocks.length > 0) {
|
|
@@ -602,7 +709,7 @@ export class SyncChain {
|
|
|
602
709
|
// Potentially process next AwaitingProcessing batch
|
|
603
710
|
this.triggerBatchProcessor();
|
|
604
711
|
} else {
|
|
605
|
-
this.logger.verbose("Batch process error",
|
|
712
|
+
this.logger.verbose("Batch process error", logCtx, res.err);
|
|
606
713
|
batch.processingError(res.err); // Throws after MAX_BATCH_PROCESSING_ATTEMPTS
|
|
607
714
|
|
|
608
715
|
// At least one block was successfully verified and imported, so we can be sure all
|