@lodestar/beacon-node 1.43.0-dev.66d2c102e3 → 1.43.0-dev.6f485b1b61
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 +13 -3
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +45 -2
- package/lib/api/impl/beacon/pool/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 +68 -2
- 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 -31
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +9 -3
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +37 -15
- 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 +35 -21
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -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 +2 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +30 -12
- 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/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +25 -8
- 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/opPools/payloadAttestationPool.d.ts +3 -2
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +26 -4
- package/lib/chain/opPools/payloadAttestationPool.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 +34 -22
- 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 +21 -11
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +70 -20
- 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/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 +5 -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 +28 -10
- 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 +3 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +3 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +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 +13 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +61 -9
- 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 +138 -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/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 +15 -15
- package/src/api/impl/beacon/blocks/index.ts +16 -3
- package/src/api/impl/beacon/pool/index.ts +83 -1
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/validator/index.ts +82 -1
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +16 -50
- package/src/chain/blocks/importExecutionPayload.ts +51 -20
- package/src/chain/blocks/index.ts +32 -15
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
- package/src/chain/blocks/types.ts +2 -1
- package/src/chain/blocks/utils/chainSegment.ts +8 -0
- package/src/chain/blocks/verifyBlock.ts +45 -13
- 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/blocks/verifyPayloadsDataAvailability.ts +7 -4
- package/src/chain/chain.ts +29 -7
- 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/opPools/payloadAttestationPool.ts +29 -8
- package/src/chain/prepareNextSlot.ts +21 -29
- package/src/chain/produceBlock/produceBlockBody.ts +45 -27
- package/src/chain/regen/queued.ts +2 -7
- package/src/chain/regen/regen.ts +2 -7
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +90 -24
- 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/interface.ts +1 -0
- package/src/network/network.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +38 -11
- 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 +4 -4
- package/src/sync/range/batch.ts +240 -42
- package/src/sync/range/chain.ts +77 -10
- package/src/sync/range/range.ts +16 -3
- package/src/sync/sync.ts +13 -1
- package/src/sync/unknownBlock.ts +170 -60
- package/src/sync/utils/downloadByRange.ts +166 -44
- package/src/sync/utils/downloadByRoot.ts +12 -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,7 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import {RequestErrorCode} from "@lodestar/reqresp";
|
|
3
|
+
import {Epoch, Root, Slot, gloas} from "@lodestar/types";
|
|
4
|
+
import {ErrorAborted, LodestarError, Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
4
5
|
import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
|
|
5
6
|
import {BlockInputErrorCode} from "../../chain/blocks/blockInput/errors.js";
|
|
6
7
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
@@ -15,7 +16,12 @@ import {CustodyConfig} from "../../util/dataColumns.js";
|
|
|
15
16
|
import {ItTrigger} from "../../util/itTrigger.js";
|
|
16
17
|
import {PeerIdStr} from "../../util/peerId.js";
|
|
17
18
|
import {WarnResult, wrapError} from "../../util/wrapError.js";
|
|
18
|
-
import {
|
|
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";
|
|
19
25
|
import {DownloadByRangeError, DownloadByRangeErrorCode} from "../utils/downloadByRange.js";
|
|
20
26
|
import {RangeSyncType} from "../utils/remoteSyncType.js";
|
|
21
27
|
import {Batch, BatchError, BatchErrorCode, BatchMetadata, BatchStatus} from "./batch.js";
|
|
@@ -139,20 +145,32 @@ export class SyncChain {
|
|
|
139
145
|
private readonly batchProcessor = new ItTrigger();
|
|
140
146
|
/** Sorted map of batches undergoing some kind of processing. */
|
|
141
147
|
private readonly batches = new Map<Epoch, Batch>();
|
|
148
|
+
/**
|
|
149
|
+
* `true` until the first `Batch` is constructed via `includeNextBatch`
|
|
150
|
+
*/
|
|
151
|
+
private isFirstBatch = true;
|
|
142
152
|
private readonly peerset = new Map<PeerIdStr, ChainTarget>();
|
|
153
|
+
/**
|
|
154
|
+
* Tracks peers that have rate-limited us, mapped to the timestamp (ms) until which we should avoid them.
|
|
155
|
+
* This is a sync-layer optimization to avoid assigning batches to backed-off peers.
|
|
156
|
+
* The reqresp SelfRateLimiter independently enforces backoff at the protocol level as a safety net.
|
|
157
|
+
*/
|
|
158
|
+
private readonly rateLimitedPeers = new Map<PeerIdStr, number>();
|
|
143
159
|
|
|
144
160
|
private readonly logger: Logger;
|
|
145
161
|
private readonly config: ChainForkConfig;
|
|
146
162
|
private readonly clock: IClock;
|
|
147
163
|
private readonly metrics: Metrics | null;
|
|
148
164
|
private readonly custodyConfig: CustodyConfig;
|
|
165
|
+
private readonly latestBid: gloas.ExecutionPayloadBid | undefined;
|
|
149
166
|
|
|
150
167
|
constructor(
|
|
151
168
|
initialBatchEpoch: Epoch,
|
|
152
169
|
initialTarget: ChainTarget,
|
|
153
170
|
syncType: RangeSyncType,
|
|
154
171
|
fns: SyncChainFns,
|
|
155
|
-
modules: SyncChainModules
|
|
172
|
+
modules: SyncChainModules,
|
|
173
|
+
latestBid: gloas.ExecutionPayloadBid | undefined
|
|
156
174
|
) {
|
|
157
175
|
const {config, clock, custodyConfig, logger, metrics} = modules;
|
|
158
176
|
this.firstBatchEpoch = initialBatchEpoch;
|
|
@@ -168,6 +186,7 @@ export class SyncChain {
|
|
|
168
186
|
this.clock = clock;
|
|
169
187
|
this.metrics = metrics;
|
|
170
188
|
this.custodyConfig = custodyConfig;
|
|
189
|
+
this.latestBid = latestBid;
|
|
171
190
|
this.logger = logger;
|
|
172
191
|
this.logId = `${syncType}-${nextChainId++}`;
|
|
173
192
|
|
|
@@ -222,12 +241,14 @@ export class SyncChain {
|
|
|
222
241
|
*/
|
|
223
242
|
stopSyncing(): void {
|
|
224
243
|
this.status = SyncChainStatus.Stopped;
|
|
244
|
+
this.logger.debug("SyncChain stopSyncing", {id: this.logId});
|
|
225
245
|
}
|
|
226
246
|
|
|
227
247
|
/**
|
|
228
248
|
* Permanently remove this chain. Throws the main AsyncIterable
|
|
229
249
|
*/
|
|
230
250
|
remove(): void {
|
|
251
|
+
this.logger.debug("SyncChain remove", {id: this.logId});
|
|
231
252
|
this.batchProcessor.end(new ErrorAborted("SyncChain"));
|
|
232
253
|
}
|
|
233
254
|
|
|
@@ -246,6 +267,7 @@ export class SyncChain {
|
|
|
246
267
|
*/
|
|
247
268
|
removePeer(peerId: PeerIdStr): boolean {
|
|
248
269
|
const deleted = this.peerset.delete(peerId);
|
|
270
|
+
this.rateLimitedPeers.delete(peerId);
|
|
249
271
|
this.computeTarget();
|
|
250
272
|
return deleted;
|
|
251
273
|
}
|
|
@@ -381,8 +403,18 @@ export class SyncChain {
|
|
|
381
403
|
return;
|
|
382
404
|
}
|
|
383
405
|
|
|
406
|
+
const now = Date.now();
|
|
384
407
|
const peersSyncInfo: PeerSyncInfo[] = [];
|
|
385
408
|
for (const [peerId, target] of this.peerset.entries()) {
|
|
409
|
+
// Skip peers that are currently in rate-limit backoff
|
|
410
|
+
const rateLimitedUntil = this.rateLimitedPeers.get(peerId);
|
|
411
|
+
if (rateLimitedUntil !== undefined) {
|
|
412
|
+
if (now < rateLimitedUntil) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
this.rateLimitedPeers.delete(peerId);
|
|
416
|
+
}
|
|
417
|
+
|
|
386
418
|
try {
|
|
387
419
|
peersSyncInfo.push({...this.getConnectedPeerSyncMeta(peerId), target});
|
|
388
420
|
} catch (e) {
|
|
@@ -456,7 +488,17 @@ export class SyncChain {
|
|
|
456
488
|
return null;
|
|
457
489
|
}
|
|
458
490
|
|
|
459
|
-
const batch = new Batch(
|
|
491
|
+
const batch = new Batch(
|
|
492
|
+
startEpoch,
|
|
493
|
+
this.config,
|
|
494
|
+
this.clock,
|
|
495
|
+
this.custodyConfig,
|
|
496
|
+
this.isFirstBatch,
|
|
497
|
+
// `latestBid` is only meaningful for the first batch's parent-payload check
|
|
498
|
+
this.isFirstBatch ? this.latestBid : undefined,
|
|
499
|
+
this.target.slot
|
|
500
|
+
);
|
|
501
|
+
this.isFirstBatch = false;
|
|
460
502
|
this.batches.set(startEpoch, batch);
|
|
461
503
|
return batch;
|
|
462
504
|
}
|
|
@@ -514,7 +556,14 @@ export class SyncChain {
|
|
|
514
556
|
{id: this.logId, ...batch.getMetadata(), peer: prettyPrintPeerIdStr(peer.peerId)},
|
|
515
557
|
res.err
|
|
516
558
|
);
|
|
517
|
-
|
|
559
|
+
if (errCode === RequestErrorCode.RESP_RATE_LIMITED || errCode === RequestErrorCode.REQUEST_SELF_RATE_LIMITED) {
|
|
560
|
+
// Peer rate-limited us — don't count as a failed download attempt and mark peer for backoff
|
|
561
|
+
this.rateLimitedPeers.set(peer.peerId, Date.now() + RATE_LIMITED_PEER_BACKOFF_MS);
|
|
562
|
+
batch.downloadingRateLimited();
|
|
563
|
+
this.triggerBatchDownloader();
|
|
564
|
+
} else {
|
|
565
|
+
batch.downloadingError(peer.peerId); // Throws after MAX_DOWNLOAD_ATTEMPTS
|
|
566
|
+
}
|
|
518
567
|
} else {
|
|
519
568
|
this.logger.verbose("Batch download success", {
|
|
520
569
|
id: this.logId,
|
|
@@ -534,7 +583,7 @@ export class SyncChain {
|
|
|
534
583
|
this.metrics?.syncRange.downloadByRange.warn.inc({client: peer.client, code: warning.type.code});
|
|
535
584
|
this.logger.debug(
|
|
536
585
|
"Batch downloaded with warning",
|
|
537
|
-
{id: this.logId,
|
|
586
|
+
{id: this.logId, ...batch.getMetadata(), ...logMeta, peer: prettyPrintPeerIdStr(peer.peerId)},
|
|
538
587
|
warning
|
|
539
588
|
);
|
|
540
589
|
}
|
|
@@ -560,10 +609,17 @@ export class SyncChain {
|
|
|
560
609
|
// the flow will continue to call triggerBatchDownloader() below
|
|
561
610
|
}
|
|
562
611
|
|
|
612
|
+
const blockSlots = downloadSuccessOutput.blocks.map((b) => b.slot);
|
|
613
|
+
const envelopeSlots = downloadSuccessOutput.payloadEnvelopes
|
|
614
|
+
? Array.from(downloadSuccessOutput.payloadEnvelopes.keys())
|
|
615
|
+
: null;
|
|
616
|
+
|
|
563
617
|
this.logger.debug(logMessage, {
|
|
564
618
|
id: this.logId,
|
|
565
|
-
|
|
619
|
+
...batch.getMetadata(),
|
|
566
620
|
...logMeta,
|
|
621
|
+
blockSlots: prettyPrintIndices(blockSlots),
|
|
622
|
+
...(envelopeSlots ? {envelopeSlots: prettyPrintIndices(envelopeSlots)} : {}),
|
|
567
623
|
peer: prettyPrintPeerIdStr(peer.peerId),
|
|
568
624
|
});
|
|
569
625
|
}
|
|
@@ -586,13 +642,24 @@ export class SyncChain {
|
|
|
586
642
|
* Sends `batch` to the processor. Note: batch may be empty
|
|
587
643
|
*/
|
|
588
644
|
private async processBatch(batch: Batch): Promise<void> {
|
|
589
|
-
const {blocks, payloadEnvelopes} = batch.startProcessing();
|
|
645
|
+
const {blocks, payloadEnvelopes, peers} = batch.startProcessing();
|
|
646
|
+
|
|
647
|
+
const logCtx = {
|
|
648
|
+
id: this.logId,
|
|
649
|
+
...batch.getMetadata(),
|
|
650
|
+
blockCount: blocks.length,
|
|
651
|
+
blockSlots: prettyPrintIndices(blocks.map((b) => b.slot)),
|
|
652
|
+
...(payloadEnvelopes ? {envelopeSlots: prettyPrintIndices(Array.from(payloadEnvelopes.keys()))} : {}),
|
|
653
|
+
peers: peers.map(prettyPrintPeerIdStr).join(","),
|
|
654
|
+
};
|
|
655
|
+
this.logger.verbose("Processing batch", logCtx);
|
|
590
656
|
|
|
591
657
|
// wrapError ensures to never call both batch success() and batch error()
|
|
592
658
|
const res = await wrapError(this.processChainSegment(blocks, payloadEnvelopes, this.syncType));
|
|
593
659
|
|
|
594
660
|
if (!res.err) {
|
|
595
661
|
batch.processingSuccess();
|
|
662
|
+
this.logger.verbose("Processed batch", {...logCtx, ...batch.getMetadata()});
|
|
596
663
|
|
|
597
664
|
// If the processed batch is not empty, validate previous AwaitingValidation blocks.
|
|
598
665
|
if (blocks.length > 0) {
|
|
@@ -602,7 +669,7 @@ export class SyncChain {
|
|
|
602
669
|
// Potentially process next AwaitingProcessing batch
|
|
603
670
|
this.triggerBatchProcessor();
|
|
604
671
|
} else {
|
|
605
|
-
this.logger.verbose("Batch process error",
|
|
672
|
+
this.logger.verbose("Batch process error", logCtx, res.err);
|
|
606
673
|
batch.processingError(res.err); // Throws after MAX_BATCH_PROCESSING_ATTEMPTS
|
|
607
674
|
|
|
608
675
|
// At least one block was successfully verified and imported, so we can be sure all
|
package/src/sync/range/range.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {EventEmitter} from "node:events";
|
|
2
2
|
import {StrictEventEmitter} from "strict-event-emitter-types";
|
|
3
3
|
import {BeaconConfig} from "@lodestar/config";
|
|
4
|
-
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
|
+
import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "@lodestar/state-transition";
|
|
5
5
|
import {Epoch, Status, fulu} from "@lodestar/types";
|
|
6
6
|
import {Logger, toRootHex} from "@lodestar/utils";
|
|
7
7
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
@@ -206,14 +206,18 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
206
206
|
|
|
207
207
|
private downloadByRange: SyncChainFns["downloadByRange"] = async (peer, batch) => {
|
|
208
208
|
const batchBlocks = batch.getBlocks();
|
|
209
|
+
const requests = batch.getRequestsForPeer(peer);
|
|
210
|
+
const parentRoot = requests.parentPayloadRequest?.envelopeBlockRoot ?? requests.parentPayloadRequest?.blockRoot;
|
|
211
|
+
const parentPayloadCommitments = parentRoot ? batch.getParentPayloadCommitments(parentRoot) : undefined;
|
|
209
212
|
const {result, warnings} = await downloadByRange({
|
|
210
213
|
config: this.config,
|
|
211
214
|
network: this.network,
|
|
212
215
|
logger: this.logger,
|
|
213
216
|
peerIdStr: peer.peerId,
|
|
214
217
|
batchBlocks,
|
|
218
|
+
parentPayloadCommitments,
|
|
215
219
|
peerDasMetrics: this.chain.metrics?.peerDas,
|
|
216
|
-
...
|
|
220
|
+
...requests,
|
|
217
221
|
});
|
|
218
222
|
const {responses, payloadEnvelopes: downloadedPayloadEnvelopes} = result;
|
|
219
223
|
const {blocks, payloadEnvelopes} = cacheByRangeResponses({
|
|
@@ -258,6 +262,14 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
258
262
|
private addPeerOrCreateChain(startEpoch: Epoch, target: ChainTarget, peer: PeerIdStr, syncType: RangeSyncType): void {
|
|
259
263
|
let syncChain = this.chains.get(syncType);
|
|
260
264
|
if (!syncChain) {
|
|
265
|
+
// The first batch of a new sync chain may need to detect whether the parent block was an
|
|
266
|
+
// gloas "empty" block (no envelope produced). It does so by comparing the first
|
|
267
|
+
// downloaded block's `bid.parentBlockHash` against the head state's `latestExecutionPayloadBid.blockHash`.
|
|
268
|
+
const headState = this.chain.getHeadState();
|
|
269
|
+
const latestBid = isStatePostGloas(headState)
|
|
270
|
+
? (headState as IBeaconStateViewGloas).latestExecutionPayloadBid
|
|
271
|
+
: undefined;
|
|
272
|
+
|
|
261
273
|
syncChain = new SyncChain(
|
|
262
274
|
startEpoch,
|
|
263
275
|
target,
|
|
@@ -276,7 +288,8 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
276
288
|
logger: this.logger,
|
|
277
289
|
custodyConfig: this.chain.custodyConfig,
|
|
278
290
|
metrics: this.metrics,
|
|
279
|
-
}
|
|
291
|
+
},
|
|
292
|
+
latestBid
|
|
280
293
|
);
|
|
281
294
|
this.chains.set(syncType, syncChain);
|
|
282
295
|
|