@lodestar/beacon-node 1.47.0-dev.7a8fd842fc → 1.47.0-dev.7aa8c9c93a
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 -8
- 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 +12 -1
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/utils.d.ts +2 -1
- package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/utils.js.map +1 -1
- package/lib/api/impl/events/index.d.ts +1 -1
- package/lib/api/impl/events/index.d.ts.map +1 -1
- package/lib/api/impl/events/index.js +12 -3
- package/lib/api/impl/events/index.js.map +1 -1
- package/lib/api/impl/validator/index.js +1 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/api/rest/base.d.ts.map +1 -1
- package/lib/api/rest/base.js +7 -0
- package/lib/api/rest/base.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
- package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +2 -7
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +11 -9
- 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 +14 -6
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +6 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +14 -5
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.js +5 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.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 +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/bls/index.d.ts +1 -1
- package/lib/chain/bls/index.d.ts.map +1 -1
- package/lib/chain/bls/interface.d.ts +5 -5
- package/lib/chain/bls/interface.d.ts.map +1 -1
- package/lib/chain/bls/multithread/index.d.ts +5 -11
- package/lib/chain/bls/multithread/index.d.ts.map +1 -1
- package/lib/chain/bls/multithread/index.js +116 -101
- package/lib/chain/bls/multithread/index.js.map +1 -1
- package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
- package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
- package/lib/chain/bls/multithread/jobItem.js +10 -65
- package/lib/chain/bls/multithread/jobItem.js.map +1 -1
- package/lib/chain/bls/multithread/types.d.ts +29 -8
- package/lib/chain/bls/multithread/types.d.ts.map +1 -1
- package/lib/chain/bls/multithread/types.js +12 -0
- package/lib/chain/bls/multithread/types.js.map +1 -1
- package/lib/chain/bls/multithread/utils.d.ts +4 -0
- package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
- package/lib/chain/bls/multithread/utils.js +10 -0
- package/lib/chain/bls/multithread/utils.js.map +1 -1
- package/lib/chain/bls/multithread/worker.js +49 -23
- package/lib/chain/bls/multithread/worker.js.map +1 -1
- package/lib/chain/bls/singleThread.d.ts +4 -10
- package/lib/chain/bls/singleThread.d.ts.map +1 -1
- package/lib/chain/bls/singleThread.js +8 -45
- package/lib/chain/bls/singleThread.js.map +1 -1
- package/lib/chain/bls/utils.d.ts +4 -4
- package/lib/chain/bls/utils.d.ts.map +1 -1
- package/lib/chain/bls/utils.js +18 -19
- package/lib/chain/bls/utils.js.map +1 -1
- package/lib/chain/builderCircuitBreaker.d.ts +6 -5
- package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
- package/lib/chain/builderCircuitBreaker.js +28 -15
- package/lib/chain/builderCircuitBreaker.js.map +1 -1
- package/lib/chain/chain.d.ts +2 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +12 -5
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +1 -0
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +6 -1
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +6 -0
- 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/interface.d.ts +2 -1
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
- package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
- package/lib/chain/opPools/attestationPool.d.ts +1 -1
- package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/attestationPool.js +1 -1
- package/lib/chain/opPools/attestationPool.js.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +3 -3
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
- package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
- package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
- package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
- package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
- package/lib/chain/opPools/utils.d.ts +1 -1
- package/lib/chain/opPools/utils.d.ts.map +1 -1
- package/lib/chain/opPools/utils.js +1 -1
- package/lib/chain/opPools/utils.js.map +1 -1
- package/lib/chain/options.d.ts +1 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +1 -1
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
- package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
- package/lib/chain/seenCache/seenAttesters.js +25 -4
- package/lib/chain/seenCache/seenAttesters.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +30 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +123 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/attestation.d.ts.map +1 -1
- package/lib/chain/validation/attestation.js +1 -4
- package/lib/chain/validation/attestation.js.map +1 -1
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +1 -1
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +1 -2
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
- package/lib/chain/validation/payloadAttestationMessage.js +5 -14
- package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
- package/lib/chain/validation/proposerPreferences.js +13 -4
- package/lib/chain/validation/proposerPreferences.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +1 -1
- package/lib/chain/validation/proposerSlashing.js.map +1 -1
- package/lib/execution/builder/http.d.ts +10 -7
- package/lib/execution/builder/http.d.ts.map +1 -1
- package/lib/execution/builder/http.js +11 -8
- package/lib/execution/builder/http.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +3 -3
- package/lib/metrics/metrics/beacon.d.ts.map +1 -1
- package/lib/metrics/metrics/beacon.js +9 -9
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +55 -20
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +103 -51
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +13 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +37 -0
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/libp2p/index.d.ts.map +1 -1
- package/lib/network/libp2p/index.js +10 -2
- package/lib/network/libp2p/index.js.map +1 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +22 -43
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +12 -3
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/reqresp/score.d.ts.map +1 -1
- package/lib/network/reqresp/score.js +5 -11
- package/lib/network/reqresp/score.js.map +1 -1
- package/lib/network/util.d.ts +0 -1
- package/lib/network/util.d.ts.map +1 -1
- package/lib/network/util.js +0 -4
- package/lib/network/util.js.map +1 -1
- package/lib/node/nodejs.d.ts +2 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +23 -5
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +61 -22
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +2 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +23 -10
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +9 -0
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +99 -21
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +16 -11
- 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 +3 -1
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/package.json +35 -35
- package/src/api/impl/beacon/blocks/index.ts +7 -8
- package/src/api/impl/beacon/pool/index.ts +12 -1
- package/src/api/impl/beacon/state/utils.ts +2 -1
- package/src/api/impl/events/index.ts +12 -4
- package/src/api/impl/validator/index.ts +1 -1
- package/src/api/rest/base.ts +7 -0
- package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
- package/src/chain/blocks/importBlock.ts +1 -6
- package/src/chain/blocks/importExecutionPayload.ts +12 -11
- package/src/chain/blocks/index.ts +18 -6
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
- package/src/chain/bls/index.ts +1 -1
- package/src/chain/bls/interface.ts +7 -3
- package/src/chain/bls/multithread/index.ts +148 -120
- package/src/chain/bls/multithread/jobItem.ts +12 -82
- package/src/chain/bls/multithread/types.ts +33 -8
- package/src/chain/bls/multithread/utils.ts +11 -0
- package/src/chain/bls/multithread/worker.ts +85 -27
- package/src/chain/bls/singleThread.ts +11 -55
- package/src/chain/bls/utils.ts +23 -24
- package/src/chain/builderCircuitBreaker.ts +31 -17
- package/src/chain/chain.ts +13 -6
- package/src/chain/errors/blockError.ts +8 -2
- package/src/chain/errors/proposerPreferences.ts +7 -0
- package/src/chain/interface.ts +2 -1
- package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
- package/src/chain/opPools/attestationPool.ts +1 -1
- package/src/chain/opPools/payloadAttestationPool.ts +3 -3
- package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
- package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
- package/src/chain/opPools/utils.ts +1 -1
- package/src/chain/options.ts +1 -1
- package/src/chain/prepareNextSlot.ts +1 -1
- package/src/chain/seenCache/seenAttesters.ts +31 -5
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +145 -14
- package/src/chain/validation/attestation.ts +1 -7
- package/src/chain/validation/attesterSlashing.ts +1 -8
- package/src/chain/validation/executionPayloadBid.ts +1 -2
- package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
- package/src/chain/validation/payloadAttestationMessage.ts +6 -17
- package/src/chain/validation/proposerPreferences.ts +15 -5
- package/src/chain/validation/proposerSlashing.ts +1 -1
- package/src/execution/builder/http.ts +11 -11
- package/src/metrics/metrics/beacon.ts +9 -9
- package/src/metrics/metrics/lodestar.ts +113 -54
- package/src/network/gossip/topic.ts +38 -0
- package/src/network/libp2p/index.ts +11 -2
- package/src/network/network.ts +27 -55
- package/src/network/processor/gossipHandlers.ts +14 -1
- package/src/network/reqresp/score.ts +5 -11
- package/src/network/util.ts +0 -5
- package/src/node/nodejs.ts +2 -1
- package/src/sync/range/batch.ts +83 -28
- package/src/sync/range/chain.ts +26 -11
- package/src/sync/types.ts +3 -1
- package/src/sync/unknownBlock.ts +115 -26
- package/src/sync/utils/downloadByRange.ts +21 -12
- package/src/sync/utils/downloadByRoot.ts +3 -1
- package/lib/chain/bls/maybeBatch.d.ts +0 -12
- package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
- package/lib/chain/bls/maybeBatch.js +0 -35
- package/lib/chain/bls/maybeBatch.js.map +0 -1
- package/src/chain/bls/maybeBatch.ts +0 -45
package/src/node/nodejs.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {setMaxListeners} from "node:events";
|
|
2
2
|
import {PrivateKey} from "@libp2p/interface";
|
|
3
3
|
import {Registry} from "prom-client";
|
|
4
|
+
import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
|
|
4
5
|
import {hasher} from "@chainsafe/persistent-merkle-tree";
|
|
5
6
|
import {BeaconApiMethods} from "@lodestar/api/beacon/server";
|
|
6
7
|
import {BeaconConfig} from "@lodestar/config";
|
|
7
8
|
import type {LoggerNode} from "@lodestar/logger/node";
|
|
8
9
|
import {ZERO_HASH_HEX} from "@lodestar/params";
|
|
9
|
-
import {IBeaconStateView,
|
|
10
|
+
import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
|
|
10
11
|
import {phase0} from "@lodestar/types";
|
|
11
12
|
import {sleep, toRootHex} from "@lodestar/utils";
|
|
12
13
|
import {ProcessShutdownCallback} from "@lodestar/validator";
|
package/src/sync/range/batch.ts
CHANGED
|
@@ -39,6 +39,7 @@ export enum BatchStatus {
|
|
|
39
39
|
AwaitingValidation = "AwaitingValidation",
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** Identity of one download attempt*/
|
|
42
43
|
export type Attempt = {
|
|
43
44
|
/** The peer that made the attempt */
|
|
44
45
|
peers: PeerIdStr[];
|
|
@@ -46,6 +47,16 @@ export type Attempt = {
|
|
|
46
47
|
hash: RootHex;
|
|
47
48
|
};
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A failed attempt that has been classified for blame.
|
|
52
|
+
*/
|
|
53
|
+
export type FailedAttempt = Attempt & {
|
|
54
|
+
/**
|
|
55
|
+
* True when the failure is evidence the peers served bad data, so they may be downscored..
|
|
56
|
+
*/
|
|
57
|
+
peerAttributable: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
49
60
|
type TrackedRequest = {
|
|
50
61
|
/** only happen for the 1st batch in checkpoint sync */
|
|
51
62
|
parentPayload: boolean;
|
|
@@ -108,10 +119,12 @@ export type BatchMetadata = {
|
|
|
108
119
|
// Retry counters
|
|
109
120
|
downloadAttempts: number;
|
|
110
121
|
processingAttempts: number;
|
|
122
|
+
executionErrorAttempts: number;
|
|
111
123
|
|
|
112
124
|
// Cumulative peer attribution for failed attempts (only present when non-empty)
|
|
113
125
|
failedDownloadPeers?: string;
|
|
114
126
|
failedProcessingPeers?: string;
|
|
127
|
+
executionErrorPeers?: string;
|
|
115
128
|
};
|
|
116
129
|
|
|
117
130
|
function formatRangeReq(req: {startSlot: Slot; count: number}): string {
|
|
@@ -152,10 +165,10 @@ export class Batch {
|
|
|
152
165
|
state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
153
166
|
/** Peers that provided good data, with column coverage for by_range requests */
|
|
154
167
|
private readonly successfulDownloads = new Map<PeerIdStr, TrackedRequest>();
|
|
155
|
-
/** The
|
|
156
|
-
readonly failedProcessingAttempts:
|
|
157
|
-
/** The
|
|
158
|
-
readonly executionErrorAttempts:
|
|
168
|
+
/** The attempts that have been made and failed to send us this batch. */
|
|
169
|
+
readonly failedProcessingAttempts: FailedAttempt[] = [];
|
|
170
|
+
/** The attempts that have been made and failed because of execution malfunction. */
|
|
171
|
+
readonly executionErrorAttempts: FailedAttempt[] = [];
|
|
159
172
|
/** The number of download retries this batch has undergone due to a failed request. */
|
|
160
173
|
private readonly failedDownloadAttempts: PeerIdStr[] = [];
|
|
161
174
|
private readonly config: ChainForkConfig;
|
|
@@ -419,10 +432,18 @@ export class Batch {
|
|
|
419
432
|
}
|
|
420
433
|
|
|
421
434
|
/**
|
|
422
|
-
* Gives a list of peers from which this batch has had a failed download or
|
|
435
|
+
* Gives a list of peers from which this batch has had a failed download attempt, or a
|
|
436
|
+
* peer-attributable failed processing/execution attempt.
|
|
423
437
|
*/
|
|
424
438
|
getFailedPeers(): PeerIdStr[] {
|
|
425
|
-
return [...this.failedDownloadAttempts, ...this.
|
|
439
|
+
return [...this.failedDownloadAttempts, ...this.getPeerAttributableAttempts().flatMap((a) => a.peers)];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Attempts whose failure is peer-attributable, across both the processing and execution error
|
|
444
|
+
*/
|
|
445
|
+
getPeerAttributableAttempts(): FailedAttempt[] {
|
|
446
|
+
return [...this.failedProcessingAttempts, ...this.executionErrorAttempts].filter((a) => a.peerAttributable);
|
|
426
447
|
}
|
|
427
448
|
|
|
428
449
|
/**
|
|
@@ -457,6 +478,7 @@ export class Batch {
|
|
|
457
478
|
getMetadata(): BatchMetadata {
|
|
458
479
|
const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
|
|
459
480
|
const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
|
|
481
|
+
const executionErrorPeerList = this.executionErrorAttempts.flatMap((a) => a.peers);
|
|
460
482
|
return {
|
|
461
483
|
startEpoch: this.startEpoch,
|
|
462
484
|
startSlot: this.startSlot,
|
|
@@ -468,12 +490,16 @@ export class Batch {
|
|
|
468
490
|
...(envelopesRequest && {envelopesReq: formatRangeReq(envelopesRequest)}),
|
|
469
491
|
downloadAttempts: this.failedDownloadAttempts.length,
|
|
470
492
|
processingAttempts: this.failedProcessingAttempts.length,
|
|
493
|
+
executionErrorAttempts: this.executionErrorAttempts.length,
|
|
471
494
|
...(this.failedDownloadAttempts.length > 0 && {
|
|
472
495
|
failedDownloadPeers: this.failedDownloadAttempts.join(","),
|
|
473
496
|
}),
|
|
474
497
|
...(failedProcessingPeerList.length > 0 && {
|
|
475
498
|
failedProcessingPeers: failedProcessingPeerList.join(","),
|
|
476
499
|
}),
|
|
500
|
+
...(executionErrorPeerList.length > 0 && {
|
|
501
|
+
executionErrorPeers: executionErrorPeerList.join(","),
|
|
502
|
+
}),
|
|
477
503
|
};
|
|
478
504
|
}
|
|
479
505
|
|
|
@@ -703,11 +729,7 @@ export class Batch {
|
|
|
703
729
|
throw new BatchError(this.wrongStatusErrorType(BatchStatus.Processing));
|
|
704
730
|
}
|
|
705
731
|
|
|
706
|
-
|
|
707
|
-
this.onExecutionEngineError(this.state.attempt);
|
|
708
|
-
} else {
|
|
709
|
-
this.onProcessingError(this.state.attempt);
|
|
710
|
-
}
|
|
732
|
+
this.routeProcessingFailure(err, this.state.attempt);
|
|
711
733
|
}
|
|
712
734
|
|
|
713
735
|
/**
|
|
@@ -718,10 +740,17 @@ export class Batch {
|
|
|
718
740
|
throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingValidation));
|
|
719
741
|
}
|
|
720
742
|
|
|
721
|
-
|
|
722
|
-
|
|
743
|
+
this.routeProcessingFailure(err, this.state.attempt);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private routeProcessingFailure(err: Error, attempt: Attempt): void {
|
|
747
|
+
const code = err instanceof BlockError || err instanceof PayloadError ? err.type.code : null;
|
|
748
|
+
const failedAttempt: FailedAttempt = {...attempt, peerAttributable: isPeerAttributableFailure(code)};
|
|
749
|
+
|
|
750
|
+
if (isExecutionEngineFailure(code)) {
|
|
751
|
+
this.onExecutionEngineError(failedAttempt);
|
|
723
752
|
} else {
|
|
724
|
-
this.onProcessingError(
|
|
753
|
+
this.onProcessingError(failedAttempt);
|
|
725
754
|
}
|
|
726
755
|
}
|
|
727
756
|
|
|
@@ -735,7 +764,7 @@ export class Batch {
|
|
|
735
764
|
return this.state.attempt;
|
|
736
765
|
}
|
|
737
766
|
|
|
738
|
-
private onExecutionEngineError(attempt:
|
|
767
|
+
private onExecutionEngineError(attempt: FailedAttempt): void {
|
|
739
768
|
this.executionErrorAttempts.push(attempt);
|
|
740
769
|
if (this.executionErrorAttempts.length > MAX_BATCH_PROCESSING_ATTEMPTS) {
|
|
741
770
|
throw new BatchError(this.errorType({code: BatchErrorCode.MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS}));
|
|
@@ -746,7 +775,7 @@ export class Batch {
|
|
|
746
775
|
this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
|
|
747
776
|
}
|
|
748
777
|
|
|
749
|
-
private onProcessingError(attempt:
|
|
778
|
+
private onProcessingError(attempt: FailedAttempt): void {
|
|
750
779
|
this.failedProcessingAttempts.push(attempt);
|
|
751
780
|
if (this.failedProcessingAttempts.length > MAX_BATCH_PROCESSING_ATTEMPTS) {
|
|
752
781
|
throw new BatchError(this.errorType({code: BatchErrorCode.MAX_PROCESSING_ATTEMPTS}));
|
|
@@ -789,18 +818,44 @@ type BatchErrorMetadata = {
|
|
|
789
818
|
|
|
790
819
|
export class BatchError extends LodestarError<BatchErrorType & BatchErrorMetadata> {}
|
|
791
820
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
if (err.type.code === BlockErrorCode.EXECUTION_ENGINE_ERROR) {
|
|
798
|
-
return true;
|
|
799
|
-
}
|
|
800
|
-
|
|
821
|
+
/**
|
|
822
|
+
* Whether a processing failure is an execution-engine failure. Pre-gloas, all EL errors stay in
|
|
823
|
+
* BlockErrorCode, post-gloas, they stay in PayloadErrorCode.
|
|
824
|
+
*/
|
|
825
|
+
function isExecutionEngineFailure(code: BlockErrorCode | PayloadErrorCode | null): boolean {
|
|
801
826
|
return (
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
827
|
+
code === BlockErrorCode.EXECUTION_ENGINE_INVALID ||
|
|
828
|
+
code === BlockErrorCode.EXECUTION_ENGINE_ERROR ||
|
|
829
|
+
code === PayloadErrorCode.EXECUTION_ENGINE_INVALID ||
|
|
830
|
+
code === PayloadErrorCode.EXECUTION_ENGINE_ERROR
|
|
805
831
|
);
|
|
806
832
|
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Based on an error code, return if peer is attributable for FailedAttempt.
|
|
836
|
+
*/
|
|
837
|
+
function isPeerAttributableFailure(code: BlockErrorCode | PayloadErrorCode | null): boolean {
|
|
838
|
+
switch (code) {
|
|
839
|
+
// EL returned a definitive INVALID verdict on the payload
|
|
840
|
+
case BlockErrorCode.EXECUTION_ENGINE_INVALID:
|
|
841
|
+
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
842
|
+
// a block or envelope signature is invalid
|
|
843
|
+
case BlockErrorCode.INVALID_SIGNATURE:
|
|
844
|
+
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
845
|
+
// the block's state transition produced an invalid state root, or failed per_slot/per_block processing
|
|
846
|
+
case BlockErrorCode.INVALID_STATE_ROOT:
|
|
847
|
+
case BlockErrorCode.PER_BLOCK_PROCESSING_ERROR:
|
|
848
|
+
// the segment this peer served is internally inconsistent (in-segment link breaks, see PR #9684)
|
|
849
|
+
case BlockErrorCode.NON_LINEAR_SLOTS:
|
|
850
|
+
case BlockErrorCode.NON_LINEAR_PARENT_ROOTS:
|
|
851
|
+
case BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS:
|
|
852
|
+
// the envelope references a mismatched block root, or fails field verification
|
|
853
|
+
case BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH:
|
|
854
|
+
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
855
|
+
// the peer served a block we have explicitly blacklisted
|
|
856
|
+
case BlockErrorCode.BLACKLISTED_BLOCK:
|
|
857
|
+
return true;
|
|
858
|
+
default:
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
}
|
package/src/sync/range/chain.ts
CHANGED
|
@@ -358,9 +358,10 @@ export class SyncChain {
|
|
|
358
358
|
// If a batch exceeds it's retry limit, maybe downscore peers.
|
|
359
359
|
// shouldDownscoreOnBatchError() functions enforces that all BatchErrorCode values are covered
|
|
360
360
|
if (e instanceof BatchError) {
|
|
361
|
-
const shouldReportPeer = shouldReportPeerOnBatchError(e.type.code);
|
|
361
|
+
const shouldReportPeer = shouldReportPeerOnBatchError(e.type.code, this.batches.get(e.type.startEpoch));
|
|
362
362
|
if (shouldReportPeer) {
|
|
363
|
-
|
|
363
|
+
// Only the peers actually at fault are reported, never the whole peerset.
|
|
364
|
+
for (const peer of shouldReportPeer.peers) {
|
|
364
365
|
this.reportPeer(peer, shouldReportPeer.action, shouldReportPeer.reason);
|
|
365
366
|
}
|
|
366
367
|
}
|
|
@@ -768,8 +769,11 @@ export class SyncChain {
|
|
|
768
769
|
|
|
769
770
|
// The last batch attempt is right, all others are wrong. Penalize other peers
|
|
770
771
|
const attemptOk = batch.validationSuccess();
|
|
771
|
-
for (const attempt of batch.
|
|
772
|
+
for (const attempt of batch.getPeerAttributableAttempts()) {
|
|
772
773
|
if (attempt.hash !== attemptOk.hash) {
|
|
774
|
+
// attempt.peers contains block/column/envelope peers.
|
|
775
|
+
// And if there is one bad envelope we will downscore block and column peers to
|
|
776
|
+
// TODO: resolve this
|
|
773
777
|
for (const badAttemptPeer of attempt.peers) {
|
|
774
778
|
if (attemptOk.peers.find((goodPeer) => goodPeer === badAttemptPeer)) {
|
|
775
779
|
// The same peer corrected its previous attempt
|
|
@@ -830,21 +834,32 @@ export class SyncChain {
|
|
|
830
834
|
* If peer should not be downscored, returns null.
|
|
831
835
|
*/
|
|
832
836
|
export function shouldReportPeerOnBatchError(
|
|
833
|
-
code: BatchErrorCode
|
|
834
|
-
|
|
837
|
+
code: BatchErrorCode,
|
|
838
|
+
batch: Batch | undefined
|
|
839
|
+
): {action: PeerAction.LowToleranceError; reason: string; peers: PeerIdStr[]} | null {
|
|
835
840
|
switch (code) {
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
// We drop the chain and report all peers.
|
|
839
|
-
// There are some edge cases with forks that could cause this situation, but it's unlikely.
|
|
841
|
+
// A batch could not be processed after max retry limit. Report
|
|
842
|
+
// only the peers that served a peer-attributable failed attempt.
|
|
840
843
|
case BatchErrorCode.MAX_PROCESSING_ATTEMPTS:
|
|
841
|
-
|
|
844
|
+
case BatchErrorCode.MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS: {
|
|
845
|
+
// penalize both the processing-error and execution-error peers
|
|
846
|
+
const attributablePeers = [
|
|
847
|
+
...new Set(batch?.getPeerAttributableAttempts().flatMap((attempt) => attempt.peers) ?? []),
|
|
848
|
+
];
|
|
849
|
+
if (attributablePeers.length === 0) {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
const reason =
|
|
853
|
+
code === BatchErrorCode.MAX_PROCESSING_ATTEMPTS
|
|
854
|
+
? "SyncChainMaxProcessingAttempts"
|
|
855
|
+
: "SyncChainMaxExecutionEngineErrorAttempts";
|
|
856
|
+
return {action: PeerAction.LowToleranceError, reason, peers: attributablePeers};
|
|
857
|
+
}
|
|
842
858
|
|
|
843
859
|
// TODO: Should peers be reported for MAX_DOWNLOAD_ATTEMPTS?
|
|
844
860
|
case BatchErrorCode.MAX_DOWNLOAD_ATTEMPTS:
|
|
845
861
|
case BatchErrorCode.INVALID_COUNT:
|
|
846
862
|
case BatchErrorCode.WRONG_STATUS:
|
|
847
|
-
case BatchErrorCode.MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS:
|
|
848
863
|
return null;
|
|
849
864
|
}
|
|
850
865
|
}
|
package/src/sync/types.ts
CHANGED
|
@@ -77,7 +77,9 @@ export type PendingPayloadInput = {
|
|
|
77
77
|
export type PendingPayloadRootHex = {
|
|
78
78
|
status: PendingPayloadInputStatus.pending | PendingPayloadInputStatus.fetching;
|
|
79
79
|
rootHex: RootHex;
|
|
80
|
-
//
|
|
80
|
+
// Trusted slot only (fork choice / validated data), may be missing until resolved. NOT the gossip
|
|
81
|
+
// message slot from ChainEvent.unknownEnvelopeBlockRoot, which is untrusted and not necessarily the
|
|
82
|
+
// payload/block slot. See BlockInputSync.resolvePayloadSlot.
|
|
81
83
|
slot?: Slot;
|
|
82
84
|
timeAddedSec: number;
|
|
83
85
|
timeSyncedSec?: number;
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
+
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
3
4
|
import {ForkSeq, isForkPostGloas} from "@lodestar/params";
|
|
4
5
|
import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
|
|
5
6
|
import {computeTimeAtSlot} from "@lodestar/state-transition";
|
|
@@ -154,6 +155,7 @@ export class BlockInputSync {
|
|
|
154
155
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
155
156
|
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
156
157
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
158
|
+
this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
|
|
157
159
|
this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
|
|
158
160
|
this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
159
161
|
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
@@ -170,6 +172,7 @@ export class BlockInputSync {
|
|
|
170
172
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
171
173
|
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
172
174
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
175
|
+
this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.pruneFinalized);
|
|
173
176
|
this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
|
|
174
177
|
this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
175
178
|
this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
@@ -215,7 +218,7 @@ export class BlockInputSync {
|
|
|
215
218
|
|
|
216
219
|
private onUnknownEnvelopeBlockRoot = (data: ChainEventData[ChainEvent.unknownEnvelopeBlockRoot]): void => {
|
|
217
220
|
try {
|
|
218
|
-
this.addByPayloadRootHex(data.rootHex, data.peer, data.
|
|
221
|
+
this.addByPayloadRootHex(data.rootHex, data.peer, this.resolvePayloadSlot(data.rootHex));
|
|
219
222
|
this.triggerUnknownBlockSearch();
|
|
220
223
|
this.metrics?.blockInputSync.requests.inc({type: PendingBlockType.UNKNOWN_PAYLOAD_BLOCK_ROOT});
|
|
221
224
|
this.metrics?.blockInputSync.payloadSource.inc({source: data.source});
|
|
@@ -258,7 +261,7 @@ export class BlockInputSync {
|
|
|
258
261
|
}
|
|
259
262
|
|
|
260
263
|
if (missingDependency.kind === "parentPayload") {
|
|
261
|
-
this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
|
|
264
|
+
this.addByPayloadRootHex(missingDependency.rootHex, data.peer, missingDependency.slot);
|
|
262
265
|
} else if (missingDependency.kind === "parentBlock") {
|
|
263
266
|
this.addByRootHex(missingDependency.rootHex, data.peer);
|
|
264
267
|
}
|
|
@@ -284,6 +287,61 @@ export class BlockInputSync {
|
|
|
284
287
|
this.triggerUnknownBlockSearch();
|
|
285
288
|
};
|
|
286
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Drop pending payloads and blocks that fell below the finalized slot.
|
|
292
|
+
* See https://github.com/ChainSafe/lodestar/issues/9800
|
|
293
|
+
*/
|
|
294
|
+
private pruneFinalized = (checkpoint: CheckpointWithHex): void => {
|
|
295
|
+
// Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
|
|
296
|
+
// because the payload at the finalized block is not finalized
|
|
297
|
+
const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
|
|
298
|
+
|
|
299
|
+
let deletedPayloads = 0;
|
|
300
|
+
let unresolvedSlotPayloads = 0;
|
|
301
|
+
for (const [rootHex, payload] of this.pendingPayloads) {
|
|
302
|
+
// Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
|
|
303
|
+
if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
|
|
304
|
+
payload.slot = this.resolvePayloadSlot(rootHex);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const slot = getPayloadSyncCacheItemSlot(payload);
|
|
308
|
+
if (typeof slot !== "number") {
|
|
309
|
+
unresolvedSlotPayloads++;
|
|
310
|
+
this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
|
|
311
|
+
root: rootHex,
|
|
312
|
+
finalizedSlot,
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (slot < finalizedSlot) {
|
|
318
|
+
this.pendingPayloads.delete(rootHex);
|
|
319
|
+
deletedPayloads++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let deletedBlocks = 0;
|
|
324
|
+
for (const [rootHex, block] of this.pendingBlocks) {
|
|
325
|
+
const slot = getBlockInputSyncCacheItemSlot(block);
|
|
326
|
+
if (typeof slot === "number" && slot < finalizedSlot) {
|
|
327
|
+
this.pendingBlocks.delete(rootHex);
|
|
328
|
+
deletedBlocks++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (deletedPayloads > 0 || deletedBlocks > 0) {
|
|
333
|
+
this.metrics?.blockInputSync.removedBlocks.inc(deletedPayloads + deletedBlocks);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
|
|
337
|
+
finalizedSlot,
|
|
338
|
+
finalizedRoot: checkpoint.rootHex,
|
|
339
|
+
deletedPayloads,
|
|
340
|
+
deletedBlocks,
|
|
341
|
+
unresolvedSlotPayloads,
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
287
345
|
private addByRootHex = (rootHex: RootHex, peerIdStr?: PeerIdStr): boolean => {
|
|
288
346
|
let pendingBlock = this.pendingBlocks.get(rootHex);
|
|
289
347
|
let added = false;
|
|
@@ -382,6 +440,29 @@ export class BlockInputSync {
|
|
|
382
440
|
return added;
|
|
383
441
|
};
|
|
384
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Resolve the block slot for a payload root.
|
|
445
|
+
*/
|
|
446
|
+
private resolvePayloadSlot(rootHex: RootHex): Slot | undefined {
|
|
447
|
+
// a pending block of the same root
|
|
448
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
449
|
+
if (pendingBlock) {
|
|
450
|
+
const pendingBlockSlot = getBlockInputSyncCacheItemSlot(pendingBlock);
|
|
451
|
+
if (typeof pendingBlockSlot === "number") {
|
|
452
|
+
return pendingBlockSlot;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// the block is in fork choice
|
|
457
|
+
const forkChoiceSlot = this.chain.forkChoice.getBlockHexDefaultStatus(rootHex)?.slot;
|
|
458
|
+
if (forkChoiceSlot !== undefined) {
|
|
459
|
+
return forkChoiceSlot;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// a seen payload envelope input
|
|
463
|
+
return this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.slot;
|
|
464
|
+
}
|
|
465
|
+
|
|
385
466
|
private addByPayloadInput = (
|
|
386
467
|
payloadInput: PayloadEnvelopeInput,
|
|
387
468
|
peerIdStr?: PeerIdStr,
|
|
@@ -436,10 +517,12 @@ export class BlockInputSync {
|
|
|
436
517
|
blockInput: IBlockInput
|
|
437
518
|
):
|
|
438
519
|
| {kind: "ready"}
|
|
439
|
-
| {kind: "block" | "parentBlock"
|
|
520
|
+
| {kind: "block" | "parentBlock"; rootHex: RootHex}
|
|
521
|
+
| {kind: "parentPayload"; rootHex: RootHex; slot: Slot}
|
|
440
522
|
| {kind: "invalidParentPayload"; parentRootHex: RootHex; parentBlockHashHex: RootHex} {
|
|
441
523
|
const parentRootHex = blockInput.parentRootHex;
|
|
442
|
-
|
|
524
|
+
const parentBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRootHex);
|
|
525
|
+
if (!parentBlock) {
|
|
443
526
|
return {kind: "parentBlock", rootHex: parentRootHex};
|
|
444
527
|
}
|
|
445
528
|
|
|
@@ -461,16 +544,8 @@ export class BlockInputSync {
|
|
|
461
544
|
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
462
545
|
}
|
|
463
546
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
|
|
467
|
-
return {kind: "parentPayload", rootHex: parentRootHex};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return {kind: "parentPayload", rootHex: parentRootHex};
|
|
547
|
+
// Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
|
|
548
|
+
return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
|
|
474
549
|
}
|
|
475
550
|
|
|
476
551
|
private advancePendingBlock(pendingBlock: PendingBlockInput): AdvancePendingBlockResult {
|
|
@@ -493,9 +568,9 @@ export class BlockInputSync {
|
|
|
493
568
|
}
|
|
494
569
|
|
|
495
570
|
case "parentPayload": {
|
|
496
|
-
let added = this.addByPayloadRootHex(missingDependency.rootHex);
|
|
571
|
+
let added = this.addByPayloadRootHex(missingDependency.rootHex, undefined, missingDependency.slot);
|
|
497
572
|
for (const peerIdStr of pendingBlock.peerIdStrings) {
|
|
498
|
-
added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
|
|
573
|
+
added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr, missingDependency.slot) || added;
|
|
499
574
|
}
|
|
500
575
|
return added ? "queued_parent_payload" : "blocked";
|
|
501
576
|
}
|
|
@@ -811,9 +886,9 @@ export class BlockInputSync {
|
|
|
811
886
|
this.pendingBlocks.delete(blockRootHex);
|
|
812
887
|
|
|
813
888
|
if (isForkPostGloas(fork)) {
|
|
814
|
-
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.
|
|
889
|
+
const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
|
|
815
890
|
if (!payloadInput) {
|
|
816
|
-
this.logger.
|
|
891
|
+
this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
|
|
817
892
|
} else {
|
|
818
893
|
// Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
|
|
819
894
|
// The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
|
|
@@ -886,12 +961,12 @@ export class BlockInputSync {
|
|
|
886
961
|
*/
|
|
887
962
|
private async reconcilePayloadEnvelope(pendingPayload: PendingPayloadEnvelope): Promise<void> {
|
|
888
963
|
const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
|
|
964
|
+
|
|
889
965
|
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
890
966
|
this.pendingPayloads.delete(rootHex);
|
|
891
967
|
return;
|
|
892
968
|
}
|
|
893
969
|
|
|
894
|
-
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
895
970
|
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
896
971
|
// Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
|
|
897
972
|
// non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
|
|
@@ -907,8 +982,9 @@ export class BlockInputSync {
|
|
|
907
982
|
return;
|
|
908
983
|
}
|
|
909
984
|
|
|
985
|
+
const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
910
986
|
if (!payloadInput) {
|
|
911
|
-
this.logger.debug("
|
|
987
|
+
this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
|
|
912
988
|
root: rootHex,
|
|
913
989
|
});
|
|
914
990
|
return;
|
|
@@ -928,6 +1004,7 @@ export class BlockInputSync {
|
|
|
928
1004
|
const pendingPayloadByRoot: PendingPayloadRootHex = {
|
|
929
1005
|
status: PendingPayloadInputStatus.pending,
|
|
930
1006
|
rootHex,
|
|
1007
|
+
slot: pendingPayload.envelope.message.payload.slotNumber,
|
|
931
1008
|
timeAddedSec: pendingPayload.timeAddedSec,
|
|
932
1009
|
peerIdStrings: new Set(pendingPayload.peerIdStrings),
|
|
933
1010
|
};
|
|
@@ -967,6 +1044,10 @@ export class BlockInputSync {
|
|
|
967
1044
|
return;
|
|
968
1045
|
}
|
|
969
1046
|
|
|
1047
|
+
if (!isPendingPayloadInput(payload) && payload.slot === undefined) {
|
|
1048
|
+
payload.slot = this.resolvePayloadSlot(rootHex);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
970
1051
|
const payloadSlot = getPayloadSyncCacheItemSlot(payload);
|
|
971
1052
|
const logCtx = {
|
|
972
1053
|
slot: payloadSlot,
|
|
@@ -982,7 +1063,13 @@ export class BlockInputSync {
|
|
|
982
1063
|
const res = await wrapError(this.fetchPayloadInput(payload));
|
|
983
1064
|
if (!res.err) {
|
|
984
1065
|
const pendingPayload = res.result;
|
|
985
|
-
|
|
1066
|
+
const resultRootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
|
|
1067
|
+
// finalization may have deleted this entry while we were awaiting the network, do not resurrect it via the set below
|
|
1068
|
+
if (!this.pendingPayloads.has(resultRootHex)) {
|
|
1069
|
+
this.logger.verbose("Dropping downloaded payload, entry pruned during fetch", logCtx);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
this.pendingPayloads.set(resultRootHex, pendingPayload);
|
|
986
1073
|
|
|
987
1074
|
if (isPendingPayloadEnvelope(pendingPayload)) {
|
|
988
1075
|
await this.reconcilePayloadEnvelope(pendingPayload);
|
|
@@ -1093,7 +1180,7 @@ export class BlockInputSync {
|
|
|
1093
1180
|
let slot = getPayloadSyncCacheItemSlot(cacheItem);
|
|
1094
1181
|
let payloadInput = isPendingPayloadInput(cacheItem)
|
|
1095
1182
|
? cacheItem.payloadInput
|
|
1096
|
-
: this.chain.seenPayloadEnvelopeInputCache.
|
|
1183
|
+
: await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1097
1184
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1098
1185
|
|
|
1099
1186
|
let i = 0;
|
|
@@ -1110,9 +1197,11 @@ export class BlockInputSync {
|
|
|
1110
1197
|
);
|
|
1111
1198
|
}
|
|
1112
1199
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1200
|
+
const reason =
|
|
1201
|
+
pendingColumns.size > 0
|
|
1202
|
+
? `cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`
|
|
1203
|
+
: "no peer available to download pending payload";
|
|
1204
|
+
throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: ${reason}`);
|
|
1116
1205
|
}
|
|
1117
1206
|
|
|
1118
1207
|
const {peerId, client: peerClient} = peerMeta;
|
|
@@ -1124,7 +1213,7 @@ export class BlockInputSync {
|
|
|
1124
1213
|
slot = envelope.message.payload.slotNumber;
|
|
1125
1214
|
}
|
|
1126
1215
|
|
|
1127
|
-
payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.
|
|
1216
|
+
payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1128
1217
|
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
1129
1218
|
// Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
|
|
1130
1219
|
// envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
|
|
@@ -136,6 +136,8 @@ export function cacheByRangeResponses({
|
|
|
136
136
|
const source = BlockInputSource.byRange;
|
|
137
137
|
const updatedBatchBlocks = new Map<Slot, IBlockInput>(batchBlocks.map((block) => [block.slot, block]));
|
|
138
138
|
|
|
139
|
+
const payloadEnvelopes = new Map<Slot, PayloadEnvelopeInput>(existingPayloadEnvelopes);
|
|
140
|
+
|
|
139
141
|
const blocks = responses.validatedBlocks ?? [];
|
|
140
142
|
for (let i = 0; i < blocks.length; i++) {
|
|
141
143
|
const {block, blockRoot} = blocks[i];
|
|
@@ -172,15 +174,17 @@ export function cacheByRangeResponses({
|
|
|
172
174
|
// later step) can throw and abort the batch — otherwise a gloas block would sit in
|
|
173
175
|
// seenBlockInputCache but unseeded here, and payload-by-root sync would later throw "Missing
|
|
174
176
|
// PayloadEnvelopeInput for known block" (see issue #9306). add() is idempotent.
|
|
175
|
-
if (isForkPostGloas(blockInput.forkName)) {
|
|
176
|
-
seenPayloadEnvelopeInputCache.add({
|
|
177
|
+
if (isForkPostGloas(blockInput.forkName) && !payloadEnvelopes.has(blockInput.slot)) {
|
|
178
|
+
const payloadInput = seenPayloadEnvelopeInputCache.add({
|
|
177
179
|
blockRootHex: blockInput.blockRootHex,
|
|
178
180
|
block: blockInput.getBlock() as SignedBeaconBlock<ForkPostGloas>,
|
|
179
181
|
forkName: blockInput.forkName,
|
|
180
182
|
sampledColumns: custodyConfig.sampledColumns,
|
|
181
183
|
custodyColumns: custodyConfig.custodyColumns,
|
|
182
|
-
|
|
184
|
+
seenTimestampSec,
|
|
185
|
+
source: PayloadEnvelopeInputSource.byRange,
|
|
183
186
|
});
|
|
187
|
+
payloadEnvelopes.set(blockInput.slot, payloadInput);
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
|
|
@@ -222,17 +226,22 @@ export function cacheByRangeResponses({
|
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
|
|
225
|
-
let payloadEnvelopes: Map<Slot, PayloadEnvelopeInput> | null =
|
|
226
|
-
existingPayloadEnvelopes !== null ? new Map(existingPayloadEnvelopes) : null;
|
|
227
229
|
if (downloadedPayloadEnvelopes !== null) {
|
|
228
|
-
payloadEnvelopes ??= new Map();
|
|
229
230
|
for (const [slot, envelope] of downloadedPayloadEnvelopes) {
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
// the only PayloadEnvelopeInput miss is the dangling parent, which we can get from the seen cache
|
|
232
|
+
let payloadInput = payloadEnvelopes.get(slot);
|
|
232
233
|
if (payloadInput === undefined) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
234
|
+
if (updatedBatchBlocks.has(slot)) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Missing PayloadEnvelopeInput for in-batch slot ${slot} root ${toRootHex(envelope.message.beaconBlockRoot)}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
payloadInput = seenPayloadEnvelopeInputCache.get(toRootHex(envelope.message.beaconBlockRoot));
|
|
240
|
+
}
|
|
241
|
+
if (payloadInput === undefined) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Missing PayloadEnvelopeInput for slot ${slot} root ${toRootHex(envelope.message.beaconBlockRoot)}`
|
|
244
|
+
);
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
if (!payloadInput.hasPayloadEnvelope()) {
|
|
@@ -262,7 +271,7 @@ export function cacheByRangeResponses({
|
|
|
262
271
|
// Gloas columns are attached to the matching PayloadEnvelopeInput, NOT to IBlockInput.
|
|
263
272
|
// Gloas DataColumnSidecar has `slot` directly (no signedBlockHeader).
|
|
264
273
|
const dataSlot = firstColumn.slot;
|
|
265
|
-
const payloadInput = payloadEnvelopes
|
|
274
|
+
const payloadInput = payloadEnvelopes.get(dataSlot);
|
|
266
275
|
if (!payloadInput) {
|
|
267
276
|
// Should not happen: we built payloadInputs for all gloas blocks above
|
|
268
277
|
continue;
|
|
@@ -13,6 +13,7 @@ import {BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lod
|
|
|
13
13
|
import {LodestarError, byteArrayEquals, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
|
|
14
14
|
import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
|
|
15
15
|
import {BlockInputSource, IBlockInput} from "../../chain/blocks/blockInput/types.js";
|
|
16
|
+
import {PayloadEnvelopeInputSource} from "../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
16
17
|
import {ChainEventEmitter} from "../../chain/emitter.js";
|
|
17
18
|
import {IBeaconChain} from "../../chain/interface.js";
|
|
18
19
|
import {validateBlockBlobSidecars} from "../../chain/validation/blobSidecar.js";
|
|
@@ -122,7 +123,8 @@ export async function downloadByRoot({
|
|
|
122
123
|
forkName: blockInput.forkName,
|
|
123
124
|
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
124
125
|
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
125
|
-
|
|
126
|
+
seenTimestampSec: Date.now() / 1000,
|
|
127
|
+
source: PayloadEnvelopeInputSource.byRoot,
|
|
126
128
|
});
|
|
127
129
|
}
|
|
128
130
|
|