@lodestar/beacon-node 1.43.0-dev.d166e3b6f7 → 1.43.0-dev.dfb984e779
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 +3 -2
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +1 -1
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +6 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +19 -8
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +31 -20
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts +5 -3
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +28 -9
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +2 -2
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +81 -12
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +3 -2
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +30 -5
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +15 -4
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +7 -3
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +8 -1
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -0
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/interface.d.ts +1 -1
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +8 -2
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +1 -0
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +1 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +4 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/processor/gossipHandlers.js +4 -6
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/node/notifier.js +7 -1
- package/lib/node/notifier.js.map +1 -1
- package/lib/sync/range/batch.d.ts +12 -2
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +56 -30
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +6 -2
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +4 -3
- 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 +17 -6
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/types.d.ts +34 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +34 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +24 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +649 -53
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +46 -10
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +147 -24
- 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 +6 -2
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.js +0 -9
- package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +5 -2
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/chain/blocks/importBlock.ts +4 -2
- package/src/chain/blocks/importExecutionPayload.ts +36 -21
- package/src/chain/blocks/index.ts +44 -12
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
- package/src/chain/blocks/types.ts +2 -2
- package/src/chain/blocks/utils/chainSegment.ts +106 -17
- package/src/chain/blocks/verifyBlock.ts +35 -6
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +16 -7
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +2 -2
- package/src/chain/chain.ts +11 -3
- package/src/chain/errors/blockError.ts +4 -1
- package/src/chain/interface.ts +5 -1
- package/src/chain/produceBlock/produceBlockBody.ts +8 -2
- package/src/chain/validation/block.ts +1 -0
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/network/processor/gossipHandlers.ts +6 -6
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
- package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
- package/src/node/notifier.ts +8 -1
- package/src/sync/range/batch.ts +90 -35
- package/src/sync/range/chain.ts +13 -5
- package/src/sync/range/range.ts +18 -6
- package/src/sync/types.ts +72 -0
- package/src/sync/unknownBlock.ts +810 -57
- package/src/sync/utils/downloadByRange.ts +256 -39
- package/src/sync/utils/downloadByRoot.ts +12 -2
- package/src/sync/utils/pendingBlocksTree.ts +0 -15
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
ForkPostDeneb,
|
|
4
|
+
ForkPostFulu,
|
|
5
|
+
ForkPostGloas,
|
|
6
|
+
ForkPreFulu,
|
|
7
|
+
isForkPostFulu,
|
|
8
|
+
isForkPostGloas,
|
|
9
|
+
} from "@lodestar/params";
|
|
10
|
+
import {
|
|
11
|
+
DataColumnSidecar,
|
|
12
|
+
SignedBeaconBlock,
|
|
13
|
+
Slot,
|
|
14
|
+
deneb,
|
|
15
|
+
fulu,
|
|
16
|
+
gloas,
|
|
17
|
+
isGloasDataColumnSidecar,
|
|
18
|
+
phase0,
|
|
19
|
+
} from "@lodestar/types";
|
|
4
20
|
import {LodestarError, Logger, byteArrayEquals, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
5
21
|
import {
|
|
6
22
|
BlockInputSource,
|
|
@@ -9,12 +25,18 @@ import {
|
|
|
9
25
|
isBlockInputBlobs,
|
|
10
26
|
isBlockInputColumns,
|
|
11
27
|
} from "../../chain/blocks/blockInput/index.js";
|
|
28
|
+
import {PayloadEnvelopeInput} from "../../chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js";
|
|
29
|
+
import {PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/types.js";
|
|
12
30
|
import {SeenBlockInput} from "../../chain/seenCache/seenGossipBlockInput.js";
|
|
31
|
+
import {SeenPayloadEnvelopeInput} from "../../chain/seenCache/seenPayloadEnvelopeInput.js";
|
|
13
32
|
import {validateBlockBlobSidecars} from "../../chain/validation/blobSidecar.js";
|
|
14
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
validateFuluBlockDataColumnSidecars,
|
|
35
|
+
validateGloasBlockDataColumnSidecars,
|
|
36
|
+
} from "../../chain/validation/dataColumnSidecar.js";
|
|
15
37
|
import {BeaconMetrics} from "../../metrics/metrics/beacon.js";
|
|
16
38
|
import {INetwork} from "../../network/index.js";
|
|
17
|
-
import {getBlobKzgCommitments} from "../../util/dataColumns.js";
|
|
39
|
+
import {CustodyConfig, getBlobKzgCommitments} from "../../util/dataColumns.js";
|
|
18
40
|
import {PeerIdStr} from "../../util/peerId.js";
|
|
19
41
|
import {WarnResult} from "../../util/wrapError.js";
|
|
20
42
|
|
|
@@ -22,12 +44,14 @@ export type DownloadByRangeRequests = {
|
|
|
22
44
|
blocksRequest?: phase0.BeaconBlocksByRangeRequest;
|
|
23
45
|
blobsRequest?: deneb.BlobSidecarsByRangeRequest;
|
|
24
46
|
columnsRequest?: fulu.DataColumnSidecarsByRangeRequest;
|
|
47
|
+
envelopesRequest?: gloas.ExecutionPayloadEnvelopesByRangeRequest;
|
|
25
48
|
};
|
|
26
49
|
|
|
27
50
|
export type DownloadByRangeResponses = {
|
|
28
51
|
blocks?: SignedBeaconBlock[];
|
|
29
52
|
blobSidecars?: deneb.BlobSidecars;
|
|
30
|
-
columnSidecars?:
|
|
53
|
+
columnSidecars?: DataColumnSidecar[];
|
|
54
|
+
payloadEnvelopes?: gloas.SignedExecutionPayloadEnvelope[];
|
|
31
55
|
};
|
|
32
56
|
|
|
33
57
|
export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
|
|
@@ -41,9 +65,17 @@ export type DownloadAndCacheByRangeProps = DownloadByRangeRequests & {
|
|
|
41
65
|
|
|
42
66
|
export type CacheByRangeResponsesProps = {
|
|
43
67
|
cache: SeenBlockInput;
|
|
68
|
+
seenPayloadEnvelopeInputCache: SeenPayloadEnvelopeInput;
|
|
44
69
|
peerIdStr: string;
|
|
45
70
|
responses: ValidatedResponses;
|
|
46
71
|
batchBlocks: IBlockInput[];
|
|
72
|
+
/** Raw envelopes downloaded in this batch, keyed by slot (from downloadByRange return) */
|
|
73
|
+
downloadedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null;
|
|
74
|
+
/** Envelopes already wrapped from previous partial downloads on this batch */
|
|
75
|
+
existingPayloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null;
|
|
76
|
+
/** Sampled/custody column indices for building PayloadEnvelopeInputs */
|
|
77
|
+
custodyConfig: Pick<CustodyConfig, "sampledColumns" | "custodyColumns">;
|
|
78
|
+
seenTimestampSec: number;
|
|
47
79
|
};
|
|
48
80
|
|
|
49
81
|
export type ValidatedBlock = {
|
|
@@ -58,7 +90,7 @@ export type ValidatedBlobSidecars = {
|
|
|
58
90
|
|
|
59
91
|
export type ValidatedColumnSidecars = {
|
|
60
92
|
blockRoot: Uint8Array;
|
|
61
|
-
columnSidecars:
|
|
93
|
+
columnSidecars: DataColumnSidecar[];
|
|
62
94
|
};
|
|
63
95
|
|
|
64
96
|
export type ValidatedResponses = {
|
|
@@ -72,12 +104,16 @@ export type ValidatedResponses = {
|
|
|
72
104
|
*/
|
|
73
105
|
export function cacheByRangeResponses({
|
|
74
106
|
cache,
|
|
107
|
+
seenPayloadEnvelopeInputCache,
|
|
75
108
|
peerIdStr,
|
|
76
109
|
responses,
|
|
77
110
|
batchBlocks,
|
|
78
|
-
|
|
111
|
+
downloadedPayloadEnvelopes,
|
|
112
|
+
existingPayloadEnvelopes,
|
|
113
|
+
custodyConfig,
|
|
114
|
+
seenTimestampSec,
|
|
115
|
+
}: CacheByRangeResponsesProps): {blocks: IBlockInput[]; payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null} {
|
|
79
116
|
const source = BlockInputSource.byRange;
|
|
80
|
-
const seenTimestampSec = Date.now() / 1000;
|
|
81
117
|
const updatedBatchBlocks = new Map<Slot, IBlockInput>(batchBlocks.map((block) => [block.slot, block]));
|
|
82
118
|
|
|
83
119
|
const blocks = responses.validatedBlocks ?? [];
|
|
@@ -149,16 +185,82 @@ export function cacheByRangeResponses({
|
|
|
149
185
|
}
|
|
150
186
|
}
|
|
151
187
|
|
|
188
|
+
// Build payloadEnvelopes map for gloas: start from existing (partial download) state.
|
|
189
|
+
// The entries are wrappers around (block + envelope + sampled columns) and also seeded into
|
|
190
|
+
// seenPayloadEnvelopeInputCache so importBlock can find them without creating a duplicate.
|
|
191
|
+
let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null = null;
|
|
192
|
+
if (downloadedPayloadEnvelopes !== null) {
|
|
193
|
+
payloadEnvelopes = new Map(existingPayloadEnvelopes ?? []);
|
|
194
|
+
|
|
195
|
+
for (const [slot, envelope] of downloadedPayloadEnvelopes) {
|
|
196
|
+
const blockInput = updatedBatchBlocks.get(slot);
|
|
197
|
+
if (!blockInput?.hasBlock() || !isForkPostGloas(blockInput.forkName)) {
|
|
198
|
+
// No block to pair this envelope with; drop silently
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const {blockRootHex} = blockInput;
|
|
202
|
+
|
|
203
|
+
// Reuse any existing PayloadEnvelopeInput (e.g. gossip arrived first) to avoid
|
|
204
|
+
// duplicate cache entries. If missing, create a fresh one from the block's bid.
|
|
205
|
+
let payloadInput = seenPayloadEnvelopeInputCache.get(blockRootHex);
|
|
206
|
+
if (payloadInput === undefined) {
|
|
207
|
+
payloadInput = seenPayloadEnvelopeInputCache.add({
|
|
208
|
+
blockRootHex,
|
|
209
|
+
block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
|
|
210
|
+
forkName: blockInput.forkName,
|
|
211
|
+
sampledColumns: custodyConfig.sampledColumns,
|
|
212
|
+
custodyColumns: custodyConfig.custodyColumns,
|
|
213
|
+
timeCreatedSec: seenTimestampSec,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!payloadInput.hasPayloadEnvelope()) {
|
|
218
|
+
payloadInput.addPayloadEnvelope({
|
|
219
|
+
envelope,
|
|
220
|
+
source: PayloadEnvelopeInputSource.byRange,
|
|
221
|
+
seenTimestampSec,
|
|
222
|
+
peerIdStr,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
payloadEnvelopes.set(slot, payloadInput);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
152
230
|
for (const {blockRoot, columnSidecars} of responses.validatedColumnSidecars ?? []) {
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
231
|
+
const firstColumn = columnSidecars[0];
|
|
232
|
+
if (!firstColumn) {
|
|
155
233
|
throw new Error(
|
|
156
234
|
`Coding Error: empty columnSidecars returned for blockRoot=${toRootHex(blockRoot)} from validation functions`
|
|
157
235
|
);
|
|
158
236
|
}
|
|
159
|
-
|
|
237
|
+
|
|
160
238
|
const blockRootHex = toRootHex(blockRoot);
|
|
161
239
|
|
|
240
|
+
if (isGloasDataColumnSidecar(firstColumn)) {
|
|
241
|
+
// Gloas columns are attached to the matching PayloadEnvelopeInput, NOT to IBlockInput.
|
|
242
|
+
// Gloas DataColumnSidecar has `slot` directly (no signedBlockHeader).
|
|
243
|
+
const dataSlot = firstColumn.slot;
|
|
244
|
+
const payloadInput = payloadEnvelopes?.get(dataSlot);
|
|
245
|
+
if (!payloadInput) {
|
|
246
|
+
// Should not happen: we built payloadInputs for all gloas blocks above
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
for (const columnSidecar of columnSidecars as gloas.DataColumnSidecar[]) {
|
|
250
|
+
payloadInput.addColumn({
|
|
251
|
+
columnSidecar,
|
|
252
|
+
seenTimestampSec,
|
|
253
|
+
peerIdStr,
|
|
254
|
+
source: PayloadEnvelopeInputSource.byRange,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const fuluColumns = columnSidecars as fulu.DataColumnSidecar[];
|
|
261
|
+
const dataSlot = fuluColumns[0].signedBlockHeader.message.slot;
|
|
262
|
+
const existing = updatedBatchBlocks.get(dataSlot);
|
|
263
|
+
|
|
162
264
|
if (!existing) {
|
|
163
265
|
throw new Error("Coding error: blockInput must exist when adding columns");
|
|
164
266
|
}
|
|
@@ -172,7 +274,7 @@ export function cacheByRangeResponses({
|
|
|
172
274
|
actual: existing.type,
|
|
173
275
|
});
|
|
174
276
|
}
|
|
175
|
-
for (const columnSidecar of
|
|
277
|
+
for (const columnSidecar of fuluColumns) {
|
|
176
278
|
// will throw if root hex does not match (meaning we are following the wrong chain)
|
|
177
279
|
existing.addColumn(
|
|
178
280
|
{
|
|
@@ -187,7 +289,7 @@ export function cacheByRangeResponses({
|
|
|
187
289
|
}
|
|
188
290
|
}
|
|
189
291
|
|
|
190
|
-
return Array.from(updatedBatchBlocks.values());
|
|
292
|
+
return {blocks: Array.from(updatedBatchBlocks.values()), payloadEnvelopes};
|
|
191
293
|
}
|
|
192
294
|
|
|
193
295
|
export async function downloadByRange({
|
|
@@ -198,8 +300,14 @@ export async function downloadByRange({
|
|
|
198
300
|
blocksRequest,
|
|
199
301
|
blobsRequest,
|
|
200
302
|
columnsRequest,
|
|
303
|
+
envelopesRequest,
|
|
201
304
|
peerDasMetrics,
|
|
202
|
-
}: DownloadAndCacheByRangeProps): Promise<
|
|
305
|
+
}: DownloadAndCacheByRangeProps): Promise<
|
|
306
|
+
WarnResult<
|
|
307
|
+
{responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null},
|
|
308
|
+
DownloadByRangeError
|
|
309
|
+
>
|
|
310
|
+
> {
|
|
203
311
|
let response: DownloadByRangeResponses;
|
|
204
312
|
try {
|
|
205
313
|
response = await requestByRange({
|
|
@@ -208,6 +316,7 @@ export async function downloadByRange({
|
|
|
208
316
|
blocksRequest,
|
|
209
317
|
blobsRequest,
|
|
210
318
|
columnsRequest,
|
|
319
|
+
envelopesRequest,
|
|
211
320
|
});
|
|
212
321
|
} catch (err) {
|
|
213
322
|
throw new DownloadByRangeError({
|
|
@@ -217,17 +326,16 @@ export async function downloadByRange({
|
|
|
217
326
|
});
|
|
218
327
|
}
|
|
219
328
|
|
|
220
|
-
|
|
329
|
+
return validateResponses({
|
|
221
330
|
config,
|
|
222
331
|
batchBlocks,
|
|
223
332
|
blocksRequest,
|
|
224
333
|
blobsRequest,
|
|
225
334
|
columnsRequest,
|
|
335
|
+
envelopesRequest,
|
|
226
336
|
peerDasMetrics,
|
|
227
337
|
...response,
|
|
228
338
|
});
|
|
229
|
-
|
|
230
|
-
return validated;
|
|
231
339
|
}
|
|
232
340
|
|
|
233
341
|
/**
|
|
@@ -239,13 +347,15 @@ export async function requestByRange({
|
|
|
239
347
|
blocksRequest,
|
|
240
348
|
blobsRequest,
|
|
241
349
|
columnsRequest,
|
|
350
|
+
envelopesRequest,
|
|
242
351
|
}: DownloadByRangeRequests & {
|
|
243
352
|
network: INetwork;
|
|
244
353
|
peerIdStr: PeerIdStr;
|
|
245
354
|
}): Promise<DownloadByRangeResponses> {
|
|
246
355
|
let blocks: undefined | SignedBeaconBlock[];
|
|
247
356
|
let blobSidecars: undefined | deneb.BlobSidecars;
|
|
248
|
-
let columnSidecars: undefined |
|
|
357
|
+
let columnSidecars: undefined | DataColumnSidecar[];
|
|
358
|
+
let payloadEnvelopes: undefined | gloas.SignedExecutionPayloadEnvelope[];
|
|
249
359
|
|
|
250
360
|
const requests: Promise<unknown>[] = [];
|
|
251
361
|
|
|
@@ -268,7 +378,15 @@ export async function requestByRange({
|
|
|
268
378
|
if (columnsRequest) {
|
|
269
379
|
requests.push(
|
|
270
380
|
network.sendDataColumnSidecarsByRange(peerIdStr, columnsRequest).then((columnResponse) => {
|
|
271
|
-
columnSidecars = columnResponse
|
|
381
|
+
columnSidecars = columnResponse;
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (envelopesRequest) {
|
|
387
|
+
requests.push(
|
|
388
|
+
network.sendExecutionPayloadEnvelopesByRange(peerIdStr, envelopesRequest).then((envelopeResponse) => {
|
|
389
|
+
payloadEnvelopes = envelopeResponse;
|
|
272
390
|
})
|
|
273
391
|
);
|
|
274
392
|
}
|
|
@@ -279,6 +397,7 @@ export async function requestByRange({
|
|
|
279
397
|
blocks,
|
|
280
398
|
blobSidecars,
|
|
281
399
|
columnSidecars,
|
|
400
|
+
payloadEnvelopes,
|
|
282
401
|
};
|
|
283
402
|
}
|
|
284
403
|
|
|
@@ -291,16 +410,23 @@ export async function validateResponses({
|
|
|
291
410
|
blocksRequest,
|
|
292
411
|
blobsRequest,
|
|
293
412
|
columnsRequest,
|
|
413
|
+
envelopesRequest,
|
|
294
414
|
blocks,
|
|
295
415
|
blobSidecars,
|
|
296
416
|
columnSidecars,
|
|
417
|
+
payloadEnvelopes,
|
|
297
418
|
peerDasMetrics,
|
|
298
419
|
}: DownloadByRangeRequests &
|
|
299
420
|
DownloadByRangeResponses & {
|
|
300
421
|
config: ChainForkConfig;
|
|
301
422
|
batchBlocks?: IBlockInput[];
|
|
302
423
|
peerDasMetrics?: BeaconMetrics["peerDas"] | null;
|
|
303
|
-
}): Promise<
|
|
424
|
+
}): Promise<
|
|
425
|
+
WarnResult<
|
|
426
|
+
{responses: ValidatedResponses; payloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null},
|
|
427
|
+
DownloadByRangeError
|
|
428
|
+
>
|
|
429
|
+
> {
|
|
304
430
|
// Blocks are always required for blob/column validation
|
|
305
431
|
// If a blocksRequest is provided, blocks have just been downloaded
|
|
306
432
|
// If no blocksRequest is provided, batchBlocks must have been provided from cache
|
|
@@ -326,8 +452,21 @@ export async function validateResponses({
|
|
|
326
452
|
}
|
|
327
453
|
|
|
328
454
|
const dataRequest = blobsRequest ?? columnsRequest;
|
|
455
|
+
if (!dataRequest && !envelopesRequest) {
|
|
456
|
+
return {result: {responses: validatedResponses, payloadEnvelopes: null}, warnings};
|
|
457
|
+
}
|
|
458
|
+
|
|
329
459
|
if (!dataRequest) {
|
|
330
|
-
|
|
460
|
+
// Only envelope validation needed
|
|
461
|
+
let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
|
|
462
|
+
if (envelopesRequest) {
|
|
463
|
+
validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
|
|
464
|
+
validatedResponses.validatedBlocks ?? [],
|
|
465
|
+
batchBlocks,
|
|
466
|
+
payloadEnvelopes ?? []
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
|
|
331
470
|
}
|
|
332
471
|
|
|
333
472
|
const blocksForDataValidation = getBlocksForDataValidation(
|
|
@@ -385,7 +524,17 @@ export async function validateResponses({
|
|
|
385
524
|
warnings = validatedColumnSidecarsResult.warnings;
|
|
386
525
|
}
|
|
387
526
|
|
|
388
|
-
|
|
527
|
+
// Validate envelopes if an envelopes request was made
|
|
528
|
+
let validatedPayloadEnvelopes: Map<Slot, gloas.SignedExecutionPayloadEnvelope> | null = null;
|
|
529
|
+
if (envelopesRequest) {
|
|
530
|
+
validatedPayloadEnvelopes = validateEnvelopesByRangeResponse(
|
|
531
|
+
validatedResponses.validatedBlocks ?? [],
|
|
532
|
+
batchBlocks,
|
|
533
|
+
payloadEnvelopes ?? []
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return {result: {responses: validatedResponses, payloadEnvelopes: validatedPayloadEnvelopes}, warnings};
|
|
389
538
|
}
|
|
390
539
|
|
|
391
540
|
/**
|
|
@@ -615,19 +764,19 @@ export async function validateColumnsByRangeResponse(
|
|
|
615
764
|
config: ChainForkConfig,
|
|
616
765
|
request: fulu.DataColumnSidecarsByRangeRequest,
|
|
617
766
|
blocks: ValidatedBlock[],
|
|
618
|
-
columnSidecars:
|
|
767
|
+
columnSidecars: DataColumnSidecar[],
|
|
619
768
|
peerDasMetrics?: BeaconMetrics["peerDas"] | null
|
|
620
769
|
): Promise<WarnResult<ValidatedColumnSidecars[], DownloadByRangeError>> {
|
|
621
770
|
const warnings: DownloadByRangeError[] = [];
|
|
622
771
|
|
|
623
|
-
|
|
624
|
-
// validate against the block bid commitments instead of the fulu signed header shape
|
|
625
|
-
const seenColumns = new Map<Slot, Map<number, fulu.DataColumnSidecar>>();
|
|
772
|
+
const seenColumns = new Map<Slot, Map<number, DataColumnSidecar>>();
|
|
626
773
|
let currentSlot = -1;
|
|
627
774
|
let currentIndex = -1;
|
|
628
775
|
// Check for duplicates and order
|
|
629
776
|
for (const columnSidecar of columnSidecars) {
|
|
630
|
-
const slot = columnSidecar
|
|
777
|
+
const slot = isGloasDataColumnSidecar(columnSidecar)
|
|
778
|
+
? columnSidecar.slot
|
|
779
|
+
: columnSidecar.signedBlockHeader.message.slot;
|
|
631
780
|
let seenSlotColumns = seenColumns.get(slot);
|
|
632
781
|
if (!seenSlotColumns) {
|
|
633
782
|
seenSlotColumns = new Map();
|
|
@@ -686,20 +835,20 @@ export async function validateColumnsByRangeResponse(
|
|
|
686
835
|
const slot = block.message.slot;
|
|
687
836
|
const rootHex = toRootHex(blockRoot);
|
|
688
837
|
const forkName = config.getForkName(slot);
|
|
689
|
-
const columnSidecarsMap: Map<number,
|
|
838
|
+
const columnSidecarsMap: Map<number, DataColumnSidecar> = seenColumns.get(slot) ?? new Map();
|
|
690
839
|
const columnSidecars = Array.from(columnSidecarsMap.values()).sort((a, b) => a.index - b.index);
|
|
691
840
|
|
|
692
841
|
let blobCount: number;
|
|
693
842
|
if (!isForkPostFulu(forkName)) {
|
|
694
|
-
const dataSlot = columnSidecars.at(0)?.signedBlockHeader.message.slot;
|
|
695
843
|
throw new DownloadByRangeError({
|
|
696
844
|
code: DownloadByRangeErrorCode.MISMATCH_BLOCK_FORK,
|
|
697
845
|
slot,
|
|
698
846
|
blockFork: forkName,
|
|
699
|
-
dataFork:
|
|
847
|
+
dataFork: "unknown",
|
|
700
848
|
});
|
|
701
849
|
}
|
|
702
|
-
|
|
850
|
+
const kzgCommitments = getBlobKzgCommitments(forkName, block as SignedBeaconBlock<ForkPostFulu>);
|
|
851
|
+
blobCount = kzgCommitments.length;
|
|
703
852
|
|
|
704
853
|
if (columnSidecars.length === 0) {
|
|
705
854
|
if (!blobCount) {
|
|
@@ -768,15 +917,25 @@ export async function validateColumnsByRangeResponse(
|
|
|
768
917
|
);
|
|
769
918
|
}
|
|
770
919
|
|
|
920
|
+
const validatePromise = isForkPostGloas(forkName)
|
|
921
|
+
? validateGloasBlockDataColumnSidecars(
|
|
922
|
+
slot,
|
|
923
|
+
blockRoot,
|
|
924
|
+
kzgCommitments,
|
|
925
|
+
columnSidecars as gloas.DataColumnSidecar[],
|
|
926
|
+
peerDasMetrics
|
|
927
|
+
)
|
|
928
|
+
: validateFuluBlockDataColumnSidecars(
|
|
929
|
+
null, // do not pass chain here so we do not validate header signature
|
|
930
|
+
slot,
|
|
931
|
+
blockRoot,
|
|
932
|
+
blobCount,
|
|
933
|
+
columnSidecars as fulu.DataColumnSidecar[],
|
|
934
|
+
peerDasMetrics
|
|
935
|
+
);
|
|
936
|
+
|
|
771
937
|
validationPromises.push(
|
|
772
|
-
|
|
773
|
-
null, // do not pass chain here so we do not validate header signature
|
|
774
|
-
slot,
|
|
775
|
-
blockRoot,
|
|
776
|
-
blobCount,
|
|
777
|
-
columnSidecars,
|
|
778
|
-
peerDasMetrics
|
|
779
|
-
).then(() => ({
|
|
938
|
+
validatePromise.then(() => ({
|
|
780
939
|
blockRoot,
|
|
781
940
|
columnSidecars,
|
|
782
941
|
}))
|
|
@@ -882,6 +1041,9 @@ export enum DownloadByRangeErrorCode {
|
|
|
882
1041
|
/** Cached block input type mismatches new data */
|
|
883
1042
|
MISMATCH_BLOCK_FORK = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_FORK",
|
|
884
1043
|
MISMATCH_BLOCK_INPUT_TYPE = "DOWNLOAD_BY_RANGE_ERROR_MISMATCH_BLOCK_INPUT_TYPE",
|
|
1044
|
+
|
|
1045
|
+
/** Envelope beaconBlockRoot does not match the block's root */
|
|
1046
|
+
INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
|
|
885
1047
|
}
|
|
886
1048
|
|
|
887
1049
|
export type DownloadByRangeErrorType =
|
|
@@ -973,6 +1135,61 @@ export type DownloadByRangeErrorType =
|
|
|
973
1135
|
blockRoot: string;
|
|
974
1136
|
expected: DAType;
|
|
975
1137
|
actual: DAType;
|
|
1138
|
+
}
|
|
1139
|
+
| {
|
|
1140
|
+
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT;
|
|
1141
|
+
slot: Slot;
|
|
1142
|
+
expected: string;
|
|
1143
|
+
actual: string;
|
|
976
1144
|
};
|
|
977
1145
|
|
|
978
1146
|
export class DownloadByRangeError extends LodestarError<DownloadByRangeErrorType> {}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Validates SignedExecutionPayloadEnvelopes received for a range request.
|
|
1150
|
+
* For each envelope whose slot appears in the downloaded blocks, verifies that
|
|
1151
|
+
* envelope.message.beaconBlockRoot matches the corresponding block's root.
|
|
1152
|
+
* Envelopes for slots not in the batch (orphaned payloads) are silently ignored.
|
|
1153
|
+
*/
|
|
1154
|
+
export function validateEnvelopesByRangeResponse(
|
|
1155
|
+
validatedBlocks: ValidatedBlock[],
|
|
1156
|
+
batchBlocks: IBlockInput[] | undefined,
|
|
1157
|
+
payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[]
|
|
1158
|
+
): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
|
|
1159
|
+
// Build a map of slot -> blockRoot for all blocks in the batch
|
|
1160
|
+
const batchBlockRoots = new Map<Slot, Uint8Array>();
|
|
1161
|
+
if (batchBlocks) {
|
|
1162
|
+
for (const blockInput of batchBlocks) {
|
|
1163
|
+
batchBlockRoots.set(blockInput.slot, fromHex(blockInput.blockRootHex));
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
for (const {block, blockRoot} of validatedBlocks) {
|
|
1167
|
+
batchBlockRoots.set(block.message.slot, blockRoot);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
|
|
1171
|
+
|
|
1172
|
+
for (const payloadEnvelope of payloadEnvelopes) {
|
|
1173
|
+
const slot = payloadEnvelope.message.payload.slotNumber;
|
|
1174
|
+
const batchBlockRoot = batchBlockRoots.get(slot);
|
|
1175
|
+
|
|
1176
|
+
// Envelopes for slots not in the batch are silently ignored (orphaned payloads)
|
|
1177
|
+
if (batchBlockRoot === undefined) {
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Verify beaconBlockRoot matches the block's root
|
|
1182
|
+
if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlockRoot)) {
|
|
1183
|
+
throw new DownloadByRangeError({
|
|
1184
|
+
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
|
|
1185
|
+
slot,
|
|
1186
|
+
expected: toRootHex(batchBlockRoot),
|
|
1187
|
+
actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
payloadEnvelopeMap.set(slot, payloadEnvelope);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
return payloadEnvelopeMap;
|
|
1195
|
+
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ForkPostDeneb,
|
|
5
|
+
ForkPostFulu,
|
|
6
|
+
ForkPreFulu,
|
|
7
|
+
isForkPostDeneb,
|
|
8
|
+
isForkPostFulu,
|
|
9
|
+
isForkPostGloas,
|
|
10
|
+
} from "@lodestar/params";
|
|
4
11
|
import {BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
|
|
5
12
|
import {LodestarError, byteArrayEquals, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
|
|
6
13
|
import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
|
|
@@ -263,7 +270,10 @@ export async function fetchByRoot({
|
|
|
263
270
|
blockRoot,
|
|
264
271
|
});
|
|
265
272
|
const forkName = config.getForkName(block.message.slot);
|
|
266
|
-
if (
|
|
273
|
+
if (isForkPostGloas(forkName)) {
|
|
274
|
+
// Post-gloas block sync only needs the block body. Payload columns stay on the
|
|
275
|
+
// payload/envelope path and are queued independently in the network processor.
|
|
276
|
+
} else if (isForkPostFulu(forkName)) {
|
|
267
277
|
columnSidecarResult = await fetchAndValidateColumns({
|
|
268
278
|
config,
|
|
269
279
|
chain,
|
|
@@ -40,21 +40,6 @@ function addToDescendantBlocks(
|
|
|
40
40
|
return descendantBlocks;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export function getDescendantBlocks(
|
|
44
|
-
blockRootHex: RootHex,
|
|
45
|
-
blocks: Map<RootHex, BlockInputSyncCacheItem>
|
|
46
|
-
): BlockInputSyncCacheItem[] {
|
|
47
|
-
const descendantBlocks: BlockInputSyncCacheItem[] = [];
|
|
48
|
-
|
|
49
|
-
for (const block of blocks.values()) {
|
|
50
|
-
if ((isPendingBlockInput(block) ? block.blockInput.parentRootHex : undefined) === blockRootHex) {
|
|
51
|
-
descendantBlocks.push(block);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return descendantBlocks;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
43
|
export type UnknownAndAncestorBlocks = {
|
|
59
44
|
unknowns: BlockInputSyncCacheItem[];
|
|
60
45
|
ancestors: PendingBlockInput[];
|