@lodestar/beacon-node 1.43.0-dev.6b7eebbf6d → 1.43.0-dev.6f485b1b61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +16 -5
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +45 -2
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +0 -1
- package/lib/api/impl/debug/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.js +1 -1
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +68 -2
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +3 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +4 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +16 -28
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts +23 -6
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +57 -24
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/index.d.ts +5 -3
- package/lib/chain/blocks/index.d.ts.map +1 -1
- package/lib/chain/blocks/index.js +58 -25
- package/lib/chain/blocks/index.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts +12 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js +28 -2
- package/lib/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +17 -0
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeProcessor.js +2 -2
- package/lib/chain/blocks/payloadEnvelopeProcessor.js.map +1 -1
- package/lib/chain/blocks/types.d.ts +4 -3
- package/lib/chain/blocks/types.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts +23 -2
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +89 -12
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts +5 -3
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +50 -7
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +0 -4
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -2
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksSanityChecks.js +25 -5
- package/lib/chain/blocks/verifyBlocksSanityChecks.js.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +12 -8
- package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js +8 -3
- package/lib/chain/blocks/verifyPayloadsDataAvailability.js.map +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js +1 -10
- package/lib/chain/blocks/writePayloadEnvelopeInputToDb.js.map +1 -1
- package/lib/chain/chain.d.ts +5 -3
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +42 -12
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +0 -11
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +0 -4
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/errors/blockError.d.ts +8 -1
- package/lib/chain/errors/blockError.d.ts.map +1 -1
- package/lib/chain/errors/blockError.js +2 -0
- package/lib/chain/errors/blockError.js.map +1 -1
- package/lib/chain/errors/executionPayloadBid.d.ts +5 -0
- package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadBid.js +1 -0
- package/lib/chain/errors/executionPayloadBid.js.map +1 -1
- package/lib/chain/errors/index.d.ts +1 -0
- package/lib/chain/errors/index.d.ts.map +1 -1
- package/lib/chain/errors/index.js +1 -0
- package/lib/chain/errors/index.js.map +1 -1
- package/lib/chain/errors/proposerPreferences.d.ts +40 -0
- package/lib/chain/errors/proposerPreferences.d.ts.map +1 -0
- package/lib/chain/errors/proposerPreferences.js +14 -0
- package/lib/chain/errors/proposerPreferences.js.map +1 -0
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +6 -1
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/interface.d.ts +5 -3
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/interface.js.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.d.ts +3 -2
- package/lib/chain/opPools/payloadAttestationPool.d.ts.map +1 -1
- package/lib/chain/opPools/payloadAttestationPool.js +26 -4
- package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +31 -13
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +11 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +47 -15
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +1 -0
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +1 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +1 -4
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +1 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/index.d.ts +1 -0
- package/lib/chain/seenCache/index.d.ts.map +1 -1
- package/lib/chain/seenCache/index.js +1 -0
- package/lib/chain/seenCache/index.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +24 -7
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +69 -17
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/seenCache/seenProposerPreferences.d.ts +16 -0
- package/lib/chain/seenCache/seenProposerPreferences.d.ts.map +1 -0
- package/lib/chain/seenCache/seenProposerPreferences.js +26 -0
- package/lib/chain/seenCache/seenProposerPreferences.js.map +1 -0
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +1 -0
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadBid.js +24 -9
- package/lib/chain/validation/executionPayloadBid.js.map +1 -1
- package/lib/chain/validation/proposerPreferences.d.ts +8 -0
- package/lib/chain/validation/proposerPreferences.d.ts.map +1 -0
- package/lib/chain/validation/proposerPreferences.js +91 -0
- package/lib/chain/validation/proposerPreferences.js.map +1 -0
- package/lib/metrics/metrics/lodestar.d.ts +1 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +4 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/gossip/interface.d.ts +7 -1
- package/lib/network/gossip/interface.d.ts.map +1 -1
- package/lib/network/gossip/interface.js +1 -0
- package/lib/network/gossip/interface.js.map +1 -1
- package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
- package/lib/network/gossip/scoringParameters.js +12 -1
- package/lib/network/gossip/scoringParameters.js.map +1 -1
- package/lib/network/gossip/topic.d.ts +10 -0
- package/lib/network/gossip/topic.d.ts.map +1 -1
- package/lib/network/gossip/topic.js +6 -0
- package/lib/network/gossip/topic.js.map +1 -1
- package/lib/network/interface.d.ts +1 -0
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +5 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +38 -16
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipQueues/index.d.ts.map +1 -1
- package/lib/network/processor/gossipQueues/index.js +5 -0
- package/lib/network/processor/gossipQueues/index.js.map +1 -1
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +6 -5
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +14 -6
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js +11 -5
- package/lib/network/reqresp/handlers/blobSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +17 -5
- package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js +7 -4
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRange.js.map +1 -1
- package/lib/node/nodejs.js +2 -2
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/constants.d.ts +3 -1
- package/lib/sync/constants.d.ts.map +1 -1
- package/lib/sync/constants.js +3 -4
- package/lib/sync/constants.js.map +1 -1
- package/lib/sync/range/batch.d.ts +35 -5
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +240 -59
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts +19 -4
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +64 -11
- 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 +31 -9
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/sync.d.ts.map +1 -1
- package/lib/sync/sync.js +13 -0
- package/lib/sync/sync.js.map +1 -1
- package/lib/sync/types.d.ts +34 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +34 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +29 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +738 -61
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/sync/utils/downloadByRange.d.ts +67 -10
- package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
- package/lib/sync/utils/downloadByRange.js +211 -26
- 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 +16 -2
- package/lib/sync/utils/downloadByRoot.js.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts +0 -1
- package/lib/sync/utils/pendingBlocksTree.d.ts.map +1 -1
- package/lib/sync/utils/pendingBlocksTree.js +0 -9
- package/lib/sync/utils/pendingBlocksTree.js.map +1 -1
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +8 -6
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +16 -15
- package/src/api/impl/beacon/blocks/index.ts +21 -5
- package/src/api/impl/beacon/pool/index.ts +83 -1
- package/src/api/impl/debug/index.ts +0 -1
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/api/impl/validator/index.ts +82 -1
- package/src/chain/blocks/blockInput/blockInput.ts +4 -1
- package/src/chain/blocks/importBlock.ts +16 -48
- package/src/chain/blocks/importExecutionPayload.ts +76 -30
- package/src/chain/blocks/index.ts +71 -22
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +37 -3
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +18 -0
- package/src/chain/blocks/payloadEnvelopeProcessor.ts +2 -2
- package/src/chain/blocks/types.ts +4 -3
- package/src/chain/blocks/utils/chainSegment.ts +114 -17
- package/src/chain/blocks/verifyBlock.ts +70 -9
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +6 -4
- package/src/chain/blocks/verifyBlocksSanityChecks.ts +26 -7
- package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +16 -8
- package/src/chain/blocks/verifyPayloadsDataAvailability.ts +7 -4
- package/src/chain/blocks/writePayloadEnvelopeInputToDb.ts +8 -17
- package/src/chain/chain.ts +55 -10
- package/src/chain/emitter.ts +0 -11
- package/src/chain/errors/blockError.ts +4 -1
- package/src/chain/errors/executionPayloadBid.ts +6 -0
- package/src/chain/errors/index.ts +1 -0
- package/src/chain/errors/proposerPreferences.ts +47 -0
- package/src/chain/initState.ts +9 -1
- package/src/chain/interface.ts +9 -1
- package/src/chain/opPools/payloadAttestationPool.ts +29 -8
- package/src/chain/prepareNextSlot.ts +36 -14
- package/src/chain/produceBlock/produceBlockBody.ts +57 -14
- package/src/chain/regen/interface.ts +1 -0
- package/src/chain/regen/queued.ts +2 -7
- package/src/chain/regen/regen.ts +2 -7
- package/src/chain/seenCache/index.ts +1 -0
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +89 -21
- package/src/chain/seenCache/seenProposerPreferences.ts +32 -0
- package/src/chain/validation/block.ts +1 -0
- package/src/chain/validation/executionPayloadBid.ts +25 -8
- package/src/chain/validation/proposerPreferences.ts +110 -0
- package/src/metrics/metrics/lodestar.ts +4 -0
- package/src/network/gossip/interface.ts +6 -0
- package/src/network/gossip/scoringParameters.ts +14 -1
- package/src/network/gossip/topic.ts +6 -0
- package/src/network/interface.ts +1 -0
- package/src/network/network.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +53 -17
- package/src/network/processor/gossipQueues/index.ts +5 -0
- package/src/network/processor/index.ts +6 -5
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +14 -6
- package/src/network/reqresp/handlers/blobSidecarsByRange.ts +11 -5
- package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +17 -5
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +7 -4
- package/src/node/nodejs.ts +2 -2
- package/src/sync/constants.ts +4 -4
- package/src/sync/range/batch.ts +320 -67
- package/src/sync/range/chain.ts +89 -14
- package/src/sync/range/range.ts +34 -9
- package/src/sync/sync.ts +13 -1
- package/src/sync/types.ts +72 -0
- package/src/sync/unknownBlock.ts +928 -65
- package/src/sync/utils/downloadByRange.ts +378 -39
- package/src/sync/utils/downloadByRoot.ts +24 -2
- package/src/sync/utils/pendingBlocksTree.ts +0 -15
- package/src/util/sszBytes.ts +8 -6
package/lib/sync/unknownBlock.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import { routes } from "@lodestar/api";
|
|
1
2
|
import { ForkSeq } from "@lodestar/params";
|
|
2
3
|
import { RequestError, RequestErrorCode } from "@lodestar/reqresp";
|
|
3
4
|
import { computeTimeAtSlot } from "@lodestar/state-transition";
|
|
4
|
-
import { prettyPrintIndices, pruneSetToMax, sleep } from "@lodestar/utils";
|
|
5
|
+
import { fromHex, prettyPrintIndices, pruneSetToMax, sleep, toRootHex } from "@lodestar/utils";
|
|
5
6
|
import { isBlockInputBlobs, isBlockInputColumns } from "../chain/blocks/blockInput/blockInput.js";
|
|
6
7
|
import { BlockInputSource } from "../chain/blocks/blockInput/types.js";
|
|
8
|
+
import { PayloadError, PayloadErrorCode } from "../chain/blocks/importExecutionPayload.js";
|
|
9
|
+
import { PayloadEnvelopeInputSource } from "../chain/blocks/payloadEnvelopeInput/index.js";
|
|
7
10
|
import { BlockError, BlockErrorCode } from "../chain/errors/index.js";
|
|
8
11
|
import { ChainEvent } from "../chain/index.js";
|
|
12
|
+
import { validateGloasBlockDataColumnSidecars } from "../chain/validation/dataColumnSidecar.js";
|
|
13
|
+
import { validateGossipExecutionPayloadEnvelope } from "../chain/validation/executionPayloadEnvelope.js";
|
|
9
14
|
import { NetworkEvent, prettyPrintPeerIdStr } from "../network/index.js";
|
|
10
15
|
import { shuffle } from "../util/shuffle.js";
|
|
11
16
|
import { sortBy } from "../util/sortBy.js";
|
|
12
17
|
import { wrapError } from "../util/wrapError.js";
|
|
13
18
|
import { MAX_CONCURRENT_REQUESTS } from "./constants.js";
|
|
14
|
-
import { PendingBlockInputStatus, PendingBlockType, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, isPendingBlockInput, } from "./types.js";
|
|
19
|
+
import { PendingBlockInputStatus, PendingBlockType, PendingPayloadInputStatus, getBlockInputSyncCacheItemRootHex, getBlockInputSyncCacheItemSlot, getPayloadSyncCacheItemRootHex, getPayloadSyncCacheItemSlot, isPendingBlockInput, isPendingPayloadEnvelope, isPendingPayloadInput, } from "./types.js";
|
|
15
20
|
import { DownloadByRootError, downloadByRoot } from "./utils/downloadByRoot.js";
|
|
16
|
-
import { getAllDescendantBlocks,
|
|
21
|
+
import { getAllDescendantBlocks, getUnknownAndAncestorBlocks } from "./utils/pendingBlocksTree.js";
|
|
17
22
|
const MAX_ATTEMPTS_PER_BLOCK = 5;
|
|
18
23
|
const MAX_KNOWN_BAD_BLOCKS = 500;
|
|
19
24
|
const MAX_PENDING_BLOCKS = 100;
|
|
@@ -25,6 +30,24 @@ var FetchResult;
|
|
|
25
30
|
FetchResult["FailureTriedAllPeers"] = "failure_tried_all_peers";
|
|
26
31
|
FetchResult["FailureMaxAttempts"] = "failure_max_attempts";
|
|
27
32
|
})(FetchResult || (FetchResult = {}));
|
|
33
|
+
class UnknownBlockRateLimitedError extends Error {
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "UnknownBlockRateLimitedError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function getRateLimitedUntilMs(e) {
|
|
40
|
+
if (!(e instanceof RequestError)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
switch (e.type.code) {
|
|
44
|
+
case RequestErrorCode.RESP_RATE_LIMITED:
|
|
45
|
+
case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
|
|
46
|
+
return e.type.rateLimitedUntilMs ?? null;
|
|
47
|
+
default:
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
28
51
|
/**
|
|
29
52
|
* BlockInputSync is a class that handles ReqResp to find blocks and data related to a specific blockRoot. The
|
|
30
53
|
* blockRoot may have been found via object gossip, or the API. Gossip objects that can trigger a search are block,
|
|
@@ -68,10 +91,13 @@ export class BlockInputSync {
|
|
|
68
91
|
* block RootHex -> PendingBlock. To avoid finding same root at the same time
|
|
69
92
|
*/
|
|
70
93
|
pendingBlocks = new Map();
|
|
94
|
+
// Payload sync is keyed by beacon block root as well, so block and payload queues can unblock each other.
|
|
95
|
+
pendingPayloads = new Map();
|
|
71
96
|
knownBadBlocks = new Set();
|
|
72
97
|
maxPendingBlocks;
|
|
73
98
|
subscribedToNetworkEvents = false;
|
|
74
99
|
peerBalancer;
|
|
100
|
+
rateLimitBackoffTimeout;
|
|
75
101
|
constructor(config, network, chain, logger, metrics, opts) {
|
|
76
102
|
this.config = config;
|
|
77
103
|
this.network = network;
|
|
@@ -83,6 +109,7 @@ export class BlockInputSync {
|
|
|
83
109
|
this.peerBalancer = new UnknownBlockPeerBalancer();
|
|
84
110
|
if (metrics) {
|
|
85
111
|
metrics.blockInputSync.pendingBlocks.addCollect(() => metrics.blockInputSync.pendingBlocks.set(this.pendingBlocks.size));
|
|
112
|
+
metrics.blockInputSync.pendingPayloads.addCollect(() => metrics.blockInputSync.pendingPayloads.set(this.pendingPayloads.size));
|
|
86
113
|
metrics.blockInputSync.knownBadBlocks.addCollect(() => metrics.blockInputSync.knownBadBlocks.set(this.knownBadBlocks.size));
|
|
87
114
|
}
|
|
88
115
|
}
|
|
@@ -95,8 +122,12 @@ export class BlockInputSync {
|
|
|
95
122
|
if (!this.subscribedToNetworkEvents) {
|
|
96
123
|
this.logger.verbose("BlockInputSync enabled.");
|
|
97
124
|
this.chain.emitter.on(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
125
|
+
this.chain.emitter.on(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
98
126
|
this.chain.emitter.on(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
127
|
+
this.chain.emitter.on(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
99
128
|
this.chain.emitter.on(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
129
|
+
this.chain.emitter.on(routes.events.EventType.block, this.onBlockImported);
|
|
130
|
+
this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
100
131
|
this.network.events.on(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
101
132
|
this.network.events.on(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
102
133
|
this.subscribedToNetworkEvents = true;
|
|
@@ -104,9 +135,14 @@ export class BlockInputSync {
|
|
|
104
135
|
}
|
|
105
136
|
unsubscribeFromNetwork() {
|
|
106
137
|
this.logger.verbose("BlockInputSync disabled.");
|
|
138
|
+
this.clearRateLimitBackoffTimer();
|
|
107
139
|
this.chain.emitter.off(ChainEvent.unknownBlockRoot, this.onUnknownBlockRoot);
|
|
140
|
+
this.chain.emitter.off(ChainEvent.unknownEnvelopeBlockRoot, this.onUnknownEnvelopeBlockRoot);
|
|
108
141
|
this.chain.emitter.off(ChainEvent.incompleteBlockInput, this.onIncompleteBlockInput);
|
|
142
|
+
this.chain.emitter.off(ChainEvent.incompletePayloadEnvelope, this.onIncompletePayloadEnvelope);
|
|
109
143
|
this.chain.emitter.off(ChainEvent.blockUnknownParent, this.onUnknownParent);
|
|
144
|
+
this.chain.emitter.off(routes.events.EventType.block, this.onBlockImported);
|
|
145
|
+
this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadImported);
|
|
110
146
|
this.network.events.off(NetworkEvent.peerConnected, this.onPeerConnected);
|
|
111
147
|
this.network.events.off(NetworkEvent.peerDisconnected, this.onPeerDisconnected);
|
|
112
148
|
this.subscribedToNetworkEvents = false;
|
|
@@ -145,12 +181,54 @@ export class BlockInputSync {
|
|
|
145
181
|
this.logger.debug("Error handling incompleteBlockInput event", {}, e);
|
|
146
182
|
}
|
|
147
183
|
};
|
|
184
|
+
onUnknownEnvelopeBlockRoot = (data) => {
|
|
185
|
+
try {
|
|
186
|
+
this.addByPayloadRootHex(data.rootHex, data.peer);
|
|
187
|
+
this.triggerUnknownBlockSearch();
|
|
188
|
+
this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_DATA });
|
|
189
|
+
this.metrics?.blockInputSync.source.inc({ source: data.source });
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
this.logger.debug("Error handling unknownEnvelopeBlockRoot event", {}, e);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
onIncompletePayloadEnvelope = (data) => {
|
|
196
|
+
try {
|
|
197
|
+
this.addByPayloadInput(data.payloadInput, data.peer);
|
|
198
|
+
this.triggerUnknownBlockSearch();
|
|
199
|
+
this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_DATA });
|
|
200
|
+
this.metrics?.blockInputSync.source.inc({ source: data.source });
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
this.logger.debug("Error handling incompletePayloadEnvelope event", {}, e);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
148
206
|
/**
|
|
149
207
|
* Process an unknownBlockParent event and register the block in `pendingBlocks` Map.
|
|
150
208
|
*/
|
|
151
209
|
onUnknownParent = (data) => {
|
|
152
210
|
try {
|
|
153
|
-
this.
|
|
211
|
+
const missingDependency = this.getMissingBlockDependency(data.blockInput);
|
|
212
|
+
if (missingDependency.kind === "invalidParentPayload") {
|
|
213
|
+
this.addByBlockInput(data.blockInput, data.peer);
|
|
214
|
+
const pendingBlock = this.pendingBlocks.get(data.blockInput.blockRootHex);
|
|
215
|
+
if (pendingBlock && isPendingBlockInput(pendingBlock)) {
|
|
216
|
+
this.logger.debug("Ignoring block with conflicting parent payload hash", {
|
|
217
|
+
slot: pendingBlock.blockInput.slot,
|
|
218
|
+
root: pendingBlock.blockInput.blockRootHex,
|
|
219
|
+
parentRoot: missingDependency.parentRootHex,
|
|
220
|
+
parentBlockHash: missingDependency.parentBlockHashHex,
|
|
221
|
+
});
|
|
222
|
+
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (missingDependency.kind === "parentPayload") {
|
|
227
|
+
this.addByPayloadRootHex(missingDependency.rootHex, data.peer);
|
|
228
|
+
}
|
|
229
|
+
else if (missingDependency.kind === "parentBlock") {
|
|
230
|
+
this.addByRootHex(missingDependency.rootHex, data.peer);
|
|
231
|
+
}
|
|
154
232
|
this.addByBlockInput(data.blockInput, data.peer);
|
|
155
233
|
this.triggerUnknownBlockSearch();
|
|
156
234
|
this.metrics?.blockInputSync.requests.inc({ type: PendingBlockType.UNKNOWN_PARENT });
|
|
@@ -160,8 +238,18 @@ export class BlockInputSync {
|
|
|
160
238
|
this.logger.debug("Error handling unknownParent event", {}, e);
|
|
161
239
|
}
|
|
162
240
|
};
|
|
241
|
+
onBlockImported = () => {
|
|
242
|
+
if (this.pendingPayloads.size > 0) {
|
|
243
|
+
this.triggerUnknownBlockSearch();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
onPayloadImported = ({ blockRoot, }) => {
|
|
247
|
+
this.pendingPayloads.delete(blockRoot);
|
|
248
|
+
this.triggerUnknownBlockSearch();
|
|
249
|
+
};
|
|
163
250
|
addByRootHex = (rootHex, peerIdStr) => {
|
|
164
251
|
let pendingBlock = this.pendingBlocks.get(rootHex);
|
|
252
|
+
let added = false;
|
|
165
253
|
if (!pendingBlock) {
|
|
166
254
|
pendingBlock = {
|
|
167
255
|
status: PendingBlockInputStatus.pending,
|
|
@@ -170,6 +258,7 @@ export class BlockInputSync {
|
|
|
170
258
|
timeAddedSec: Date.now() / 1000,
|
|
171
259
|
};
|
|
172
260
|
this.pendingBlocks.set(rootHex, pendingBlock);
|
|
261
|
+
added = true;
|
|
173
262
|
this.logger.verbose("Added new rootHex to BlockInputSync.pendingBlocks", {
|
|
174
263
|
root: pendingBlock.rootHex,
|
|
175
264
|
peerIdStr: peerIdStr ?? "unknown peer",
|
|
@@ -184,6 +273,7 @@ export class BlockInputSync {
|
|
|
184
273
|
if (prunedItemCount > 0) {
|
|
185
274
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
|
|
186
275
|
}
|
|
276
|
+
return added;
|
|
187
277
|
};
|
|
188
278
|
addByBlockInput = (blockInput, peerIdStr) => {
|
|
189
279
|
let pendingBlock = this.pendingBlocks.get(blockInput.blockRootHex);
|
|
@@ -211,6 +301,43 @@ export class BlockInputSync {
|
|
|
211
301
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
|
|
212
302
|
}
|
|
213
303
|
};
|
|
304
|
+
addByPayloadRootHex = (rootHex, peerIdStr) => {
|
|
305
|
+
let pendingPayload = this.pendingPayloads.get(rootHex);
|
|
306
|
+
let added = false;
|
|
307
|
+
if (!pendingPayload) {
|
|
308
|
+
pendingPayload = {
|
|
309
|
+
status: PendingPayloadInputStatus.pending,
|
|
310
|
+
rootHex,
|
|
311
|
+
peerIdStrings: new Set(),
|
|
312
|
+
timeAddedSec: Date.now() / 1000,
|
|
313
|
+
};
|
|
314
|
+
this.pendingPayloads.set(rootHex, pendingPayload);
|
|
315
|
+
added = true;
|
|
316
|
+
this.logger.verbose("Added new payload rootHex to BlockInputSync.pendingPayloads", {
|
|
317
|
+
root: rootHex,
|
|
318
|
+
peerIdStr: peerIdStr ?? "unknown peer",
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (peerIdStr) {
|
|
322
|
+
pendingPayload.peerIdStrings.add(peerIdStr);
|
|
323
|
+
}
|
|
324
|
+
const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
|
|
325
|
+
if (prunedItemCount > 0) {
|
|
326
|
+
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
|
|
327
|
+
}
|
|
328
|
+
return added;
|
|
329
|
+
};
|
|
330
|
+
addByPayloadInput = (payloadInput, peerIdStr, envelope) => {
|
|
331
|
+
const pendingPayload = this.toPendingPayloadInput(payloadInput, this.pendingPayloads.get(payloadInput.blockRootHex), envelope);
|
|
332
|
+
if (peerIdStr) {
|
|
333
|
+
pendingPayload.peerIdStrings.add(peerIdStr);
|
|
334
|
+
}
|
|
335
|
+
this.pendingPayloads.set(payloadInput.blockRootHex, pendingPayload);
|
|
336
|
+
const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
|
|
337
|
+
if (prunedItemCount > 0) {
|
|
338
|
+
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
214
341
|
onPeerConnected = (data) => {
|
|
215
342
|
try {
|
|
216
343
|
const peerId = data.peer;
|
|
@@ -225,49 +352,197 @@ export class BlockInputSync {
|
|
|
225
352
|
onPeerDisconnected = (data) => {
|
|
226
353
|
const peerId = data.peer;
|
|
227
354
|
this.peerBalancer.onPeerDisconnected(peerId);
|
|
355
|
+
this.scheduleRateLimitBackoffRetry();
|
|
228
356
|
};
|
|
357
|
+
/**
|
|
358
|
+
* Post-gloas, a locally complete block can still be blocked on its parent's execution payload lineage.
|
|
359
|
+
* Distinguish which dependency is missing so the scheduler can enqueue the right follow-up work.
|
|
360
|
+
*/
|
|
361
|
+
getMissingBlockDependency(blockInput) {
|
|
362
|
+
const parentRootHex = blockInput.parentRootHex;
|
|
363
|
+
if (!this.chain.forkChoice.hasBlockHex(parentRootHex)) {
|
|
364
|
+
return { kind: "parentBlock", rootHex: parentRootHex };
|
|
365
|
+
}
|
|
366
|
+
if (!blockInput.hasBlock()) {
|
|
367
|
+
return { kind: "block", rootHex: blockInput.blockRootHex };
|
|
368
|
+
}
|
|
369
|
+
if (this.config.getForkSeq(blockInput.slot) < ForkSeq.gloas) {
|
|
370
|
+
return { kind: "ready" };
|
|
371
|
+
}
|
|
372
|
+
const block = blockInput.getBlock();
|
|
373
|
+
const parentBlockHashHex = toRootHex(block.message.body.signedExecutionPayloadBid.message.parentBlockHash);
|
|
374
|
+
if (this.chain.forkChoice.getBlockHexAndBlockHash(parentRootHex, parentBlockHashHex) !== null) {
|
|
375
|
+
return { kind: "ready" };
|
|
376
|
+
}
|
|
377
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(parentRootHex)) {
|
|
378
|
+
return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
|
|
379
|
+
}
|
|
380
|
+
const parentPayloadInput = this.chain.seenPayloadEnvelopeInputCache.get(parentRootHex);
|
|
381
|
+
if (parentPayloadInput) {
|
|
382
|
+
if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
|
|
383
|
+
return { kind: "parentPayload", rootHex: parentRootHex };
|
|
384
|
+
}
|
|
385
|
+
return { kind: "invalidParentPayload", parentRootHex, parentBlockHashHex };
|
|
386
|
+
}
|
|
387
|
+
return { kind: "parentPayload", rootHex: parentRootHex };
|
|
388
|
+
}
|
|
389
|
+
advancePendingBlock(pendingBlock) {
|
|
390
|
+
const missingDependency = this.getMissingBlockDependency(pendingBlock.blockInput);
|
|
391
|
+
switch (missingDependency.kind) {
|
|
392
|
+
case "ready":
|
|
393
|
+
return "ready";
|
|
394
|
+
case "block":
|
|
395
|
+
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
396
|
+
return "queued_block";
|
|
397
|
+
case "parentBlock": {
|
|
398
|
+
let added = this.addByRootHex(missingDependency.rootHex);
|
|
399
|
+
for (const peerIdStr of pendingBlock.peerIdStrings) {
|
|
400
|
+
added = this.addByRootHex(missingDependency.rootHex, peerIdStr) || added;
|
|
401
|
+
}
|
|
402
|
+
return added ? "queued_parent_block" : "blocked";
|
|
403
|
+
}
|
|
404
|
+
case "parentPayload": {
|
|
405
|
+
let added = this.addByPayloadRootHex(missingDependency.rootHex);
|
|
406
|
+
for (const peerIdStr of pendingBlock.peerIdStrings) {
|
|
407
|
+
added = this.addByPayloadRootHex(missingDependency.rootHex, peerIdStr) || added;
|
|
408
|
+
}
|
|
409
|
+
return added ? "queued_parent_payload" : "blocked";
|
|
410
|
+
}
|
|
411
|
+
case "invalidParentPayload":
|
|
412
|
+
this.logger.debug("Removing block with conflicting parent payload hash", {
|
|
413
|
+
slot: pendingBlock.blockInput.slot,
|
|
414
|
+
root: pendingBlock.blockInput.blockRootHex,
|
|
415
|
+
parentRoot: missingDependency.parentRootHex,
|
|
416
|
+
parentBlockHash: missingDependency.parentBlockHashHex,
|
|
417
|
+
});
|
|
418
|
+
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
419
|
+
return "removed";
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
toPendingPayloadInput(payloadInput, previous, envelope) {
|
|
423
|
+
// Normalize every payload queueing path into the same cache shape while preserving first-seen
|
|
424
|
+
// timing and peer provenance from any earlier by-root or envelope-only entry.
|
|
425
|
+
const queuedEnvelope = envelope ?? (previous && isPendingPayloadEnvelope(previous) ? previous.envelope : undefined);
|
|
426
|
+
if (queuedEnvelope && !payloadInput.hasPayloadEnvelope()) {
|
|
427
|
+
payloadInput.addPayloadEnvelope({
|
|
428
|
+
envelope: queuedEnvelope,
|
|
429
|
+
source: PayloadEnvelopeInputSource.byRoot,
|
|
430
|
+
seenTimestampSec: Date.now() / 1000,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
status: payloadInput.isComplete() ? PendingPayloadInputStatus.downloaded : PendingPayloadInputStatus.pending,
|
|
435
|
+
payloadInput,
|
|
436
|
+
timeAddedSec: previous?.timeAddedSec ?? Date.now() / 1000,
|
|
437
|
+
timeSyncedSec: payloadInput.isComplete() ? Date.now() / 1000 : undefined,
|
|
438
|
+
peerIdStrings: new Set(previous?.peerIdStrings ?? []),
|
|
439
|
+
};
|
|
440
|
+
}
|
|
229
441
|
/**
|
|
230
442
|
* Gather tip parent blocks with unknown parent and do a search for all of them
|
|
231
443
|
*/
|
|
232
444
|
triggerUnknownBlockSearch = () => {
|
|
233
445
|
// Cheap early stop to prevent calling the network.getConnectedPeers()
|
|
234
|
-
if (this.pendingBlocks.size === 0) {
|
|
446
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
235
447
|
return;
|
|
236
448
|
}
|
|
237
|
-
// If the node loses all peers with pending unknown blocks, the sync will stall
|
|
449
|
+
// If the node loses all peers with pending unknown blocks or payloads, the sync will stall
|
|
238
450
|
const connectedPeers = this.network.getConnectedPeers();
|
|
239
|
-
|
|
240
|
-
this.logger.debug("No connected peers, skipping unknown block search.");
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
451
|
+
const hasConnectedPeers = connectedPeers.length > 0;
|
|
243
452
|
const { unknowns, ancestors } = getUnknownAndAncestorBlocks(this.pendingBlocks);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (this.chain.forkChoice.hasBlockHex(block.blockInput.parentRootHex)) {
|
|
453
|
+
let processedBlocks = 0;
|
|
454
|
+
let shouldRerunBlockSearch = false;
|
|
455
|
+
for (const block of ancestors) {
|
|
456
|
+
const advanceResult = this.advancePendingBlock(block);
|
|
457
|
+
switch (advanceResult) {
|
|
458
|
+
case "ready":
|
|
251
459
|
processedBlocks++;
|
|
252
|
-
this.
|
|
460
|
+
this.processReadyBlock(block).catch((e) => {
|
|
253
461
|
this.logger.debug("Unexpected error - process old downloaded block", {}, e);
|
|
254
462
|
});
|
|
463
|
+
break;
|
|
464
|
+
case "queued_block":
|
|
465
|
+
case "queued_parent_block":
|
|
466
|
+
shouldRerunBlockSearch = true;
|
|
467
|
+
break;
|
|
468
|
+
case "queued_parent_payload":
|
|
469
|
+
case "blocked":
|
|
470
|
+
case "removed":
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (unknowns.length > 0) {
|
|
475
|
+
if (!hasConnectedPeers) {
|
|
476
|
+
this.logger.debug("No connected peers, skipping unknown block download.");
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Most of the time there is exactly 1 unknown block
|
|
480
|
+
for (const block of unknowns) {
|
|
481
|
+
this.downloadBlock(block).catch((e) => {
|
|
482
|
+
this.logger.debug("Unexpected error - downloadBlock", { root: getBlockInputSyncCacheItemRootHex(block) }, e);
|
|
483
|
+
});
|
|
255
484
|
}
|
|
256
485
|
}
|
|
486
|
+
}
|
|
487
|
+
else if (ancestors.length > 0) {
|
|
488
|
+
// It's rare when there is no unknown block
|
|
489
|
+
// see https://github.com/ChainSafe/lodestar/issues/5649#issuecomment-1594213550
|
|
257
490
|
this.logger.verbose("No unknown block, process ancestor downloaded blocks", {
|
|
258
491
|
pendingBlocks: this.pendingBlocks.size,
|
|
259
492
|
ancestorBlocks: ancestors.length,
|
|
260
493
|
processedBlocks,
|
|
261
494
|
});
|
|
262
|
-
return;
|
|
263
495
|
}
|
|
264
|
-
//
|
|
265
|
-
for (const
|
|
266
|
-
|
|
267
|
-
this.
|
|
496
|
+
// Blocks can unblock payloads and payloads can unblock blocks, so every scheduler pass services both queues.
|
|
497
|
+
for (const payload of Array.from(this.pendingPayloads.values())) {
|
|
498
|
+
if (isPendingPayloadInput(payload) && payload.status === PendingPayloadInputStatus.downloaded) {
|
|
499
|
+
this.processPayload(payload).catch((e) => {
|
|
500
|
+
this.logger.debug("Unexpected error - process downloaded payload", {}, e);
|
|
501
|
+
});
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (isPendingPayloadEnvelope(payload)) {
|
|
505
|
+
this.reconcilePayloadEnvelope(payload).catch((e) => {
|
|
506
|
+
this.logger.debug("Unexpected error - reconcile pending payload envelope", {}, e);
|
|
507
|
+
});
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (!hasConnectedPeers) {
|
|
511
|
+
this.logger.debug("No connected peers, skipping unknown payload download.", {
|
|
512
|
+
root: getPayloadSyncCacheItemRootHex(payload),
|
|
513
|
+
});
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
this.downloadPayload(payload).catch((e) => {
|
|
517
|
+
this.logger.debug("Unexpected error - downloadPayload", { root: getPayloadSyncCacheItemRootHex(payload) }, e);
|
|
268
518
|
});
|
|
269
519
|
}
|
|
520
|
+
if (shouldRerunBlockSearch) {
|
|
521
|
+
this.triggerUnknownBlockSearch();
|
|
522
|
+
}
|
|
270
523
|
};
|
|
524
|
+
scheduleRateLimitBackoffRetry() {
|
|
525
|
+
this.clearRateLimitBackoffTimer();
|
|
526
|
+
if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const now = Date.now();
|
|
530
|
+
const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
|
|
531
|
+
if (retryAt === null) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
this.rateLimitBackoffTimeout = setTimeout(() => {
|
|
535
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
536
|
+
this.triggerUnknownBlockSearch();
|
|
537
|
+
this.scheduleRateLimitBackoffRetry();
|
|
538
|
+
}, Math.max(0, retryAt - now));
|
|
539
|
+
}
|
|
540
|
+
clearRateLimitBackoffTimer() {
|
|
541
|
+
if (this.rateLimitBackoffTimeout !== undefined) {
|
|
542
|
+
clearTimeout(this.rateLimitBackoffTimeout);
|
|
543
|
+
this.rateLimitBackoffTimeout = undefined;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
271
546
|
async downloadBlock(block) {
|
|
272
547
|
if (block.status !== PendingBlockInputStatus.pending) {
|
|
273
548
|
return;
|
|
@@ -297,10 +572,24 @@ export class BlockInputSync {
|
|
|
297
572
|
};
|
|
298
573
|
this.logger.verbose("Downloaded unknown block", logCtx2);
|
|
299
574
|
if (parentInForkChoice) {
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
575
|
+
// If the direct parent is already in fork choice, let the block state machine decide if
|
|
576
|
+
// the next step is block import, parent payload download, or branch removal.
|
|
577
|
+
const advanceResult = this.advancePendingBlock(pending);
|
|
578
|
+
switch (advanceResult) {
|
|
579
|
+
case "ready":
|
|
580
|
+
this.processReadyBlock(pending).catch((e) => {
|
|
581
|
+
this.logger.debug("Unexpected error - process newly downloaded block", logCtx2, e);
|
|
582
|
+
});
|
|
583
|
+
break;
|
|
584
|
+
case "queued_block":
|
|
585
|
+
case "queued_parent_block":
|
|
586
|
+
case "queued_parent_payload":
|
|
587
|
+
this.triggerUnknownBlockSearch();
|
|
588
|
+
break;
|
|
589
|
+
case "blocked":
|
|
590
|
+
case "removed":
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
304
593
|
}
|
|
305
594
|
else if (blockSlot <= finalizedSlot) {
|
|
306
595
|
// the common ancestor of the downloading chain and canonical chain should be at least the finalized slot and
|
|
@@ -319,32 +608,26 @@ export class BlockInputSync {
|
|
|
319
608
|
}
|
|
320
609
|
}
|
|
321
610
|
else {
|
|
611
|
+
if (res.err instanceof UnknownBlockRateLimitedError) {
|
|
612
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
613
|
+
if (pendingBlock) {
|
|
614
|
+
pendingBlock.status = PendingBlockInputStatus.pending;
|
|
615
|
+
}
|
|
616
|
+
this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
|
|
617
|
+
this.scheduleRateLimitBackoffRetry();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
322
620
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
323
621
|
this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
|
|
324
622
|
this.removeAndDownScoreAllDescendants(block);
|
|
325
623
|
}
|
|
326
624
|
}
|
|
327
625
|
/**
|
|
328
|
-
*
|
|
329
|
-
* On error, remove and downscore
|
|
330
|
-
* This function could run recursively for all descendant blocks
|
|
626
|
+
* Import a block that has already passed the local dependency checks in BlockInputSync.
|
|
627
|
+
* On error, remove and downscore descendants as appropriate for the failure type.
|
|
331
628
|
*/
|
|
332
|
-
async
|
|
333
|
-
// pending block status is `downloaded` right after `downloadBlock`
|
|
334
|
-
// but could be `pending` if added by `onUnknownBlockParent` event and this function is called recursively
|
|
629
|
+
async processReadyBlock(pendingBlock) {
|
|
335
630
|
if (pendingBlock.status !== PendingBlockInputStatus.downloaded) {
|
|
336
|
-
if (pendingBlock.status === PendingBlockInputStatus.pending) {
|
|
337
|
-
const connectedPeers = this.network.getConnectedPeers();
|
|
338
|
-
if (connectedPeers.length === 0) {
|
|
339
|
-
this.logger.debug("No connected peers, skipping download block", {
|
|
340
|
-
slot: pendingBlock.blockInput.slot,
|
|
341
|
-
blockRoot: pendingBlock.blockInput.blockRootHex,
|
|
342
|
-
});
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
// if the download is a success we'll call `processBlock()` for this block
|
|
346
|
-
await this.downloadBlock(pendingBlock);
|
|
347
|
-
}
|
|
348
631
|
return;
|
|
349
632
|
}
|
|
350
633
|
pendingBlock.status = PendingBlockInputStatus.processing;
|
|
@@ -384,14 +667,9 @@ export class BlockInputSync {
|
|
|
384
667
|
if (!res.err) {
|
|
385
668
|
// no need to update status to "processed", delete anyway
|
|
386
669
|
this.pendingBlocks.delete(pendingBlock.blockInput.blockRootHex);
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this.processBlock(descendantBlock).catch((e) => {
|
|
391
|
-
this.logger.debug("Unexpected error - process descendant block", {}, e);
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
670
|
+
// Re-enter the scheduler so descendants blocked on either parent blocks or parent payloads
|
|
671
|
+
// are advanced through the same dependency checks as every other pending item.
|
|
672
|
+
this.triggerUnknownBlockSearch();
|
|
395
673
|
}
|
|
396
674
|
else {
|
|
397
675
|
const errorData = { slot: pendingBlock.blockInput.slot, root: pendingBlock.blockInput.blockRootHex };
|
|
@@ -406,6 +684,14 @@ export class BlockInputSync {
|
|
|
406
684
|
this.logger.debug("Attempted to process block but its parent was still unknown", errorData, res.err);
|
|
407
685
|
pendingBlock.status = PendingBlockInputStatus.downloaded;
|
|
408
686
|
break;
|
|
687
|
+
case BlockErrorCode.PARENT_PAYLOAD_UNKNOWN:
|
|
688
|
+
this.logger.error("processReadyBlock() hit unexpected parent payload dependency after readiness checks", {
|
|
689
|
+
...errorData,
|
|
690
|
+
parentRoot: pendingBlock.blockInput.parentRootHex,
|
|
691
|
+
parentBlockHash: res.err.type.parentBlockHash,
|
|
692
|
+
}, res.err);
|
|
693
|
+
pendingBlock.status = PendingBlockInputStatus.downloaded;
|
|
694
|
+
break;
|
|
409
695
|
case BlockErrorCode.EXECUTION_ENGINE_ERROR:
|
|
410
696
|
// Removing the block(s) without penalizing the peers, hoping for EL to
|
|
411
697
|
// recover on a latter download + verify attempt
|
|
@@ -424,6 +710,312 @@ export class BlockInputSync {
|
|
|
424
710
|
}
|
|
425
711
|
}
|
|
426
712
|
}
|
|
713
|
+
/**
|
|
714
|
+
* Reconcile an envelope-first payload entry once the block import path has seeded its
|
|
715
|
+
* PayloadEnvelopeInput. This may queue block download, validate the speculative envelope, or
|
|
716
|
+
* downgrade back to by-root fetching when the cached envelope does not match the imported block.
|
|
717
|
+
*/
|
|
718
|
+
async reconcilePayloadEnvelope(pendingPayload) {
|
|
719
|
+
const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
|
|
720
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
721
|
+
this.pendingPayloads.delete(rootHex);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
725
|
+
if (!payloadInput) {
|
|
726
|
+
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
727
|
+
// Column commitments live on the block body, so an envelope-only entry has to pull the block first.
|
|
728
|
+
if (!this.pendingBlocks.has(rootHex)) {
|
|
729
|
+
this.addByRootHex(rootHex);
|
|
730
|
+
}
|
|
731
|
+
const pendingBlock = this.pendingBlocks.get(rootHex);
|
|
732
|
+
if (pendingBlock && this.network.getConnectedPeers().length > 0) {
|
|
733
|
+
await this.downloadBlock(pendingBlock);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
this.logger.debug("Missing PayloadEnvelopeInput for known block while reconciling payload envelope", {
|
|
738
|
+
root: rootHex,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
if (!payloadInput.hasPayloadEnvelope()) {
|
|
744
|
+
const validationResult = await wrapError(validateGossipExecutionPayloadEnvelope(this.chain, pendingPayload.envelope));
|
|
745
|
+
if (validationResult.err) {
|
|
746
|
+
this.logger.debug("Pending payload envelope failed validation after block import, refetching by root", { slot: pendingPayload.envelope.message.payload.slotNumber, root: rootHex }, validationResult.err);
|
|
747
|
+
const pendingPayloadByRoot = {
|
|
748
|
+
status: PendingPayloadInputStatus.pending,
|
|
749
|
+
rootHex,
|
|
750
|
+
timeAddedSec: pendingPayload.timeAddedSec,
|
|
751
|
+
peerIdStrings: new Set(pendingPayload.peerIdStrings),
|
|
752
|
+
};
|
|
753
|
+
this.pendingPayloads.set(rootHex, pendingPayloadByRoot);
|
|
754
|
+
if (this.network.getConnectedPeers().length > 0) {
|
|
755
|
+
await this.downloadPayload(pendingPayloadByRoot);
|
|
756
|
+
}
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const upgradedPayload = this.toPendingPayloadInput(payloadInput, pendingPayload, pendingPayload.envelope);
|
|
761
|
+
this.pendingPayloads.set(rootHex, upgradedPayload);
|
|
762
|
+
if (upgradedPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
763
|
+
await this.processPayload(upgradedPayload);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
await this.downloadPayload(upgradedPayload);
|
|
767
|
+
}
|
|
768
|
+
async downloadPayload(payload) {
|
|
769
|
+
if (isPendingPayloadEnvelope(payload)) {
|
|
770
|
+
await this.reconcilePayloadEnvelope(payload);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const rootHex = getPayloadSyncCacheItemRootHex(payload);
|
|
774
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
775
|
+
this.pendingPayloads.delete(rootHex);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (payload.status !== PendingPayloadInputStatus.pending) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const logCtx = {
|
|
782
|
+
slot: getPayloadSyncCacheItemSlot(payload),
|
|
783
|
+
root: rootHex,
|
|
784
|
+
pendingPayloads: this.pendingPayloads.size,
|
|
785
|
+
};
|
|
786
|
+
this.logger.verbose("BlockInputSync.downloadPayload()", logCtx);
|
|
787
|
+
payload.status = PendingPayloadInputStatus.fetching;
|
|
788
|
+
const res = await wrapError(this.fetchPayloadInput(payload));
|
|
789
|
+
if (!res.err) {
|
|
790
|
+
const pendingPayload = res.result;
|
|
791
|
+
this.pendingPayloads.set(getPayloadSyncCacheItemRootHex(pendingPayload), pendingPayload);
|
|
792
|
+
if (isPendingPayloadEnvelope(pendingPayload)) {
|
|
793
|
+
await this.reconcilePayloadEnvelope(pendingPayload);
|
|
794
|
+
}
|
|
795
|
+
else if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
796
|
+
await this.processPayload(pendingPayload);
|
|
797
|
+
}
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
this.logger.debug("Ignoring unknown payload root after failed download", logCtx, res.err);
|
|
801
|
+
if (!isPendingPayloadEnvelope(payload)) {
|
|
802
|
+
payload.status = PendingPayloadInputStatus.pending;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async processPayload(pendingPayload) {
|
|
806
|
+
const rootHex = pendingPayload.payloadInput.blockRootHex;
|
|
807
|
+
const logCtx = { slot: pendingPayload.payloadInput.slot, root: rootHex };
|
|
808
|
+
if (pendingPayload.status !== PendingPayloadInputStatus.downloaded) {
|
|
809
|
+
this.logger.debug("Skipping payload processing before payload input is downloaded", {
|
|
810
|
+
...logCtx,
|
|
811
|
+
status: pendingPayload.status,
|
|
812
|
+
});
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
816
|
+
this.logger.debug("Payload already imported while processing unknown payload", logCtx);
|
|
817
|
+
this.pendingPayloads.delete(rootHex);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
821
|
+
this.logger.debug("Payload input is ready before its block is in fork choice", logCtx);
|
|
822
|
+
const added = this.addByRootHex(rootHex);
|
|
823
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
824
|
+
if (added) {
|
|
825
|
+
this.triggerUnknownBlockSearch();
|
|
826
|
+
}
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
pendingPayload.status = PendingPayloadInputStatus.processing;
|
|
830
|
+
const res = await wrapError(this.chain.processExecutionPayload(pendingPayload.payloadInput));
|
|
831
|
+
if (!res.err) {
|
|
832
|
+
this.logger.debug("Processed payload from unknown sync", logCtx);
|
|
833
|
+
this.pendingPayloads.delete(rootHex);
|
|
834
|
+
this.triggerUnknownBlockSearch();
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
if (res.err instanceof PayloadError) {
|
|
838
|
+
switch (res.err.type.code) {
|
|
839
|
+
case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
|
|
840
|
+
// Payload sync discovered the block dependency before the block queue did. Re-enqueue the
|
|
841
|
+
// block and keep the payload ready so the scheduler can retry once the block reaches fork choice.
|
|
842
|
+
if (this.addByRootHex(rootHex)) {
|
|
843
|
+
this.triggerUnknownBlockSearch();
|
|
844
|
+
}
|
|
845
|
+
// Keep the payload out of any synchronous requeue pass; a later scheduler pass will retry it.
|
|
846
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
847
|
+
break;
|
|
848
|
+
case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
|
|
849
|
+
this.logger.debug("Execution engine error while processing payload from unknown sync", logCtx, res.err);
|
|
850
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
851
|
+
break;
|
|
852
|
+
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
853
|
+
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
854
|
+
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
855
|
+
// TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
|
|
856
|
+
// reintroducing envelope replacement / recreation flows.
|
|
857
|
+
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
858
|
+
this.removePendingPayloadAndDescendants(rootHex);
|
|
859
|
+
break;
|
|
860
|
+
default:
|
|
861
|
+
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
862
|
+
this.pendingPayloads.delete(rootHex);
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
this.logger.debug("Unknown error processing payload from unknown sync", logCtx, res.err);
|
|
867
|
+
pendingPayload.status = PendingPayloadInputStatus.downloaded;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Download payload material keyed by beacon block root. Unlike block download, payload sync may
|
|
871
|
+
* already have a locally cached envelope or partial columns, so each attempt starts from local state
|
|
872
|
+
* and only asks peers for the remaining pieces.
|
|
873
|
+
*/
|
|
874
|
+
async fetchPayloadInput(cacheItem) {
|
|
875
|
+
const rootHex = getPayloadSyncCacheItemRootHex(cacheItem);
|
|
876
|
+
const blockRoot = fromHex(rootHex);
|
|
877
|
+
const excludedPeers = new Set();
|
|
878
|
+
let slot = getPayloadSyncCacheItemSlot(cacheItem);
|
|
879
|
+
let payloadInput = isPendingPayloadInput(cacheItem)
|
|
880
|
+
? cacheItem.payloadInput
|
|
881
|
+
: this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
882
|
+
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
883
|
+
let i = 0;
|
|
884
|
+
let deferredByRateLimit = false;
|
|
885
|
+
while (i++ < this.getMaxDownloadAttempts()) {
|
|
886
|
+
const pendingColumns = payloadInput?.hasAllData()
|
|
887
|
+
? new Set()
|
|
888
|
+
: new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
|
|
889
|
+
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
890
|
+
if (peerMeta === null) {
|
|
891
|
+
if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
|
|
892
|
+
throw new UnknownBlockRateLimitedError(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
|
|
893
|
+
}
|
|
894
|
+
throw Error(`Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`);
|
|
895
|
+
}
|
|
896
|
+
const { peerId, client: peerClient } = peerMeta;
|
|
897
|
+
cacheItem.peerIdStrings.add(peerId);
|
|
898
|
+
try {
|
|
899
|
+
if (!envelope) {
|
|
900
|
+
envelope = await this.fetchExecutionPayloadEnvelope(peerId, blockRoot, rootHex);
|
|
901
|
+
slot = envelope.message.payload.slotNumber;
|
|
902
|
+
}
|
|
903
|
+
payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
904
|
+
if (!payloadInput) {
|
|
905
|
+
if (this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
906
|
+
throw new Error(`Missing PayloadEnvelopeInput for known block ${rootHex}`);
|
|
907
|
+
}
|
|
908
|
+
// Keep the validated envelope around, but wait for the block body before turning it into a full payload input.
|
|
909
|
+
return {
|
|
910
|
+
status: PendingPayloadInputStatus.waitingForBlock,
|
|
911
|
+
envelope,
|
|
912
|
+
timeAddedSec: cacheItem.timeAddedSec,
|
|
913
|
+
peerIdStrings: cacheItem.peerIdStrings,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
if (!payloadInput.hasPayloadEnvelope()) {
|
|
917
|
+
await validateGossipExecutionPayloadEnvelope(this.chain, envelope);
|
|
918
|
+
}
|
|
919
|
+
let pendingPayload = this.toPendingPayloadInput(payloadInput, cacheItem, envelope);
|
|
920
|
+
if (!pendingPayload.payloadInput.hasAllData()) {
|
|
921
|
+
const missing = pendingPayload.payloadInput.getMissingSampledColumnMeta().missing;
|
|
922
|
+
if (missing.length > 0) {
|
|
923
|
+
const columnSidecars = await this.fetchPayloadColumns(peerMeta, pendingPayload.payloadInput, missing);
|
|
924
|
+
const seenTimestampSec = Date.now() / 1000;
|
|
925
|
+
for (const columnSidecar of columnSidecars) {
|
|
926
|
+
if (pendingPayload.payloadInput.hasColumn(columnSidecar.index)) {
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
pendingPayload.payloadInput.addColumn({
|
|
930
|
+
columnSidecar,
|
|
931
|
+
source: PayloadEnvelopeInputSource.byRoot,
|
|
932
|
+
seenTimestampSec,
|
|
933
|
+
peerIdStr: peerId,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
pendingPayload = this.toPendingPayloadInput(pendingPayload.payloadInput, pendingPayload);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
this.logger.verbose("BlockInputSync.fetchPayloadInput: successful download", {
|
|
940
|
+
slot,
|
|
941
|
+
rootHex,
|
|
942
|
+
peerId,
|
|
943
|
+
peerClient,
|
|
944
|
+
hasPayload: pendingPayload.payloadInput.hasPayloadEnvelope(),
|
|
945
|
+
hasAllData: pendingPayload.payloadInput.hasAllData(),
|
|
946
|
+
});
|
|
947
|
+
if (pendingPayload.status === PendingPayloadInputStatus.downloaded) {
|
|
948
|
+
return pendingPayload;
|
|
949
|
+
}
|
|
950
|
+
cacheItem = pendingPayload;
|
|
951
|
+
payloadInput = pendingPayload.payloadInput;
|
|
952
|
+
}
|
|
953
|
+
catch (e) {
|
|
954
|
+
this.logger.debug("Error downloading payload in BlockInputSync.fetchPayloadInput", { slot, rootHex, attempt: i, peer: peerId, peerClient }, e);
|
|
955
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(e);
|
|
956
|
+
if (rateLimitedUntilMs !== null) {
|
|
957
|
+
deferredByRateLimit = true;
|
|
958
|
+
this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
|
|
959
|
+
this.scheduleRateLimitBackoffRetry();
|
|
960
|
+
}
|
|
961
|
+
else if (e instanceof RequestError) {
|
|
962
|
+
switch (e.type.code) {
|
|
963
|
+
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
964
|
+
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
965
|
+
break;
|
|
966
|
+
default:
|
|
967
|
+
excludedPeers.add(peerId);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
excludedPeers.add(peerId);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
finally {
|
|
976
|
+
this.peerBalancer.onRequestCompleted(peerId);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
980
|
+
throw new UnknownBlockRateLimitedError(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`);
|
|
981
|
+
}
|
|
982
|
+
throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
|
|
983
|
+
}
|
|
984
|
+
async fetchExecutionPayloadEnvelope(peerIdStr, blockRoot, rootHex) {
|
|
985
|
+
const response = await this.network.sendExecutionPayloadEnvelopesByRoot(peerIdStr, [blockRoot]);
|
|
986
|
+
const envelope = response.at(0);
|
|
987
|
+
if (!envelope) {
|
|
988
|
+
throw new Error(`Missing execution payload envelope for root=${rootHex}`);
|
|
989
|
+
}
|
|
990
|
+
const receivedRootHex = toRootHex(envelope.message.beaconBlockRoot);
|
|
991
|
+
if (receivedRootHex !== rootHex) {
|
|
992
|
+
throw new Error(`Execution payload envelope root mismatch requested=${rootHex} received=${receivedRootHex}`);
|
|
993
|
+
}
|
|
994
|
+
return envelope;
|
|
995
|
+
}
|
|
996
|
+
async fetchPayloadColumns(peerMeta, payloadInput, missing) {
|
|
997
|
+
const { peerId: peerIdStr } = peerMeta;
|
|
998
|
+
const peerColumns = new Set(peerMeta.custodyColumns ?? []);
|
|
999
|
+
const requestedColumns = missing.filter((columnIndex) => peerColumns.has(columnIndex));
|
|
1000
|
+
if (requestedColumns.length === 0) {
|
|
1001
|
+
return [];
|
|
1002
|
+
}
|
|
1003
|
+
const columnSidecars = (await this.network.sendDataColumnSidecarsByRoot(peerIdStr, [
|
|
1004
|
+
{ blockRoot: fromHex(payloadInput.blockRootHex), columns: requestedColumns },
|
|
1005
|
+
]));
|
|
1006
|
+
if (columnSidecars.length === 0) {
|
|
1007
|
+
throw new Error(`No data column sidecars returned for payload root=${payloadInput.blockRootHex}`);
|
|
1008
|
+
}
|
|
1009
|
+
const requestedColumnsSet = new Set(requestedColumns);
|
|
1010
|
+
const extraColumns = columnSidecars.filter((columnSidecar) => !requestedColumnsSet.has(columnSidecar.index));
|
|
1011
|
+
if (extraColumns.length > 0) {
|
|
1012
|
+
throw new Error(`Received unexpected payload data columns indices=${prettyPrintIndices(extraColumns.map((column) => column.index))}`);
|
|
1013
|
+
}
|
|
1014
|
+
// PayloadEnvelopeInput already carries the block slot, root, and commitments, so reuse the
|
|
1015
|
+
// block-based Gloas validator rather than maintaining a second payload-specific variant.
|
|
1016
|
+
await validateGloasBlockDataColumnSidecars(payloadInput.slot, fromHex(payloadInput.blockRootHex), payloadInput.getBlobKzgCommitments(), columnSidecars, this.chain.metrics?.peerDas);
|
|
1017
|
+
return columnSidecars;
|
|
1018
|
+
}
|
|
427
1019
|
/**
|
|
428
1020
|
* From a set of shuffled peers:
|
|
429
1021
|
* - fetch the block
|
|
@@ -443,12 +1035,16 @@ export class BlockInputSync {
|
|
|
443
1035
|
this.metrics?.blockInputSync.fetchBegin.observe(this.chain.clock.secFromSlot(slot, fetchStartSec));
|
|
444
1036
|
}
|
|
445
1037
|
let i = 0;
|
|
1038
|
+
let deferredByRateLimit = false;
|
|
446
1039
|
while (i++ < this.getMaxDownloadAttempts()) {
|
|
447
1040
|
const pendingColumns = isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
|
|
448
1041
|
? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
|
|
449
1042
|
: defaultPendingColumns;
|
|
450
1043
|
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(pendingColumns, excludedPeers);
|
|
451
1044
|
if (peerMeta === null) {
|
|
1045
|
+
if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
|
|
1046
|
+
throw new UnknownBlockRateLimitedError(`Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`);
|
|
1047
|
+
}
|
|
452
1048
|
// no more peer with needed columns to try, throw error
|
|
453
1049
|
const message = `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: cannot find peer with needed columns=${prettyPrintIndices(Array.from(pendingColumns))}`;
|
|
454
1050
|
this.metrics?.blockInputSync.fetchTimeSec.observe({ result: FetchResult.FailureTriedAllPeers }, Date.now() / 1000 - fetchStartSec);
|
|
@@ -496,14 +1092,24 @@ export class BlockInputSync {
|
|
|
496
1092
|
else if (e instanceof RequestError) {
|
|
497
1093
|
// should look into req_resp metrics in this case
|
|
498
1094
|
downloadByRootMetrics?.error.inc({ code: "req_resp", client: peerClient });
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
1095
|
+
const rateLimitedUntilMs = getRateLimitedUntilMs(e);
|
|
1096
|
+
if (rateLimitedUntilMs !== null) {
|
|
1097
|
+
deferredByRateLimit = true;
|
|
1098
|
+
this.peerBalancer.onRateLimited(peerId, rateLimitedUntilMs);
|
|
1099
|
+
this.scheduleRateLimitBackoffRetry();
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
switch (e.type.code) {
|
|
1103
|
+
case RequestErrorCode.REQUEST_RATE_LIMITED:
|
|
1104
|
+
case RequestErrorCode.RESP_RATE_LIMITED:
|
|
1105
|
+
case RequestErrorCode.REQUEST_SELF_RATE_LIMITED:
|
|
1106
|
+
case RequestErrorCode.REQUEST_TIMEOUT:
|
|
1107
|
+
// do not exclude peer for these errors
|
|
1108
|
+
break;
|
|
1109
|
+
default:
|
|
1110
|
+
excludedPeers.add(peerId);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
507
1113
|
}
|
|
508
1114
|
}
|
|
509
1115
|
else {
|
|
@@ -529,6 +1135,9 @@ export class BlockInputSync {
|
|
|
529
1135
|
}
|
|
530
1136
|
} // end while loop over peers
|
|
531
1137
|
const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
|
|
1138
|
+
if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
|
|
1139
|
+
throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
|
|
1140
|
+
}
|
|
532
1141
|
if (!isPendingBlockInput(cacheItem)) {
|
|
533
1142
|
throw Error(`${message} No block and no data was found.`);
|
|
534
1143
|
}
|
|
@@ -582,6 +1191,25 @@ export class BlockInputSync {
|
|
|
582
1191
|
// Prune knownBadBlocks
|
|
583
1192
|
pruneSetToMax(this.knownBadBlocks, MAX_KNOWN_BAD_BLOCKS);
|
|
584
1193
|
}
|
|
1194
|
+
// Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
|
|
1195
|
+
removePendingPayloadAndDescendants(rootHex) {
|
|
1196
|
+
// Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
|
|
1197
|
+
// later validation/finalization logic decides when it can leave memory.
|
|
1198
|
+
this.pendingPayloads.delete(rootHex);
|
|
1199
|
+
const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
|
|
1200
|
+
this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
|
|
1201
|
+
for (const block of badPendingBlocks) {
|
|
1202
|
+
const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
1203
|
+
this.pendingBlocks.delete(descendantRootHex);
|
|
1204
|
+
this.pendingPayloads.delete(descendantRootHex);
|
|
1205
|
+
this.chain.seenBlockInputCache.prune(descendantRootHex);
|
|
1206
|
+
this.logger.debug("Removing pending descendant after invalid parent payload", {
|
|
1207
|
+
slot: getBlockInputSyncCacheItemSlot(block),
|
|
1208
|
+
blockRoot: descendantRootHex,
|
|
1209
|
+
parentPayloadRoot: rootHex,
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
585
1213
|
removeAllDescendants(block) {
|
|
586
1214
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
587
1215
|
const slot = getBlockInputSyncCacheItemSlot(block);
|
|
@@ -591,7 +1219,10 @@ export class BlockInputSync {
|
|
|
591
1219
|
for (const block of badPendingBlocks) {
|
|
592
1220
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
593
1221
|
this.pendingBlocks.delete(rootHex);
|
|
1222
|
+
this.pendingPayloads.delete(rootHex);
|
|
594
1223
|
this.chain.seenBlockInputCache.prune(rootHex);
|
|
1224
|
+
// Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
|
|
1225
|
+
// importBlock()-owned lifecycle.
|
|
595
1226
|
this.logger.debug("Removing bad/unknown/incomplete BlockInputSyncCacheItem", {
|
|
596
1227
|
slot,
|
|
597
1228
|
blockRoot: rootHex,
|
|
@@ -614,9 +1245,11 @@ export class BlockInputSync {
|
|
|
614
1245
|
export class UnknownBlockPeerBalancer {
|
|
615
1246
|
peersMeta;
|
|
616
1247
|
activeRequests;
|
|
1248
|
+
rateLimitedUntilByPeer;
|
|
617
1249
|
constructor() {
|
|
618
1250
|
this.peersMeta = new Map();
|
|
619
1251
|
this.activeRequests = new Map();
|
|
1252
|
+
this.rateLimitedUntilByPeer = new Map();
|
|
620
1253
|
}
|
|
621
1254
|
/** Trigger on each peer re-status */
|
|
622
1255
|
onPeerConnected(peerId, syncMeta) {
|
|
@@ -628,6 +1261,33 @@ export class UnknownBlockPeerBalancer {
|
|
|
628
1261
|
onPeerDisconnected(peerId) {
|
|
629
1262
|
this.peersMeta.delete(peerId);
|
|
630
1263
|
this.activeRequests.delete(peerId);
|
|
1264
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1265
|
+
}
|
|
1266
|
+
onRateLimited(peerId, rateLimitedUntilMs) {
|
|
1267
|
+
this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
|
|
1268
|
+
}
|
|
1269
|
+
getNextRateLimitRetryAt(pendingColumns, excludedPeers) {
|
|
1270
|
+
const now = Date.now();
|
|
1271
|
+
let retryAt = null;
|
|
1272
|
+
for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
|
|
1273
|
+
if (rateLimitedUntil <= now) {
|
|
1274
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
if (excludedPeers?.has(peerId)) {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const syncMeta = this.peersMeta.get(peerId);
|
|
1281
|
+
if (syncMeta === undefined) {
|
|
1282
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (pendingColumns !== undefined && !this.peerHasPendingColumns(syncMeta, pendingColumns)) {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
retryAt = Math.min(retryAt ?? rateLimitedUntil, rateLimitedUntil);
|
|
1289
|
+
}
|
|
1290
|
+
return retryAt;
|
|
631
1291
|
}
|
|
632
1292
|
/**
|
|
633
1293
|
* called from fetchBlockInput() where we only have block root and nothing else
|
|
@@ -667,6 +1327,7 @@ export class UnknownBlockPeerBalancer {
|
|
|
667
1327
|
return totalActiveRequests;
|
|
668
1328
|
}
|
|
669
1329
|
filterPeers(pendingDataColumns, excludedPeers) {
|
|
1330
|
+
const now = Date.now();
|
|
670
1331
|
let maxColumnCount = 0;
|
|
671
1332
|
const considerPeers = [];
|
|
672
1333
|
for (const [peerId, syncMeta] of this.peersMeta.entries()) {
|
|
@@ -674,11 +1335,21 @@ export class UnknownBlockPeerBalancer {
|
|
|
674
1335
|
// made request to this peer already
|
|
675
1336
|
continue;
|
|
676
1337
|
}
|
|
1338
|
+
const rateLimitedUntil = this.rateLimitedUntilByPeer.get(peerId);
|
|
1339
|
+
if (rateLimitedUntil !== undefined) {
|
|
1340
|
+
if (now < rateLimitedUntil) {
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
this.rateLimitedUntilByPeer.delete(peerId);
|
|
1344
|
+
}
|
|
677
1345
|
const activeRequests = this.activeRequests.get(peerId) ?? 0;
|
|
678
1346
|
if (activeRequests >= MAX_CONCURRENT_REQUESTS) {
|
|
679
1347
|
// should return peer with no more than MAX_CONCURRENT_REQUESTS active requests
|
|
680
1348
|
continue;
|
|
681
1349
|
}
|
|
1350
|
+
if (!this.peerHasPendingColumns(syncMeta, pendingDataColumns)) {
|
|
1351
|
+
continue;
|
|
1352
|
+
}
|
|
682
1353
|
if (pendingDataColumns.size === 0) {
|
|
683
1354
|
considerPeers.push({ peerId, columnCount: 0 });
|
|
684
1355
|
continue;
|
|
@@ -708,5 +1379,11 @@ export class UnknownBlockPeerBalancer {
|
|
|
708
1379
|
}
|
|
709
1380
|
return eligiblePeers;
|
|
710
1381
|
}
|
|
1382
|
+
peerHasPendingColumns(syncMeta, pendingDataColumns) {
|
|
1383
|
+
if (pendingDataColumns.size === 0) {
|
|
1384
|
+
return true;
|
|
1385
|
+
}
|
|
1386
|
+
return syncMeta.custodyColumns.some((column) => pendingDataColumns.has(column));
|
|
1387
|
+
}
|
|
711
1388
|
}
|
|
712
1389
|
//# sourceMappingURL=unknownBlock.js.map
|