@lodestar/beacon-node 1.50.0-dev.db953a4f93 → 1.50.0-dev.e8dec5f2fe
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 +6 -1
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js +13 -0
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +23 -3
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +26 -18
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +1 -0
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +23 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +14 -24
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +1 -1
- package/lib/chain/archiveStore/interface.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +7 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +1 -1
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +9 -5
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +53 -18
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +7 -1
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +2 -0
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +3 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +2 -0
- package/lib/chain/errors/gossipValidation.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +3 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
- package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +4 -4
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/options.d.ts +2 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +5 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -2
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +2 -0
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +10 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -0
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +21 -0
- package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/shufflingCache.d.ts +1 -0
- package/lib/chain/shufflingCache.d.ts.map +1 -1
- package/lib/chain/shufflingCache.js +4 -0
- package/lib/chain/shufflingCache.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +5 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +64 -54
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts +2 -0
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +3 -0
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/buckets.d.ts +2 -1
- package/lib/db/buckets.d.ts.map +1 -1
- package/lib/db/buckets.js +1 -0
- package/lib/db/buckets.js.map +1 -1
- package/lib/db/interface.d.ts +2 -0
- package/lib/db/interface.d.ts.map +1 -1
- package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
- package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
- package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
- package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
- package/lib/db/repositories/earliestAvailableSlot.js +19 -0
- package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
- package/lib/execution/engine/disabled.d.ts +0 -2
- package/lib/execution/engine/disabled.d.ts.map +1 -1
- package/lib/execution/engine/disabled.js +0 -6
- package/lib/execution/engine/disabled.js.map +1 -1
- package/lib/execution/engine/http.d.ts +1 -3
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +2 -17
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -4
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/mock.d.ts +0 -2
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +0 -8
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/types.d.ts +0 -8
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.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/metrics/metrics.d.ts +7 -3
- package/lib/metrics/metrics.d.ts.map +1 -1
- package/lib/metrics/metrics.js +4 -4
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +62 -63
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +7 -3
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +9 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +77 -10
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/node/nodejs.d.ts +3 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +27 -4
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +6 -0
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +11 -0
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +6 -1
- 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 +50 -18
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +5 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +49 -24
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +8 -0
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +32 -9
- package/lib/sync/utils/downloadByRange.js.map +1 -1
- package/lib/util/execution.d.ts +9 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +5 -1
- package/lib/util/execution.js.map +1 -1
- package/package.json +17 -17
- package/src/api/impl/beacon/blocks/index.ts +7 -4
- package/src/api/impl/beacon/index.ts +15 -0
- package/src/api/impl/beacon/state/index.ts +31 -3
- package/src/api/impl/debug/index.ts +28 -19
- package/src/api/impl/validator/index.ts +24 -2
- package/src/chain/archiveStore/archiveStore.ts +19 -25
- package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -2
- package/src/chain/archiveStore/historicalState/worker.ts +1 -2
- package/src/chain/archiveStore/interface.ts +1 -1
- package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
- package/src/chain/blocks/importBlock.ts +9 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
- package/src/chain/blocks/verifyBlock.ts +1 -0
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
- package/src/chain/chain.ts +73 -20
- package/src/chain/emitter.ts +7 -1
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/errors/gossipValidation.ts +2 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/interface.ts +3 -2
- package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
- package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
- package/src/chain/options.ts +3 -3
- package/src/chain/prepareNextSlot.ts +6 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
- package/src/chain/produceBlock/produceBlockBody.ts +8 -1
- package/src/chain/regen/regen.ts +12 -4
- package/src/chain/seenCache/seenGossipBlockInput.ts +23 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
- package/src/chain/shufflingCache.ts +5 -0
- package/src/chain/validatorMonitor.ts +75 -57
- package/src/db/beacon.ts +3 -0
- package/src/db/buckets.ts +1 -0
- package/src/db/interface.ts +2 -0
- package/src/db/repositories/earliestAvailableSlot.ts +19 -0
- package/src/execution/engine/disabled.ts +0 -8
- package/src/execution/engine/http.ts +1 -30
- package/src/execution/engine/interface.ts +2 -6
- package/src/execution/engine/mock.ts +1 -19
- package/src/execution/engine/types.ts +0 -10
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/metrics/metrics.ts +21 -6
- package/src/network/processor/gossipHandlers.ts +93 -76
- package/src/network/processor/gossipValidatorFn.ts +13 -3
- package/src/network/processor/index.ts +89 -10
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
- package/src/node/nodejs.ts +35 -3
- package/src/sync/range/batch.ts +12 -0
- package/src/sync/range/chain.ts +7 -1
- package/src/sync/range/range.ts +50 -18
- package/src/sync/unknownBlock.ts +62 -25
- package/src/sync/utils/downloadByRange.ts +47 -8
- package/src/util/execution.ts +16 -8
package/src/node/nodejs.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import {setMaxListeners} from "node:events";
|
|
2
2
|
import {PrivateKey} from "@libp2p/interface";
|
|
3
3
|
import {Registry} from "prom-client";
|
|
4
|
+
import {
|
|
5
|
+
init as initNativeMetrics,
|
|
6
|
+
registerLocalValidator as registerNativeLocalValidator,
|
|
7
|
+
scrapeMetrics as scrapeNativeMetrics,
|
|
8
|
+
unregisterLocalValidator as unregisterNativeLocalValidator,
|
|
9
|
+
} from "@chainsafe/lodestar-z/metrics";
|
|
4
10
|
import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
|
|
5
11
|
import {hasher} from "@chainsafe/persistent-merkle-tree";
|
|
6
12
|
import {BeaconApiMethods} from "@lodestar/api/beacon/server";
|
|
@@ -8,6 +14,7 @@ import {BeaconConfig} from "@lodestar/config";
|
|
|
8
14
|
import type {LoggerNode} from "@lodestar/logger/node";
|
|
9
15
|
import {ZERO_HASH_HEX} from "@lodestar/params";
|
|
10
16
|
import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
|
|
17
|
+
import {Slot} from "@lodestar/types";
|
|
11
18
|
import {sleep, toRootHex} from "@lodestar/utils";
|
|
12
19
|
import {ProcessShutdownCallback} from "@lodestar/validator";
|
|
13
20
|
import {BeaconRestApiServer, getApi} from "../api/index.js";
|
|
@@ -55,6 +62,7 @@ export type BeaconNodeInitModules = {
|
|
|
55
62
|
peerStoreDir?: string;
|
|
56
63
|
anchorState: IBeaconStateView;
|
|
57
64
|
isAnchorStateFinalized: boolean;
|
|
65
|
+
earliestAvailableSlot: Slot;
|
|
58
66
|
metricsRegistries?: Registry[];
|
|
59
67
|
};
|
|
60
68
|
|
|
@@ -153,6 +161,7 @@ export class BeaconNode {
|
|
|
153
161
|
peerStoreDir,
|
|
154
162
|
anchorState,
|
|
155
163
|
isAnchorStateFinalized,
|
|
164
|
+
earliestAvailableSlot,
|
|
156
165
|
metricsRegistries = [],
|
|
157
166
|
}: BeaconNodeInitModules): Promise<T> {
|
|
158
167
|
if (hasher.name !== "hashtree") {
|
|
@@ -171,7 +180,13 @@ export class BeaconNode {
|
|
|
171
180
|
// monitoring relies on metrics data
|
|
172
181
|
opts.monitoring.endpoint
|
|
173
182
|
) {
|
|
174
|
-
|
|
183
|
+
if (opts.chain.nativeStateTransition) {
|
|
184
|
+
initNativeMetrics();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries, {
|
|
188
|
+
includeStateTransitionMetrics: !opts.chain.nativeStateTransition,
|
|
189
|
+
});
|
|
175
190
|
initBeaconMetrics(metrics, anchorState);
|
|
176
191
|
// Since the db is instantiated before this, metrics must be injected manually afterwards
|
|
177
192
|
db.setMetrics(metrics.db, metrics.flatFileStore);
|
|
@@ -185,7 +200,13 @@ export class BeaconNode {
|
|
|
185
200
|
config,
|
|
186
201
|
anchorState.genesisTime,
|
|
187
202
|
logger.child({module: LoggerModule.vmon}),
|
|
188
|
-
opts.validatorMonitor
|
|
203
|
+
opts.validatorMonitor,
|
|
204
|
+
opts.chain.nativeStateTransition
|
|
205
|
+
? {
|
|
206
|
+
registerLocalValidator: registerNativeLocalValidator,
|
|
207
|
+
unregisterLocalValidator: unregisterNativeLocalValidator,
|
|
208
|
+
}
|
|
209
|
+
: null
|
|
189
210
|
)
|
|
190
211
|
: null;
|
|
191
212
|
|
|
@@ -248,6 +269,7 @@ export class BeaconNode {
|
|
|
248
269
|
validatorMonitor,
|
|
249
270
|
anchorState,
|
|
250
271
|
isAnchorStateFinalized,
|
|
272
|
+
earliestAvailableSlot,
|
|
251
273
|
executionEngine: initializeExecutionEngine(executionEngineOpts, {
|
|
252
274
|
metrics,
|
|
253
275
|
signal,
|
|
@@ -303,7 +325,17 @@ export class BeaconNode {
|
|
|
303
325
|
const metricsServer = opts.metrics.enabled
|
|
304
326
|
? await getHttpMetricsServer(opts.metrics, {
|
|
305
327
|
register: (metrics as Metrics).register,
|
|
306
|
-
getOtherMetrics: async () =>
|
|
328
|
+
getOtherMetrics: async () => {
|
|
329
|
+
const otherMetrics = await Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]);
|
|
330
|
+
if (opts.chain.nativeStateTransition) {
|
|
331
|
+
try {
|
|
332
|
+
otherMetrics.push(scrapeNativeMetrics());
|
|
333
|
+
} catch (e) {
|
|
334
|
+
logger.warn("Failed to scrape native state-transition metrics", {}, e as Error);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return otherMetrics;
|
|
338
|
+
},
|
|
307
339
|
logger: logger.child({module: LoggerModule.metrics}),
|
|
308
340
|
})
|
|
309
341
|
: null;
|
package/src/sync/range/batch.ts
CHANGED
|
@@ -224,6 +224,7 @@ export class Batch {
|
|
|
224
224
|
return {
|
|
225
225
|
blockRoot: parentBlockRoot,
|
|
226
226
|
blockRootHex: toRootHex(parentBlockRoot),
|
|
227
|
+
blockHash: this.latestBid.blockHash,
|
|
227
228
|
kzgCommitments: this.latestBid.blobKzgCommitments,
|
|
228
229
|
};
|
|
229
230
|
}
|
|
@@ -773,6 +774,7 @@ export class Batch {
|
|
|
773
774
|
// remove any downloaded blocks and re-attempt
|
|
774
775
|
// TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
|
|
775
776
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
777
|
+
this.resetRequests();
|
|
776
778
|
}
|
|
777
779
|
|
|
778
780
|
private onProcessingError(attempt: FailedAttempt): void {
|
|
@@ -784,6 +786,16 @@ export class Batch {
|
|
|
784
786
|
// remove any downloaded blocks and re-attempt
|
|
785
787
|
// TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
|
|
786
788
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
789
|
+
this.resetRequests();
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* After a partial download `requests` only cover the data still missing from the downloaded blocks,
|
|
794
|
+
* e.g. a single column. Once those blocks are dropped, request the whole batch again, otherwise
|
|
795
|
+
* every peer fails the retry for lack of blocks to validate the data against.
|
|
796
|
+
*/
|
|
797
|
+
private resetRequests(): void {
|
|
798
|
+
this.requests = this.getRequests([]);
|
|
787
799
|
}
|
|
788
800
|
|
|
789
801
|
/** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -225,6 +225,9 @@ export class SyncChain {
|
|
|
225
225
|
Math.floor((localFinalizedEpoch - this.lastEpochWithProcessBlocks) / EPOCHS_PER_BATCH) * EPOCHS_PER_BATCH;
|
|
226
226
|
this.advanceChain(lastEpochWithProcessBlocksAligned);
|
|
227
227
|
|
|
228
|
+
// stopSyncing() cleared the retry timer, but peers can still be in rate limit backoff
|
|
229
|
+
this.scheduleRateLimitBackoffRetry();
|
|
230
|
+
|
|
228
231
|
// Potentially download new batches and process pending
|
|
229
232
|
this.triggerBatchDownloader();
|
|
230
233
|
this.triggerBatchProcessor();
|
|
@@ -417,8 +420,10 @@ export class SyncChain {
|
|
|
417
420
|
this.rateLimitBackoffTimeout = setTimeout(
|
|
418
421
|
() => {
|
|
419
422
|
this.rateLimitBackoffTimeout = undefined;
|
|
420
|
-
|
|
423
|
+
// Must run before the downloader, it drops expired peers without triggering a download. If it ran
|
|
424
|
+
// after, a backoff expiring in between would leave the peer idle with no retry scheduled.
|
|
421
425
|
this.scheduleRateLimitBackoffRetry();
|
|
426
|
+
this.triggerBatchDownloader();
|
|
422
427
|
},
|
|
423
428
|
Math.max(0, retryAt - now)
|
|
424
429
|
);
|
|
@@ -581,6 +586,7 @@ export class SyncChain {
|
|
|
581
586
|
case DownloadByRangeErrorCode.OUT_OF_RANGE_BLOCKS:
|
|
582
587
|
case DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH:
|
|
583
588
|
case DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT:
|
|
589
|
+
case DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH:
|
|
584
590
|
case DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT:
|
|
585
591
|
case BlobSidecarErrorCode.INCLUSION_PROOF_INVALID:
|
|
586
592
|
case BlobSidecarErrorCode.INVALID_KZG_PROOF_BATCH:
|
package/src/sync/range/range.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "
|
|
|
5
5
|
import {Epoch, Status, fulu} from "@lodestar/types";
|
|
6
6
|
import {Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
|
|
7
7
|
import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
8
|
+
import {PayloadError, PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
8
9
|
import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
|
|
9
10
|
import {assertLinearChainSegment} from "../../chain/blocks/utils/chainSegment.js";
|
|
10
11
|
import {BlockError} from "../../chain/errors/index.js";
|
|
@@ -207,22 +208,50 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
207
208
|
blsVerifyOnMainThread: false,
|
|
208
209
|
};
|
|
209
210
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
try {
|
|
212
|
+
if (this.opts?.disableProcessAsChainSegment) {
|
|
213
|
+
// Should only be used for debugging or testing
|
|
214
|
+
for (const block of blocks) {
|
|
215
|
+
await this.chain.processBlock(block, flags);
|
|
216
|
+
const payloadEnvelope = payloadEnvelopes?.get(block.slot);
|
|
217
|
+
if (payloadEnvelope?.hasPayloadEnvelope()) {
|
|
218
|
+
await this.chain.processExecutionPayload(payloadEnvelope);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
const {orphaned, skipped} = await this.chain.processChainSegment(blocks, payloadEnvelopes, flags);
|
|
223
|
+
// We log orphaned payloads to work with different clients
|
|
224
|
+
// in the future, consider applying penalties in certain conditions
|
|
225
|
+
// make sure the payload source was from range sync in that case
|
|
226
|
+
for (const {slot, payloadEnvelopeInput} of orphaned) {
|
|
227
|
+
const {peerIdStr, source} = payloadEnvelopeInput.getPayloadEnvelopeSource();
|
|
228
|
+
let client = "unknown";
|
|
229
|
+
if (peerIdStr !== undefined) {
|
|
230
|
+
try {
|
|
231
|
+
client = this.getConnectedPeerSyncMeta(peerIdStr).client;
|
|
232
|
+
} catch {
|
|
233
|
+
// peer disconnected since serving the envelope, keep "unknown"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
this.logger.debug("Orphaned payload envelope in range sync batch", {
|
|
237
|
+
slot,
|
|
238
|
+
root: payloadEnvelopeInput.blockRootHex,
|
|
239
|
+
peer: peerIdStr ?? "unknown",
|
|
240
|
+
source,
|
|
241
|
+
client,
|
|
242
|
+
skipped,
|
|
243
|
+
});
|
|
217
244
|
}
|
|
218
245
|
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
246
|
+
} catch (e) {
|
|
247
|
+
if (
|
|
248
|
+
e instanceof PayloadError &&
|
|
249
|
+
(e.type.code === PayloadErrorCode.INVALID_SIGNATURE ||
|
|
250
|
+
e.type.code === PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR ||
|
|
251
|
+
e.type.code === PayloadErrorCode.EXECUTION_ENGINE_INVALID)
|
|
252
|
+
) {
|
|
253
|
+
const {payloadInput} = e;
|
|
254
|
+
const {peerIdStr, source} = payloadInput.getPayloadEnvelopeSource();
|
|
226
255
|
let client = "unknown";
|
|
227
256
|
if (peerIdStr !== undefined) {
|
|
228
257
|
try {
|
|
@@ -231,15 +260,18 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
231
260
|
// peer disconnected since serving the envelope, keep "unknown"
|
|
232
261
|
}
|
|
233
262
|
}
|
|
234
|
-
this.logger.debug("
|
|
235
|
-
slot,
|
|
236
|
-
root:
|
|
263
|
+
this.logger.debug("Removing invalid payload envelope in range sync batch", {
|
|
264
|
+
slot: payloadInput.slot,
|
|
265
|
+
root: payloadInput.blockRootHex,
|
|
237
266
|
peer: peerIdStr ?? "unknown",
|
|
238
267
|
source,
|
|
239
268
|
client,
|
|
240
|
-
|
|
269
|
+
code: e.type.code,
|
|
241
270
|
});
|
|
271
|
+
// The batch is downloaded again, it would otherwise reuse the cached envelope and fail the same way
|
|
272
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
|
|
242
273
|
}
|
|
274
|
+
throw e;
|
|
243
275
|
}
|
|
244
276
|
};
|
|
245
277
|
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -73,7 +73,11 @@ type AdvancePendingBlockResult =
|
|
|
73
73
|
| "removed";
|
|
74
74
|
|
|
75
75
|
class UnknownBlockRateLimitedError extends Error {
|
|
76
|
-
constructor(
|
|
76
|
+
constructor(
|
|
77
|
+
message: string,
|
|
78
|
+
/** Earliest backoff expiry seen when the download was deferred, it is retried once that has passed */
|
|
79
|
+
readonly retryAt: number
|
|
80
|
+
) {
|
|
77
81
|
super(message);
|
|
78
82
|
this.name = "UnknownBlockRateLimitedError";
|
|
79
83
|
}
|
|
@@ -849,17 +853,20 @@ export class BlockInputSync {
|
|
|
849
853
|
}
|
|
850
854
|
};
|
|
851
855
|
|
|
852
|
-
|
|
856
|
+
/**
|
|
857
|
+
* Returns false if there is no backoff left to wait for, in which case no retry is scheduled.
|
|
858
|
+
*/
|
|
859
|
+
private scheduleRateLimitBackoffRetry(): boolean {
|
|
853
860
|
this.clearRateLimitBackoffTimer();
|
|
854
861
|
|
|
855
862
|
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
856
|
-
return;
|
|
863
|
+
return false;
|
|
857
864
|
}
|
|
858
865
|
|
|
859
866
|
const now = Date.now();
|
|
860
867
|
const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
|
|
861
868
|
if (retryAt === null) {
|
|
862
|
-
return;
|
|
869
|
+
return false;
|
|
863
870
|
}
|
|
864
871
|
|
|
865
872
|
this.rateLimitBackoffTimeout = setTimeout(
|
|
@@ -870,6 +877,7 @@ export class BlockInputSync {
|
|
|
870
877
|
},
|
|
871
878
|
Math.max(0, retryAt - now)
|
|
872
879
|
);
|
|
880
|
+
return true;
|
|
873
881
|
}
|
|
874
882
|
|
|
875
883
|
private clearRateLimitBackoffTimer(): void {
|
|
@@ -1081,7 +1089,11 @@ export class BlockInputSync {
|
|
|
1081
1089
|
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
1082
1090
|
}
|
|
1083
1091
|
this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
|
|
1084
|
-
this.scheduleRateLimitBackoffRetry()
|
|
1092
|
+
if (!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now()) {
|
|
1093
|
+
// The backoff this download waited for expired in the meantime, a timer is only armed if another
|
|
1094
|
+
// peer is still in backoff and would retry it later than needed
|
|
1095
|
+
this.triggerUnknownBlockSearch();
|
|
1096
|
+
}
|
|
1085
1097
|
return;
|
|
1086
1098
|
}
|
|
1087
1099
|
|
|
@@ -1364,6 +1376,14 @@ export class BlockInputSync {
|
|
|
1364
1376
|
if (!isPendingPayloadEnvelope(payload)) {
|
|
1365
1377
|
payload.status = PendingPayloadInputStatus.pending;
|
|
1366
1378
|
}
|
|
1379
|
+
if (
|
|
1380
|
+
res.err instanceof UnknownBlockRateLimitedError &&
|
|
1381
|
+
(!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now())
|
|
1382
|
+
) {
|
|
1383
|
+
// The backoff this download waited for expired in the meantime, a timer is only armed if another
|
|
1384
|
+
// peer is still in backoff and would retry it later than needed
|
|
1385
|
+
this.triggerUnknownBlockSearch();
|
|
1386
|
+
}
|
|
1367
1387
|
}
|
|
1368
1388
|
|
|
1369
1389
|
private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
|
|
@@ -1426,20 +1446,23 @@ export class BlockInputSync {
|
|
|
1426
1446
|
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
1427
1447
|
break;
|
|
1428
1448
|
|
|
1449
|
+
// The invalid envelope is evicted from the seen cache in the cases below, otherwise the next
|
|
1450
|
+
// attempt would reuse it instead of fetching another envelope from peers
|
|
1429
1451
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1430
1452
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1453
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1431
1454
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
|
|
1432
1455
|
break;
|
|
1433
1456
|
|
|
1434
1457
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1435
1458
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1459
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1436
1460
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
|
|
1437
1461
|
break;
|
|
1438
1462
|
|
|
1439
1463
|
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1440
|
-
// TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
|
|
1441
|
-
// reintroducing envelope replacement / recreation flows.
|
|
1442
1464
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1465
|
+
this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
|
|
1443
1466
|
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
|
|
1444
1467
|
break;
|
|
1445
1468
|
|
|
@@ -1485,16 +1508,21 @@ export class BlockInputSync {
|
|
|
1485
1508
|
const pendingColumns = payloadInput?.hasAllData()
|
|
1486
1509
|
? new Set<number>()
|
|
1487
1510
|
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
1511
|
+
// Same `now` for both calls, a backoff expiring in between would be reported as no peer available
|
|
1512
|
+
const now = Date.now();
|
|
1488
1513
|
// prefer peers that gossiped this payload root to us (#9923)
|
|
1489
1514
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
|
|
1490
1515
|
pendingColumns,
|
|
1491
1516
|
excludedPeers,
|
|
1492
|
-
cacheItem.peerIdStrings
|
|
1517
|
+
cacheItem.peerIdStrings,
|
|
1518
|
+
now
|
|
1493
1519
|
);
|
|
1494
1520
|
if (peerMeta === null) {
|
|
1495
|
-
|
|
1521
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
|
|
1522
|
+
if (retryAt !== null) {
|
|
1496
1523
|
throw new UnknownBlockRateLimitedError(
|
|
1497
|
-
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited
|
|
1524
|
+
`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
|
|
1525
|
+
retryAt
|
|
1498
1526
|
);
|
|
1499
1527
|
}
|
|
1500
1528
|
|
|
@@ -1633,9 +1661,11 @@ export class BlockInputSync {
|
|
|
1633
1661
|
);
|
|
1634
1662
|
this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
|
|
1635
1663
|
|
|
1636
|
-
|
|
1664
|
+
const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
|
|
1665
|
+
if (retryAt !== null) {
|
|
1637
1666
|
throw new UnknownBlockRateLimitedError(
|
|
1638
|
-
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited
|
|
1667
|
+
`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`,
|
|
1668
|
+
retryAt
|
|
1639
1669
|
);
|
|
1640
1670
|
}
|
|
1641
1671
|
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
@@ -1727,16 +1757,21 @@ export class BlockInputSync {
|
|
|
1727
1757
|
isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
1728
1758
|
? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
|
|
1729
1759
|
: defaultPendingColumns;
|
|
1760
|
+
// Same `now` for both calls, a backoff expiring in between would be reported as no peer available
|
|
1761
|
+
const now = Date.now();
|
|
1730
1762
|
// prefer peers that gossiped this block root to us (#9923)
|
|
1731
1763
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
|
|
1732
1764
|
pendingColumns,
|
|
1733
1765
|
excludedPeers,
|
|
1734
|
-
cacheItem.peerIdStrings
|
|
1766
|
+
cacheItem.peerIdStrings,
|
|
1767
|
+
now
|
|
1735
1768
|
);
|
|
1736
1769
|
if (peerMeta === null) {
|
|
1737
|
-
|
|
1770
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
|
|
1771
|
+
if (retryAt !== null) {
|
|
1738
1772
|
throw new UnknownBlockRateLimitedError(
|
|
1739
|
-
`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited
|
|
1773
|
+
`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
|
|
1774
|
+
retryAt
|
|
1740
1775
|
);
|
|
1741
1776
|
}
|
|
1742
1777
|
|
|
@@ -1842,8 +1877,9 @@ export class BlockInputSync {
|
|
|
1842
1877
|
|
|
1843
1878
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1844
1879
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1880
|
+
const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
|
|
1881
|
+
if (retryAt !== null) {
|
|
1882
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`, retryAt);
|
|
1847
1883
|
}
|
|
1848
1884
|
|
|
1849
1885
|
if (!isPendingBlockInput(cacheItem)) {
|
|
@@ -1879,8 +1915,6 @@ export class BlockInputSync {
|
|
|
1879
1915
|
|
|
1880
1916
|
// Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
|
|
1881
1917
|
private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
|
|
1882
|
-
// Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
|
|
1883
|
-
// later validation/finalization logic decides when it can leave memory.
|
|
1884
1918
|
if (this.pendingPayloads.delete(rootHex)) {
|
|
1885
1919
|
this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
|
|
1886
1920
|
}
|
|
@@ -2036,8 +2070,11 @@ export class UnknownBlockPeerBalancer {
|
|
|
2036
2070
|
this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
|
|
2037
2071
|
}
|
|
2038
2072
|
|
|
2039
|
-
getNextRateLimitRetryAt(
|
|
2040
|
-
|
|
2073
|
+
getNextRateLimitRetryAt(
|
|
2074
|
+
pendingColumns?: Set<number>,
|
|
2075
|
+
excludedPeers?: Set<PeerIdStr>,
|
|
2076
|
+
now = Date.now()
|
|
2077
|
+
): number | null {
|
|
2041
2078
|
let retryAt: number | null = null;
|
|
2042
2079
|
|
|
2043
2080
|
for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
|
|
@@ -2076,9 +2113,10 @@ export class UnknownBlockPeerBalancer {
|
|
|
2076
2113
|
bestPeerForPendingColumns(
|
|
2077
2114
|
pendingColumns: Set<number>,
|
|
2078
2115
|
excludedPeers: Set<PeerIdStr>,
|
|
2079
|
-
preferredPeers?: Set<PeerIdStr
|
|
2116
|
+
preferredPeers?: Set<PeerIdStr>,
|
|
2117
|
+
now = Date.now()
|
|
2080
2118
|
): PeerSyncMeta | null {
|
|
2081
|
-
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
|
|
2119
|
+
const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers, now);
|
|
2082
2120
|
if (eligiblePeers.length === 0) {
|
|
2083
2121
|
return null;
|
|
2084
2122
|
}
|
|
@@ -2119,8 +2157,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
2119
2157
|
return totalActiveRequests;
|
|
2120
2158
|
}
|
|
2121
2159
|
|
|
2122
|
-
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr
|
|
2123
|
-
const now = Date.now();
|
|
2160
|
+
private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>, now: number): PeerIdStr[] {
|
|
2124
2161
|
let maxColumnCount = 0;
|
|
2125
2162
|
const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
|
|
2126
2163
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
deneb,
|
|
17
17
|
fulu,
|
|
18
18
|
gloas,
|
|
19
|
+
isGloasBeaconBlock,
|
|
19
20
|
isGloasDataColumnSidecar,
|
|
20
21
|
phase0,
|
|
21
22
|
} from "@lodestar/types";
|
|
@@ -62,6 +63,7 @@ export type DownloadByRangeRequests = {
|
|
|
62
63
|
export type ParentPayloadCommitments = {
|
|
63
64
|
blockRoot: Uint8Array;
|
|
64
65
|
blockRootHex: RootHex;
|
|
66
|
+
blockHash: Uint8Array;
|
|
65
67
|
kzgCommitments: deneb.BlobKzgCommitments;
|
|
66
68
|
};
|
|
67
69
|
|
|
@@ -1171,6 +1173,8 @@ export enum DownloadByRangeErrorCode {
|
|
|
1171
1173
|
|
|
1172
1174
|
/** Envelope beaconBlockRoot does not match the block's root */
|
|
1173
1175
|
INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
|
|
1176
|
+
/** Envelope payload.blockHash does not match the block's bid */
|
|
1177
|
+
INVALID_ENVELOPE_BLOCK_HASH = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BLOCK_HASH",
|
|
1174
1178
|
|
|
1175
1179
|
/** Block segment + envelopes failed chain-segment linearity / FULL-chain checks */
|
|
1176
1180
|
INVALID_CHAIN_SEGMENT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_CHAIN_SEGMENT",
|
|
@@ -1272,6 +1276,12 @@ export type DownloadByRangeErrorType =
|
|
|
1272
1276
|
expected: string;
|
|
1273
1277
|
actual: string;
|
|
1274
1278
|
}
|
|
1279
|
+
| {
|
|
1280
|
+
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH;
|
|
1281
|
+
slot: Slot;
|
|
1282
|
+
expected: string;
|
|
1283
|
+
actual: string;
|
|
1284
|
+
}
|
|
1275
1285
|
| {
|
|
1276
1286
|
code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT;
|
|
1277
1287
|
slot: Slot;
|
|
@@ -1295,47 +1305,76 @@ export function validateEnvelopesByRangeResponse(
|
|
|
1295
1305
|
payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
|
|
1296
1306
|
parentPayloadCommitments?: ParentPayloadCommitments
|
|
1297
1307
|
): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
|
|
1298
|
-
// Build a map of slot ->
|
|
1299
|
-
const
|
|
1308
|
+
// Build a map of slot -> block root and bid block hash for all blocks in the batch
|
|
1309
|
+
const batchBlocksBySlot = new Map<Slot, {blockRoot: Uint8Array; block: SignedBeaconBlock | null}>();
|
|
1300
1310
|
if (batchBlocks) {
|
|
1301
1311
|
for (const blockInput of batchBlocks) {
|
|
1302
|
-
|
|
1312
|
+
batchBlocksBySlot.set(blockInput.slot, {
|
|
1313
|
+
blockRoot: fromHex(blockInput.blockRootHex),
|
|
1314
|
+
block: blockInput.hasBlock() ? blockInput.getBlock() : null,
|
|
1315
|
+
});
|
|
1303
1316
|
}
|
|
1304
1317
|
}
|
|
1305
1318
|
for (const {block, blockRoot} of validatedBlocks) {
|
|
1306
|
-
|
|
1319
|
+
batchBlocksBySlot.set(block.message.slot, {blockRoot, block});
|
|
1307
1320
|
}
|
|
1308
1321
|
|
|
1309
1322
|
const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
|
|
1310
1323
|
|
|
1311
1324
|
for (const payloadEnvelope of payloadEnvelopes) {
|
|
1312
1325
|
const slot = payloadEnvelope.message.payload.slotNumber;
|
|
1313
|
-
const
|
|
1326
|
+
const batchBlock = batchBlocksBySlot.get(slot);
|
|
1314
1327
|
|
|
1315
|
-
if (
|
|
1328
|
+
if (batchBlock === undefined) {
|
|
1316
1329
|
// Keep the requested dangling-parent envelope only when its beaconBlockRoot matches
|
|
1317
1330
|
// exactly. All other unrelated envelopes are dropped.
|
|
1318
1331
|
if (
|
|
1319
1332
|
parentPayloadCommitments !== undefined &&
|
|
1320
1333
|
byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayloadCommitments.blockRoot)
|
|
1321
1334
|
) {
|
|
1335
|
+
assertEnvelopeBlockHash(payloadEnvelope, parentPayloadCommitments.blockHash);
|
|
1322
1336
|
payloadEnvelopeMap.set(slot, payloadEnvelope);
|
|
1323
1337
|
}
|
|
1324
1338
|
continue;
|
|
1325
1339
|
}
|
|
1326
1340
|
|
|
1327
1341
|
// Verify beaconBlockRoot matches the block's root
|
|
1328
|
-
if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot,
|
|
1342
|
+
if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlock.blockRoot)) {
|
|
1329
1343
|
throw new DownloadByRangeError({
|
|
1330
1344
|
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
|
|
1331
1345
|
slot,
|
|
1332
|
-
expected: toRootHex(
|
|
1346
|
+
expected: toRootHex(batchBlock.blockRoot),
|
|
1333
1347
|
actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
|
|
1334
1348
|
});
|
|
1335
1349
|
}
|
|
1336
1350
|
|
|
1351
|
+
if (batchBlock.block !== null && isGloasBeaconBlock(batchBlock.block.message)) {
|
|
1352
|
+
assertEnvelopeBlockHash(
|
|
1353
|
+
payloadEnvelope,
|
|
1354
|
+
batchBlock.block.message.body.signedExecutionPayloadBid.message.blockHash
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1337
1358
|
payloadEnvelopeMap.set(slot, payloadEnvelope);
|
|
1338
1359
|
}
|
|
1339
1360
|
|
|
1340
1361
|
return payloadEnvelopeMap;
|
|
1341
1362
|
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Rejected before it is cached. In-batch, `assertLinearChainSegment` fails on a wrong hash without evicting the
|
|
1366
|
+
* envelope. For the dangling parent, the import evicts the parent's entry and range sync cannot recreate it.
|
|
1367
|
+
*/
|
|
1368
|
+
function assertEnvelopeBlockHash(
|
|
1369
|
+
payloadEnvelope: gloas.SignedExecutionPayloadEnvelope,
|
|
1370
|
+
bidBlockHash: Uint8Array
|
|
1371
|
+
): void {
|
|
1372
|
+
if (!byteArrayEquals(payloadEnvelope.message.payload.blockHash, bidBlockHash)) {
|
|
1373
|
+
throw new DownloadByRangeError({
|
|
1374
|
+
code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH,
|
|
1375
|
+
slot: payloadEnvelope.message.payload.slotNumber,
|
|
1376
|
+
expected: toRootHex(bidBlockHash),
|
|
1377
|
+
actual: toRootHex(payloadEnvelope.message.payload.blockHash),
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
package/src/util/execution.ts
CHANGED
|
@@ -266,14 +266,15 @@ type SlotEnvelopeBytes = {slot: Slot; envelopeBytes: Uint8Array};
|
|
|
266
266
|
|
|
267
267
|
type RangeEntry = ArchivedEnvelope & {slot: Slot};
|
|
268
268
|
|
|
269
|
-
/** What
|
|
270
|
-
export type
|
|
269
|
+
/** What an envelope that cannot be rebuilt (EL-unavailable body or body root mismatch) means on a given serving path */
|
|
270
|
+
export type ReconstructMissPolicy = "throw" | "omit";
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
/**
|
|
273
|
+
* An archived envelope that could not be rebuilt. `"unavailable"`: the EL does not have the block or its
|
|
274
|
+
* block access list (BODY_UNAVAILABLE). `"mismatch"`: an EL body does not hash to its stored root, a local
|
|
275
|
+
* inconsistency (BODY_ROOT_MISMATCH). `error` carries the matching code for paths that surface it.
|
|
276
|
+
*/
|
|
277
|
+
export type RebuildMiss = {slot: Slot; reason: "unavailable" | "mismatch"; error: EnvelopeReconstructionError};
|
|
277
278
|
|
|
278
279
|
/**
|
|
279
280
|
* Stream finalized envelopes over [startSlot, endSlot) as serialized bytes, rebuilding header
|
|
@@ -405,7 +406,14 @@ async function reconstructEnvelopesBatch(
|
|
|
405
406
|
// A zero-length block access list cannot be valid, RLP encodes an empty list as 0xc0
|
|
406
407
|
if (body == null || body.withdrawals == null || body.blockAccessList == null || body.blockAccessList.length === 0) {
|
|
407
408
|
metrics?.payloadEnvelopeReconstruction.envelopes.inc({result: "unavailable"});
|
|
408
|
-
return {
|
|
409
|
+
return {
|
|
410
|
+
slot,
|
|
411
|
+
reason: "unavailable",
|
|
412
|
+
error: new EnvelopeReconstructionError(
|
|
413
|
+
{code: EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE, slot},
|
|
414
|
+
`execution client cannot serve the payload body or block access list for archived envelope slot=${slot}`
|
|
415
|
+
),
|
|
416
|
+
};
|
|
409
417
|
}
|
|
410
418
|
try {
|
|
411
419
|
const envelope = signedHeaderEnvelopeToFull(headerEnvelope, {
|