@lodestar/beacon-node 1.47.0-dev.aa6eaee53a → 1.47.0-dev.aa8303e9a2
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 +2 -1
- 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 +13 -1
- package/lib/api/impl/beacon/pool/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/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/archiveStore.js +1 -1
- package/lib/chain/archiveStore/archiveStore.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/chain.d.ts +2 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +10 -2
- package/lib/chain/chain.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/deferredVoluntaryExitPool.d.ts +16 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.d.ts.map +1 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.js +57 -0
- package/lib/chain/opPools/deferredVoluntaryExitPool.js.map +1 -0
- package/lib/chain/opPools/index.d.ts +1 -0
- package/lib/chain/opPools/index.d.ts.map +1 -1
- package/lib/chain/opPools/index.js +1 -0
- package/lib/chain/opPools/index.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +23 -5
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +4 -2
- package/lib/chain/regen/interface.d.ts.map +1 -1
- package/lib/chain/regen/interface.js +2 -0
- package/lib/chain/regen/interface.js.map +1 -1
- package/lib/chain/regen/queued.d.ts +2 -2
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js +5 -5
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +28 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +121 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts +5 -1
- package/lib/chain/stateCache/fifoBlockStateCache.d.ts.map +1 -1
- package/lib/chain/stateCache/fifoBlockStateCache.js +6 -0
- package/lib/chain/stateCache/fifoBlockStateCache.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +47 -5
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +117 -7
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/stateCache/types.d.ts +3 -3
- package/lib/chain/stateCache/types.d.ts.map +1 -1
- package/lib/chain/validation/voluntaryExit.d.ts +9 -1
- package/lib/chain/validation/voluntaryExit.d.ts.map +1 -1
- package/lib/chain/validation/voluntaryExit.js +46 -1
- package/lib/chain/validation/voluntaryExit.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +24 -4
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +34 -2
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +5 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/node/deferredVoluntaryExitPublisher.d.ts +15 -0
- package/lib/node/deferredVoluntaryExitPublisher.d.ts.map +1 -0
- package/lib/node/deferredVoluntaryExitPublisher.js +38 -0
- package/lib/node/deferredVoluntaryExitPublisher.js.map +1 -0
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +2 -0
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/types.d.ts +32 -0
- package/lib/sync/types.d.ts.map +1 -1
- package/lib/sync/types.js +33 -0
- package/lib/sync/types.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +95 -51
- 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 +31 -31
- package/src/api/impl/beacon/blocks/index.ts +2 -1
- package/src/api/impl/beacon/pool/index.ts +15 -1
- package/src/api/impl/lodestar/index.ts +1 -1
- package/src/api/rest/base.ts +7 -0
- package/src/chain/archiveStore/archiveStore.ts +1 -1
- package/src/chain/blocks/payloadEnvelopeInput/payloadEnvelopeInput.ts +8 -2
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +17 -4
- package/src/chain/chain.ts +10 -1
- package/src/chain/interface.ts +2 -0
- package/src/chain/opPools/deferredVoluntaryExitPool.ts +65 -0
- package/src/chain/opPools/index.ts +1 -0
- package/src/chain/prepareNextSlot.ts +23 -5
- package/src/chain/regen/interface.ts +4 -2
- package/src/chain/regen/queued.ts +5 -5
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +143 -14
- package/src/chain/stateCache/fifoBlockStateCache.ts +8 -1
- package/src/chain/stateCache/persistentCheckpointsCache.ts +133 -8
- package/src/chain/stateCache/types.ts +3 -3
- package/src/chain/validation/voluntaryExit.ts +59 -2
- package/src/metrics/metrics/lodestar.ts +41 -6
- package/src/network/processor/gossipHandlers.ts +5 -1
- package/src/node/deferredVoluntaryExitPublisher.ts +54 -0
- package/src/node/nodejs.ts +3 -0
- package/src/sync/types.ts +33 -0
- package/src/sync/unknownBlock.ts +105 -55
- package/src/sync/utils/downloadByRange.ts +21 -12
- package/src/sync/utils/downloadByRoot.ts +3 -1
|
@@ -4,7 +4,10 @@ import {ArchiveStoreTask} from "../../chain/archiveStore/archiveStore.js";
|
|
|
4
4
|
import {FrequencyStateArchiveStep} from "../../chain/archiveStore/strategies/frequencyStateArchiveStrategy.js";
|
|
5
5
|
import {BlockInputSource} from "../../chain/blocks/blockInput/index.js";
|
|
6
6
|
import {PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
PayloadEnvelopeInputPruneReason,
|
|
9
|
+
PayloadEnvelopeInputSource,
|
|
10
|
+
} from "../../chain/blocks/payloadEnvelopeInput/index.js";
|
|
8
11
|
import {JobQueueItemType} from "../../chain/bls/index.js";
|
|
9
12
|
import {AttestationErrorCode, BlockErrorCode} from "../../chain/errors/index.js";
|
|
10
13
|
import {
|
|
@@ -21,7 +24,7 @@ import {ExecutionPayloadStatus} from "../../execution/index.js";
|
|
|
21
24
|
import {GossipType} from "../../network/index.js";
|
|
22
25
|
import {CannotAcceptWorkReason, ReprocessRejectReason} from "../../network/processor/index.js";
|
|
23
26
|
import {BackfillSyncMethod} from "../../sync/backfill/backfill.js";
|
|
24
|
-
import {PendingBlockType} from "../../sync/types.js";
|
|
27
|
+
import {DroppedItemReason, PendingBlockType} from "../../sync/types.js";
|
|
25
28
|
import {PeerSyncType, RangeSyncType} from "../../sync/utils/remoteSyncType.js";
|
|
26
29
|
import {AllocSource} from "../../util/bufferPool.js";
|
|
27
30
|
import {DataColumnReconstructionCode} from "../../util/dataColumns.js";
|
|
@@ -677,9 +680,15 @@ export function createLodestarMetrics(
|
|
|
677
680
|
name: "lodestar_sync_unknown_block_downloaded_blocks_error_total",
|
|
678
681
|
help: "Total number of downloaded blocks errors in UnknownBlockSync",
|
|
679
682
|
}),
|
|
680
|
-
removedBlocks: register.gauge({
|
|
683
|
+
removedBlocks: register.gauge<{reason: DroppedItemReason}>({
|
|
681
684
|
name: "lodestar_sync_unknown_block_removed_blocks_total",
|
|
682
|
-
help: "Total
|
|
685
|
+
help: "Total pending blocks dropped from BlockInputSync without completing, by reason",
|
|
686
|
+
labelNames: ["reason"],
|
|
687
|
+
}),
|
|
688
|
+
removedPayloads: register.gauge<{reason: DroppedItemReason}>({
|
|
689
|
+
name: "lodestar_sync_unknown_block_removed_payloads_total",
|
|
690
|
+
help: "Total pending payloads dropped from BlockInputSync without completing, by reason",
|
|
691
|
+
labelNames: ["reason"],
|
|
683
692
|
}),
|
|
684
693
|
elapsedTimeTillReceived: register.histogram({
|
|
685
694
|
name: "lodestar_sync_unknown_block_elapsed_time_till_received",
|
|
@@ -1225,6 +1234,12 @@ export function createLodestarMetrics(
|
|
|
1225
1234
|
name: "lodestar_oppool_attester_slashing_pool_size",
|
|
1226
1235
|
help: "Current size of the AttesterSlashingPool",
|
|
1227
1236
|
}),
|
|
1237
|
+
deferredVoluntaryExitPool: {
|
|
1238
|
+
size: register.gauge({
|
|
1239
|
+
name: "lodestar_oppool_deferred_voluntary_exit_pool_size",
|
|
1240
|
+
help: "Current size of the DeferredVoluntaryExitPool",
|
|
1241
|
+
}),
|
|
1242
|
+
},
|
|
1228
1243
|
proposerSlashingPoolSize: register.gauge({
|
|
1229
1244
|
name: "lodestar_oppool_proposer_slashing_pool_size",
|
|
1230
1245
|
help: "Current size of the ProposerSlashingPool",
|
|
@@ -1429,6 +1444,16 @@ export function createLodestarMetrics(
|
|
|
1429
1444
|
help: "Checkpoint state cache size",
|
|
1430
1445
|
labelNames: ["type"],
|
|
1431
1446
|
}),
|
|
1447
|
+
persistentTierEpochs: register.gauge<{tier: number}>({
|
|
1448
|
+
name: "lodestar_cp_state_cache_persistent_tier_epochs",
|
|
1449
|
+
help: "Number of epoch keys retained in each on-disk checkpoint state retention tier",
|
|
1450
|
+
labelNames: ["tier"],
|
|
1451
|
+
}),
|
|
1452
|
+
persistentTierStates: register.gauge<{tier: number}>({
|
|
1453
|
+
name: "lodestar_cp_state_cache_persistent_tier_states",
|
|
1454
|
+
help: "Number of persisted checkpoint states retained in each on-disk retention tier",
|
|
1455
|
+
labelNames: ["tier"],
|
|
1456
|
+
}),
|
|
1432
1457
|
reads: register.avgMinMax({
|
|
1433
1458
|
name: "lodestar_cp_state_epoch_reads",
|
|
1434
1459
|
help: "Avg min max of all state cache items total read count",
|
|
@@ -1643,9 +1668,15 @@ export function createLodestarMetrics(
|
|
|
1643
1668
|
name: "lodestar_seen_payload_envelope_input_cache_serialized_object_refs",
|
|
1644
1669
|
help: "Number of serialized-cache object refs retained by cached PayloadEnvelopeInputs",
|
|
1645
1670
|
}),
|
|
1646
|
-
created: register.counter({
|
|
1671
|
+
created: register.counter<{source: PayloadEnvelopeInputSource}>({
|
|
1647
1672
|
name: "lodestar_seen_payload_envelope_input_cache_items_created_total",
|
|
1648
|
-
help: "Number of PayloadEnvelopeInputs created",
|
|
1673
|
+
help: "Number of PayloadEnvelopeInputs created by source",
|
|
1674
|
+
labelNames: ["source"],
|
|
1675
|
+
}),
|
|
1676
|
+
pruned: register.counter<{reason: PayloadEnvelopeInputPruneReason}>({
|
|
1677
|
+
name: "lodestar_seen_payload_envelope_input_cache_items_pruned_total",
|
|
1678
|
+
help: "Number of PayloadEnvelopeInputs evicted by reason",
|
|
1679
|
+
labelNames: ["reason"],
|
|
1649
1680
|
}),
|
|
1650
1681
|
},
|
|
1651
1682
|
},
|
|
@@ -1755,6 +1786,10 @@ export function createLodestarMetrics(
|
|
|
1755
1786
|
name: "lodestar_precompute_next_epoch_transition_waste_total",
|
|
1756
1787
|
help: "Total number of precomputing next epoch transition wasted",
|
|
1757
1788
|
}),
|
|
1789
|
+
predictedReorg: register.counter({
|
|
1790
|
+
name: "lodestar_precompute_next_epoch_transition_predicted_reorg_total",
|
|
1791
|
+
help: "Predicted epoch-boundary reorgs where the strong parent was dialed instead of the weak head",
|
|
1792
|
+
}),
|
|
1758
1793
|
duration: register.histogram({
|
|
1759
1794
|
name: "lodestar_precompute_next_epoch_transition_duration_seconds",
|
|
1760
1795
|
help: "Duration of precomputeNextEpochTransition, including epoch transition and hashTreeRoot",
|
|
@@ -196,7 +196,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
196
196
|
forkName: fork,
|
|
197
197
|
sampledColumns: chain.custodyConfig.sampledColumns,
|
|
198
198
|
custodyColumns: chain.custodyConfig.custodyColumns,
|
|
199
|
-
|
|
199
|
+
seenTimestampSec,
|
|
200
|
+
source: PayloadEnvelopeInputSource.gossip,
|
|
200
201
|
});
|
|
201
202
|
}
|
|
202
203
|
|
|
@@ -640,6 +641,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
640
641
|
metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
|
|
641
642
|
|
|
642
643
|
if (isForkPostGloas(blockInput.forkName)) {
|
|
644
|
+
// we should have the payloadInput in the seen cache so no need getOrReload() here
|
|
643
645
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockInput.blockRootHex);
|
|
644
646
|
// This payloadInput should have been created just after gossip validation
|
|
645
647
|
if (!payloadInput) {
|
|
@@ -1189,6 +1191,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1189
1191
|
chain.validatorMonitor?.registerExecutionPayloadEnvelope(OpSource.gossip, delaySec, signedEnvelope);
|
|
1190
1192
|
|
|
1191
1193
|
const blockRootHex = toRootHex(envelope.beaconBlockRoot);
|
|
1194
|
+
// a gossip payload cannot be `MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE` slots after the block
|
|
1195
|
+
// otherwise it'll get to UnknownBlockInput flow
|
|
1192
1196
|
const payloadInput = chain.seenPayloadEnvelopeInputCache.get(blockRootHex);
|
|
1193
1197
|
|
|
1194
1198
|
if (!payloadInput) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {routes} from "@lodestar/api";
|
|
2
|
+
import {Logger} from "@lodestar/utils";
|
|
3
|
+
import {IBeaconChain} from "../chain/index.js";
|
|
4
|
+
import {INetwork} from "../network/index.js";
|
|
5
|
+
import {ClockEvent} from "../util/clock.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* On every epoch, drain the deferred voluntary exit pool and publish the exits that have become
|
|
9
|
+
* processable. Lives at the node layer rather than inside `BeaconChain` because publishing needs
|
|
10
|
+
* `network`, which the chain does not (and should not) depend on.
|
|
11
|
+
*/
|
|
12
|
+
export function startDeferredVoluntaryExitPublisher({
|
|
13
|
+
chain,
|
|
14
|
+
network,
|
|
15
|
+
logger,
|
|
16
|
+
signal,
|
|
17
|
+
}: {
|
|
18
|
+
chain: IBeaconChain;
|
|
19
|
+
network: INetwork;
|
|
20
|
+
logger: Logger;
|
|
21
|
+
signal: AbortSignal;
|
|
22
|
+
}): void {
|
|
23
|
+
const onEpoch = async (): Promise<void> => {
|
|
24
|
+
try {
|
|
25
|
+
// Use the cached head state directly rather than getHeadStateAtCurrentEpoch, which would regen
|
|
26
|
+
// the head forward to the current epoch. At init from a stale db-finalized state that regen can
|
|
27
|
+
// be many epoch transitions. The head state is fine here: this is a best-effort drain that
|
|
28
|
+
// re-runs every epoch, so a just-processable exit is republished next tick.
|
|
29
|
+
const state = chain.getHeadState();
|
|
30
|
+
const exits = chain.deferredVoluntaryExitPool.drainProcessableExits(state);
|
|
31
|
+
for (const exit of exits) {
|
|
32
|
+
try {
|
|
33
|
+
chain.opPool.insertVoluntaryExit(exit);
|
|
34
|
+
chain.emitter.emit(routes.events.EventType.voluntaryExit, exit);
|
|
35
|
+
await network.publishVoluntaryExit(exit);
|
|
36
|
+
logger.info("Voluntary exit successfully published for validator", {
|
|
37
|
+
validatorIndex: exit.message.validatorIndex,
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
logger.warn(
|
|
41
|
+
"Failed to publish deferred voluntary exit",
|
|
42
|
+
{validatorIndex: exit.message.validatorIndex},
|
|
43
|
+
e as Error
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
logger.warn("Failed to drain deferred voluntary exit pool", {}, e as Error);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
chain.clock.addListener(ClockEvent.epoch, onEpoch);
|
|
53
|
+
signal.addEventListener("abort", () => chain.clock.removeListener(ClockEvent.epoch, onEpoch), {once: true});
|
|
54
|
+
}
|
package/src/node/nodejs.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {Network, getReqRespHandlers} from "../network/index.js";
|
|
|
22
22
|
import {BackfillSync} from "../sync/backfill/index.js";
|
|
23
23
|
import {BeaconSync, IBeaconSync} from "../sync/index.js";
|
|
24
24
|
import {Clock} from "../util/clock.js";
|
|
25
|
+
import {startDeferredVoluntaryExitPublisher} from "./deferredVoluntaryExitPublisher.js";
|
|
25
26
|
import {runNodeNotifier} from "./notifier.js";
|
|
26
27
|
import {IBeaconNodeOptions} from "./options.js";
|
|
27
28
|
|
|
@@ -335,6 +336,8 @@ export class BeaconNode {
|
|
|
335
336
|
|
|
336
337
|
void runNodeNotifier({network, chain, sync, config, logger, signal});
|
|
337
338
|
|
|
339
|
+
startDeferredVoluntaryExitPublisher({chain, network, logger, signal});
|
|
340
|
+
|
|
338
341
|
return new BeaconNode({
|
|
339
342
|
opts,
|
|
340
343
|
config,
|
package/src/sync/types.ts
CHANGED
|
@@ -36,6 +36,39 @@ export enum PendingBlockInputStatus {
|
|
|
36
36
|
processing = "processing",
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Why a pending item was dropped from BlockInputSync WITHOUT completing.
|
|
41
|
+
* The happy path — a successfully imported/processed block or payload — is removed by the import
|
|
42
|
+
* flow (onPayloadImported, processReadyBlock/processPayload success) and is deliberately NOT tracked
|
|
43
|
+
* here
|
|
44
|
+
*/
|
|
45
|
+
export enum DroppedItemReason {
|
|
46
|
+
/** prune: item's own slot is below the finalized slot (routine cleanup), or a downloaded block landed at/below finality */
|
|
47
|
+
belowFinalized = "below_finalized",
|
|
48
|
+
/** prune: slot-less, unresolvable payload evicted after PRUNE_UNRESOLVED_SLOT_EPOCHS */
|
|
49
|
+
agedOut = "aged_out",
|
|
50
|
+
/** pruneSetToMax evicted the oldest entry because the cache hit its capacity (DoS guard) */
|
|
51
|
+
capacity = "capacity",
|
|
52
|
+
/** descendant removed because an ancestor was invalid/removed */
|
|
53
|
+
invalidParent = "invalid_parent",
|
|
54
|
+
/** transient execution-engine error removal (EXECUTION_ENGINE_ERROR) */
|
|
55
|
+
elError = "el_error",
|
|
56
|
+
/** the execution engine declared the item invalid (EXECUTION_ENGINE_INVALID) */
|
|
57
|
+
elInvalid = "el_invalid",
|
|
58
|
+
/** a block failed our own checks (not correct w.r.t. our chain) */
|
|
59
|
+
invalidBlock = "invalid_block",
|
|
60
|
+
/** a block declares a parent payload hash that conflicts with the parent's actual payload (gloas) */
|
|
61
|
+
invalidParentPayload = "invalid_parent_payload",
|
|
62
|
+
/** a payload envelope failed gossip verification (ENVELOPE_VERIFICATION_ERROR) */
|
|
63
|
+
invalidEnvelope = "invalid_envelope",
|
|
64
|
+
/** a payload had an invalid signature (INVALID_SIGNATURE) */
|
|
65
|
+
invalidSignature = "invalid_signature",
|
|
66
|
+
/** a block root was given up on after exhausting download attempts (data could not be fetched) */
|
|
67
|
+
unavailable = "unavailable",
|
|
68
|
+
/** an unexpected / unhandled error code led to removal */
|
|
69
|
+
unknown = "unknown",
|
|
70
|
+
}
|
|
71
|
+
|
|
39
72
|
export enum PendingPayloadInputStatus {
|
|
40
73
|
pending = "pending",
|
|
41
74
|
fetching = "fetching",
|
package/src/sync/unknownBlock.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
3
|
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
4
|
-
import {ForkSeq, isForkPostGloas} from "@lodestar/params";
|
|
4
|
+
import {ForkSeq, SLOTS_PER_EPOCH, isForkPostGloas} from "@lodestar/params";
|
|
5
5
|
import {RequestError, RequestErrorCode} from "@lodestar/reqresp";
|
|
6
6
|
import {computeTimeAtSlot} from "@lodestar/state-transition";
|
|
7
7
|
import {RootHex, Slot, gloas} from "@lodestar/types";
|
|
@@ -25,6 +25,7 @@ import {MAX_CONCURRENT_REQUESTS} from "./constants.js";
|
|
|
25
25
|
import {SyncOptions} from "./options.js";
|
|
26
26
|
import {
|
|
27
27
|
BlockInputSyncCacheItem,
|
|
28
|
+
DroppedItemReason,
|
|
28
29
|
PayloadSyncCacheItem,
|
|
29
30
|
PendingBlockInput,
|
|
30
31
|
PendingBlockInputStatus,
|
|
@@ -48,6 +49,10 @@ import {getRateLimitedUntilMs} from "./utils/rateLimit.js";
|
|
|
48
49
|
const MAX_ATTEMPTS_PER_BLOCK = 5;
|
|
49
50
|
const MAX_KNOWN_BAD_BLOCKS = 500;
|
|
50
51
|
const MAX_PENDING_BLOCKS = 100;
|
|
52
|
+
/**
|
|
53
|
+
* A pending payload without slot can only stay at most 2 epochs.
|
|
54
|
+
*/
|
|
55
|
+
const PRUNE_UNRESOLVED_SLOT_EPOCHS = 2;
|
|
51
56
|
|
|
52
57
|
type AdvancePendingBlockResult =
|
|
53
58
|
| "ready"
|
|
@@ -255,7 +260,7 @@ export class BlockInputSync {
|
|
|
255
260
|
parentRoot: missingDependency.parentRootHex,
|
|
256
261
|
parentBlockHash: missingDependency.parentBlockHashHex,
|
|
257
262
|
});
|
|
258
|
-
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
263
|
+
this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
|
|
259
264
|
}
|
|
260
265
|
return;
|
|
261
266
|
}
|
|
@@ -295,28 +300,37 @@ export class BlockInputSync {
|
|
|
295
300
|
// Use the actual finalized block slot, not computeStartSlotAtEpoch(checkpoint.epoch)
|
|
296
301
|
// because the payload at the finalized block is not finalized
|
|
297
302
|
const finalizedSlot = this.chain.forkChoice.getFinalizedBlock().slot;
|
|
303
|
+
const nowSec = Date.now() / 1000;
|
|
304
|
+
const maxSecInCache = (PRUNE_UNRESOLVED_SLOT_EPOCHS * SLOTS_PER_EPOCH * this.config.SLOT_DURATION_MS) / 1000;
|
|
298
305
|
|
|
299
|
-
let
|
|
306
|
+
let belowFinalizedPayloads = 0;
|
|
307
|
+
let agedOutPayloads = 0;
|
|
300
308
|
let unresolvedSlotPayloads = 0;
|
|
309
|
+
|
|
301
310
|
for (const [rootHex, payload] of this.pendingPayloads) {
|
|
302
311
|
// Last-effort resolve for a slot-less PendingPayloadRootHex, and store it back for later passes.
|
|
303
312
|
if (!isPendingPayloadInput(payload) && !isPendingPayloadEnvelope(payload) && payload.slot === undefined) {
|
|
304
313
|
payload.slot = this.resolvePayloadSlot(rootHex);
|
|
314
|
+
if (payload.slot === undefined) {
|
|
315
|
+
// Still unresolvable: age it out so the MAX_PENDING_BLOCKS cap cannot stay saturated.
|
|
316
|
+
if (nowSec - payload.timeAddedSec > maxSecInCache) {
|
|
317
|
+
this.pendingPayloads.delete(rootHex);
|
|
318
|
+
agedOutPayloads++;
|
|
319
|
+
} else {
|
|
320
|
+
unresolvedSlotPayloads++;
|
|
321
|
+
this.logger.debug("BlockInputSync.pruneFinalized: pending payload with unresolved slot", {
|
|
322
|
+
root: rootHex,
|
|
323
|
+
finalizedSlot,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
305
328
|
}
|
|
306
329
|
|
|
307
330
|
const slot = getPayloadSyncCacheItemSlot(payload);
|
|
308
|
-
if (typeof slot
|
|
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) {
|
|
331
|
+
if (typeof slot === "number" && slot < finalizedSlot) {
|
|
318
332
|
this.pendingPayloads.delete(rootHex);
|
|
319
|
-
|
|
333
|
+
belowFinalizedPayloads++;
|
|
320
334
|
}
|
|
321
335
|
}
|
|
322
336
|
|
|
@@ -329,14 +343,24 @@ export class BlockInputSync {
|
|
|
329
343
|
}
|
|
330
344
|
}
|
|
331
345
|
|
|
332
|
-
if (
|
|
333
|
-
this.metrics?.blockInputSync.
|
|
346
|
+
if (belowFinalizedPayloads > 0) {
|
|
347
|
+
this.metrics?.blockInputSync.removedPayloads.inc(
|
|
348
|
+
{reason: DroppedItemReason.belowFinalized},
|
|
349
|
+
belowFinalizedPayloads
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
if (agedOutPayloads > 0) {
|
|
353
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.agedOut}, agedOutPayloads);
|
|
354
|
+
}
|
|
355
|
+
if (deletedBlocks > 0) {
|
|
356
|
+
this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.belowFinalized}, deletedBlocks);
|
|
334
357
|
}
|
|
335
358
|
|
|
336
359
|
this.logger.debug("BlockInputSync.pruneFinalized dropped pre-finalized pending items", {
|
|
337
360
|
finalizedSlot,
|
|
338
361
|
finalizedRoot: checkpoint.rootHex,
|
|
339
|
-
|
|
362
|
+
belowFinalizedPayloads,
|
|
363
|
+
agedOutPayloads,
|
|
340
364
|
deletedBlocks,
|
|
341
365
|
unresolvedSlotPayloads,
|
|
342
366
|
});
|
|
@@ -369,6 +393,7 @@ export class BlockInputSync {
|
|
|
369
393
|
// Limit pending blocks to prevent DOS attacks that cause OOM
|
|
370
394
|
const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
|
|
371
395
|
if (prunedItemCount > 0) {
|
|
396
|
+
this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
|
|
372
397
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
|
|
373
398
|
}
|
|
374
399
|
return added;
|
|
@@ -403,6 +428,7 @@ export class BlockInputSync {
|
|
|
403
428
|
// Limit pending blocks to prevent DOS attacks that cause OOM
|
|
404
429
|
const prunedItemCount = pruneSetToMax(this.pendingBlocks, this.maxPendingBlocks);
|
|
405
430
|
if (prunedItemCount > 0) {
|
|
431
|
+
this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
|
|
406
432
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingBlocks`);
|
|
407
433
|
}
|
|
408
434
|
};
|
|
@@ -427,6 +453,14 @@ export class BlockInputSync {
|
|
|
427
453
|
root: rootHex,
|
|
428
454
|
peerIdStr: peerIdStr ?? "unknown peer",
|
|
429
455
|
});
|
|
456
|
+
} else if (
|
|
457
|
+
// update a previously slot-less entry once a slot becomes available
|
|
458
|
+
slot !== undefined &&
|
|
459
|
+
!isPendingPayloadInput(pendingPayload) &&
|
|
460
|
+
!isPendingPayloadEnvelope(pendingPayload) &&
|
|
461
|
+
pendingPayload.slot === undefined
|
|
462
|
+
) {
|
|
463
|
+
pendingPayload.slot = slot;
|
|
430
464
|
}
|
|
431
465
|
|
|
432
466
|
if (peerIdStr) {
|
|
@@ -435,6 +469,7 @@ export class BlockInputSync {
|
|
|
435
469
|
|
|
436
470
|
const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
|
|
437
471
|
if (prunedItemCount > 0) {
|
|
472
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
|
|
438
473
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
|
|
439
474
|
}
|
|
440
475
|
return added;
|
|
@@ -488,6 +523,7 @@ export class BlockInputSync {
|
|
|
488
523
|
|
|
489
524
|
const prunedItemCount = pruneSetToMax(this.pendingPayloads, this.maxPendingBlocks);
|
|
490
525
|
if (prunedItemCount > 0) {
|
|
526
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.capacity}, prunedItemCount);
|
|
491
527
|
this.logger.verbose(`Pruned ${prunedItemCount} items from BlockInputSync.pendingPayloads`);
|
|
492
528
|
}
|
|
493
529
|
};
|
|
@@ -544,15 +580,7 @@ export class BlockInputSync {
|
|
|
544
580
|
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
545
581
|
}
|
|
546
582
|
|
|
547
|
-
|
|
548
|
-
if (parentPayloadInput) {
|
|
549
|
-
if (parentPayloadInput.getBlockHashHex() === parentBlockHashHex) {
|
|
550
|
-
return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return {kind: "invalidParentPayload", parentRootHex, parentBlockHashHex};
|
|
554
|
-
}
|
|
555
|
-
|
|
583
|
+
// Parent is in fork choice but its payload `parentBlockHashHex` isn't revealed yet
|
|
556
584
|
return {kind: "parentPayload", rootHex: parentRootHex, slot: parentBlock.slot};
|
|
557
585
|
}
|
|
558
586
|
|
|
@@ -590,7 +618,7 @@ export class BlockInputSync {
|
|
|
590
618
|
parentRoot: missingDependency.parentRootHex,
|
|
591
619
|
parentBlockHash: missingDependency.parentBlockHashHex,
|
|
592
620
|
});
|
|
593
|
-
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
621
|
+
this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidParentPayload);
|
|
594
622
|
return "removed";
|
|
595
623
|
}
|
|
596
624
|
}
|
|
@@ -813,7 +841,7 @@ export class BlockInputSync {
|
|
|
813
841
|
...logCtx2,
|
|
814
842
|
finalizedSlot,
|
|
815
843
|
});
|
|
816
|
-
this.removeAndDownScoreAllDescendants(block);
|
|
844
|
+
this.removeAndDownScoreAllDescendants(block, DroppedItemReason.belowFinalized);
|
|
817
845
|
} else {
|
|
818
846
|
this.onUnknownBlockRoot({rootHex: pending.blockInput.parentRootHex, source: BlockInputSource.byRoot});
|
|
819
847
|
}
|
|
@@ -830,7 +858,7 @@ export class BlockInputSync {
|
|
|
830
858
|
|
|
831
859
|
this.metrics?.blockInputSync.downloadedBlocksError.inc();
|
|
832
860
|
this.logger.debug("Ignoring unknown block root after many failed downloads", logCtx, res.err);
|
|
833
|
-
this.removeAndDownScoreAllDescendants(block);
|
|
861
|
+
this.removeAndDownScoreAllDescendants(block, DroppedItemReason.unavailable);
|
|
834
862
|
}
|
|
835
863
|
}
|
|
836
864
|
|
|
@@ -894,9 +922,9 @@ export class BlockInputSync {
|
|
|
894
922
|
this.pendingBlocks.delete(blockRootHex);
|
|
895
923
|
|
|
896
924
|
if (isForkPostGloas(fork)) {
|
|
897
|
-
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.
|
|
925
|
+
const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(blockRootHex);
|
|
898
926
|
if (!payloadInput) {
|
|
899
|
-
this.logger.
|
|
927
|
+
this.logger.debug("PayloadEnvelopeInput unavailable during unknown sync processReadyBlock()", logCtx);
|
|
900
928
|
} else {
|
|
901
929
|
// Similar to the gossip path: immediately attempt fetch of data columns from the execution engine.
|
|
902
930
|
// The bid already carries the kzg commitments, so there is no reason to wait for the payload to arrive.
|
|
@@ -938,19 +966,19 @@ export class BlockInputSync {
|
|
|
938
966
|
case BlockErrorCode.EXECUTION_ENGINE_ERROR:
|
|
939
967
|
// Removing the block(s) without penalizing the peers, hoping for EL to
|
|
940
968
|
// recover on a latter download + verify attempt.
|
|
941
|
-
this.removeAllDescendants(pendingBlock);
|
|
969
|
+
this.removeAllDescendants(pendingBlock, DroppedItemReason.elError, DroppedItemReason.elError);
|
|
942
970
|
break;
|
|
943
971
|
|
|
944
972
|
case BlockErrorCode.EXECUTION_ENGINE_INVALID:
|
|
945
973
|
// the peer served a bad block
|
|
946
974
|
this.logger.debug("Execution engine rejected block from unknown parent sync", errorData, res.err);
|
|
947
|
-
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
975
|
+
this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.elInvalid);
|
|
948
976
|
break;
|
|
949
977
|
|
|
950
978
|
default:
|
|
951
979
|
// Block is not correct with respect to our chain. Log error loudly
|
|
952
980
|
this.logger.debug("Error processing block from unknown parent sync", errorData, res.err);
|
|
953
|
-
this.removeAndDownScoreAllDescendants(pendingBlock);
|
|
981
|
+
this.removeAndDownScoreAllDescendants(pendingBlock, DroppedItemReason.invalidBlock);
|
|
954
982
|
}
|
|
955
983
|
}
|
|
956
984
|
|
|
@@ -969,12 +997,12 @@ export class BlockInputSync {
|
|
|
969
997
|
*/
|
|
970
998
|
private async reconcilePayloadEnvelope(pendingPayload: PendingPayloadEnvelope): Promise<void> {
|
|
971
999
|
const rootHex = getPayloadSyncCacheItemRootHex(pendingPayload);
|
|
1000
|
+
|
|
972
1001
|
if (this.chain.forkChoice.hasPayloadHexUnsafe(rootHex)) {
|
|
973
1002
|
this.pendingPayloads.delete(rootHex);
|
|
974
1003
|
return;
|
|
975
1004
|
}
|
|
976
1005
|
|
|
977
|
-
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(rootHex);
|
|
978
1006
|
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
979
1007
|
// Block not in fork choice yet. payloadInput may be seeded from the block body during download, so a
|
|
980
1008
|
// non-null payloadInput does not imply the block is imported; defer regardless and pull the block first.
|
|
@@ -990,8 +1018,9 @@ export class BlockInputSync {
|
|
|
990
1018
|
return;
|
|
991
1019
|
}
|
|
992
1020
|
|
|
1021
|
+
const payloadInput = await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
993
1022
|
if (!payloadInput) {
|
|
994
|
-
this.logger.debug("
|
|
1023
|
+
this.logger.debug("PayloadEnvelopeInput not yet reloadable for imported block, will retry", {
|
|
995
1024
|
root: rootHex,
|
|
996
1025
|
});
|
|
997
1026
|
return;
|
|
@@ -1153,17 +1182,27 @@ export class BlockInputSync {
|
|
|
1153
1182
|
break;
|
|
1154
1183
|
|
|
1155
1184
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1185
|
+
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1186
|
+
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
|
|
1187
|
+
break;
|
|
1188
|
+
|
|
1156
1189
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1190
|
+
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1191
|
+
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
|
|
1192
|
+
break;
|
|
1193
|
+
|
|
1157
1194
|
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1158
1195
|
// TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
|
|
1159
1196
|
// reintroducing envelope replacement / recreation flows.
|
|
1160
1197
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1161
|
-
this.removePendingPayloadAndDescendants(rootHex);
|
|
1198
|
+
this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
|
|
1162
1199
|
break;
|
|
1163
1200
|
|
|
1164
1201
|
default:
|
|
1165
1202
|
this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
|
|
1166
|
-
this.pendingPayloads.delete(rootHex)
|
|
1203
|
+
if (this.pendingPayloads.delete(rootHex)) {
|
|
1204
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.unknown}, 1);
|
|
1205
|
+
}
|
|
1167
1206
|
}
|
|
1168
1207
|
return;
|
|
1169
1208
|
}
|
|
@@ -1187,7 +1226,7 @@ export class BlockInputSync {
|
|
|
1187
1226
|
let slot = getPayloadSyncCacheItemSlot(cacheItem);
|
|
1188
1227
|
let payloadInput = isPendingPayloadInput(cacheItem)
|
|
1189
1228
|
? cacheItem.payloadInput
|
|
1190
|
-
: this.chain.seenPayloadEnvelopeInputCache.
|
|
1229
|
+
: await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1191
1230
|
let envelope = payloadInput?.hasPayloadEnvelope() ? payloadInput.getPayloadEnvelope() : undefined;
|
|
1192
1231
|
|
|
1193
1232
|
let i = 0;
|
|
@@ -1220,7 +1259,7 @@ export class BlockInputSync {
|
|
|
1220
1259
|
slot = envelope.message.payload.slotNumber;
|
|
1221
1260
|
}
|
|
1222
1261
|
|
|
1223
|
-
payloadInput ??= this.chain.seenPayloadEnvelopeInputCache.
|
|
1262
|
+
payloadInput ??= await this.chain.seenPayloadEnvelopeInputCache.getOrReload(rootHex);
|
|
1224
1263
|
if (!this.chain.forkChoice.hasBlockHex(rootHex)) {
|
|
1225
1264
|
// Block not in fork choice yet. Validating now would throw BLOCK_ROOT_UNKNOWN, so keep the downloaded
|
|
1226
1265
|
// envelope and wait for the block body; reconcilePayloadEnvelope validates once the block lands.
|
|
@@ -1553,9 +1592,9 @@ export class BlockInputSync {
|
|
|
1553
1592
|
* Downscore all peers that have referenced any of this bad blocks. May report peers multiple times if they have
|
|
1554
1593
|
* referenced more than one bad block.
|
|
1555
1594
|
*/
|
|
1556
|
-
private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem): void {
|
|
1595
|
+
private removeAndDownScoreAllDescendants(block: BlockInputSyncCacheItem, headReason: DroppedItemReason): void {
|
|
1557
1596
|
// Get all blocks that are a descendant of this one
|
|
1558
|
-
const badPendingBlocks = this.removeAllDescendants(block);
|
|
1597
|
+
const badPendingBlocks = this.removeAllDescendants(block, headReason, DroppedItemReason.invalidParent);
|
|
1559
1598
|
// just console log and do not penalize on pending/bad blocks for debugging
|
|
1560
1599
|
// console.log("removeAndDownscoreAllDescendants", {block});
|
|
1561
1600
|
|
|
@@ -1582,18 +1621,22 @@ export class BlockInputSync {
|
|
|
1582
1621
|
}
|
|
1583
1622
|
|
|
1584
1623
|
// Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
|
|
1585
|
-
private removePendingPayloadAndDescendants(rootHex: RootHex): void {
|
|
1624
|
+
private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
|
|
1586
1625
|
// Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
|
|
1587
1626
|
// later validation/finalization logic decides when it can leave memory.
|
|
1588
|
-
this.pendingPayloads.delete(rootHex)
|
|
1627
|
+
if (this.pendingPayloads.delete(rootHex)) {
|
|
1628
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
|
|
1629
|
+
}
|
|
1589
1630
|
|
|
1590
1631
|
const badPendingBlocks = getAllDescendantBlocks(rootHex, this.pendingBlocks);
|
|
1591
|
-
this.metrics?.blockInputSync.removedBlocks.inc(badPendingBlocks.length);
|
|
1592
|
-
|
|
1593
1632
|
for (const block of badPendingBlocks) {
|
|
1594
1633
|
const descendantRootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
1595
|
-
this.pendingBlocks.delete(descendantRootHex)
|
|
1596
|
-
|
|
1634
|
+
if (this.pendingBlocks.delete(descendantRootHex)) {
|
|
1635
|
+
this.metrics?.blockInputSync.removedBlocks.inc({reason: DroppedItemReason.invalidParent}, 1);
|
|
1636
|
+
}
|
|
1637
|
+
if (this.pendingPayloads.delete(descendantRootHex)) {
|
|
1638
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason: DroppedItemReason.invalidParent}, 1);
|
|
1639
|
+
}
|
|
1597
1640
|
this.chain.seenBlockInputCache.prune(descendantRootHex);
|
|
1598
1641
|
this.logger.debug("Removing pending descendant after invalid parent payload", {
|
|
1599
1642
|
slot: getBlockInputSyncCacheItemSlot(block),
|
|
@@ -1603,18 +1646,25 @@ export class BlockInputSync {
|
|
|
1603
1646
|
}
|
|
1604
1647
|
}
|
|
1605
1648
|
|
|
1606
|
-
private removeAllDescendants(
|
|
1649
|
+
private removeAllDescendants(
|
|
1650
|
+
block: BlockInputSyncCacheItem,
|
|
1651
|
+
headReason: DroppedItemReason,
|
|
1652
|
+
descendantReason: DroppedItemReason = DroppedItemReason.invalidParent
|
|
1653
|
+
): BlockInputSyncCacheItem[] {
|
|
1607
1654
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
1608
1655
|
const slot = getBlockInputSyncCacheItemSlot(block);
|
|
1609
|
-
// Get all blocks that are a descendant of this one
|
|
1656
|
+
// Get all blocks that are a descendant of this one (index 0 is the head item itself)
|
|
1610
1657
|
const badPendingBlocks = [block, ...getAllDescendantBlocks(rootHex, this.pendingBlocks)];
|
|
1611
1658
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
for (const block of badPendingBlocks) {
|
|
1659
|
+
for (const [i, block] of badPendingBlocks.entries()) {
|
|
1615
1660
|
const rootHex = getBlockInputSyncCacheItemRootHex(block);
|
|
1616
|
-
|
|
1617
|
-
this.
|
|
1661
|
+
const reason = i === 0 ? headReason : descendantReason;
|
|
1662
|
+
if (this.pendingBlocks.delete(rootHex)) {
|
|
1663
|
+
this.metrics?.blockInputSync.removedBlocks.inc({reason}, 1);
|
|
1664
|
+
}
|
|
1665
|
+
if (this.pendingPayloads.delete(rootHex)) {
|
|
1666
|
+
this.metrics?.blockInputSync.removedPayloads.inc({reason}, 1);
|
|
1667
|
+
}
|
|
1618
1668
|
this.chain.seenBlockInputCache.prune(rootHex);
|
|
1619
1669
|
// Keep PayloadEnvelopeInput resident in the seen cache for consistency with the
|
|
1620
1670
|
// importBlock()-owned lifecycle.
|