@lodestar/beacon-node 1.47.0-dev.da8dfeabda → 1.47.0-dev.e73f1f5165
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/events/index.d.ts.map +1 -1
- package/lib/api/impl/events/index.js +2 -0
- package/lib/api/impl/events/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/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +4 -0
- package/lib/chain/forkChoice/index.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/constants.d.ts +7 -0
- package/lib/network/processor/constants.d.ts.map +1 -0
- package/lib/network/processor/constants.js +7 -0
- package/lib/network/processor/constants.js.map +1 -0
- 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/network/processor/index.d.ts +3 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +36 -15
- package/lib/network/processor/index.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 +6 -2
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +143 -68
- 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/events/index.ts +3 -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/forkChoice/index.ts +4 -0
- 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/constants.ts +6 -0
- package/src/network/processor/gossipHandlers.ts +5 -1
- package/src/network/processor/index.ts +42 -19
- 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 +167 -73
- 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",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Max distinct peers tracked per unknown block/payload root.
|
|
3
|
+
* Normally we have 8 mesh peers per topic, and multi topics may point to the same root.
|
|
4
|
+
* Having 32 peers per root seems enough to search for missing blocks/payloads.
|
|
5
|
+
**/
|
|
6
|
+
export const MAX_PEERS_PER_ROOT = 32;
|
|
@@ -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) {
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
GossipValidatorBatchFn,
|
|
31
31
|
GossipValidatorFn,
|
|
32
32
|
} from "../gossip/interface.js";
|
|
33
|
+
import {MAX_PEERS_PER_ROOT} from "./constants.js";
|
|
33
34
|
import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
|
|
34
35
|
import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
|
|
35
36
|
import {createGossipQueues} from "./gossipQueues/index.js";
|
|
@@ -191,8 +192,12 @@ export class NetworkProcessor {
|
|
|
191
192
|
// in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
|
|
192
193
|
private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
|
|
193
194
|
private awaitingPayloadMessageCount = 0;
|
|
194
|
-
private unknownBlocksBySlot = new MapDef<Slot,
|
|
195
|
-
|
|
195
|
+
private unknownBlocksBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
|
|
196
|
+
() => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
|
|
197
|
+
);
|
|
198
|
+
private unknownEnvelopesBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
|
|
199
|
+
() => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
|
|
200
|
+
);
|
|
196
201
|
|
|
197
202
|
constructor(
|
|
198
203
|
modules: NetworkProcessorModules,
|
|
@@ -271,35 +276,53 @@ export class NetworkProcessor {
|
|
|
271
276
|
* Search block via `ChainEvent.unknownBlockRoot` event
|
|
272
277
|
* Slot is the message slot, which is not necessarily the same as the block's slot, but it can be used for a good prune strategy.
|
|
273
278
|
* In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
|
|
279
|
+
* We forward every distinct peer that gossiped this root (not just the first) so BlockInputSync can
|
|
280
|
+
* prefer them as by-root download candidates.
|
|
274
281
|
*/
|
|
275
282
|
searchUnknownBlock({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
|
|
276
|
-
if (
|
|
277
|
-
this.chain.seenBlock(root) ||
|
|
278
|
-
this.awaitingMessagesByBlockRoot.has(root) ||
|
|
279
|
-
this.unknownBlocksBySlot.getOrDefault(slot).has(root)
|
|
280
|
-
) {
|
|
283
|
+
if (this.chain.seenBlock(root)) {
|
|
281
284
|
return;
|
|
282
285
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
+
const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
|
|
287
|
+
const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
|
|
288
|
+
const forwardedPeers = peersForRoot.getOrDefault(root);
|
|
289
|
+
|
|
290
|
+
// brand-new search always emits (peer may be undefined)
|
|
291
|
+
let shouldEmit = !alreadySearching;
|
|
292
|
+
if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
|
|
293
|
+
// First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
|
|
294
|
+
forwardedPeers.add(peer);
|
|
295
|
+
shouldEmit = true;
|
|
296
|
+
}
|
|
297
|
+
if (shouldEmit) {
|
|
298
|
+
this.chain.emitter.emit(ChainEvent.unknownBlockRoot, {rootHex: root, peer, source});
|
|
299
|
+
}
|
|
286
300
|
}
|
|
287
301
|
|
|
288
302
|
/**
|
|
289
303
|
* Search envelope via `ChainEvent.unknownEnvelopeBlockRoot` event
|
|
290
304
|
* Slot is the message slot, which is not necessarily the same as the envelope's slot, but it can be used for a good prune strategy.
|
|
291
305
|
* In the rare case, if 2 messages on 2 slots search for the same root (for example beacon_attestation) we may emit the same root twice but BlockInputSync should handle it well.
|
|
306
|
+
* Same peer-forwarding + dedup behavior as searchUnknownBlock.
|
|
292
307
|
*/
|
|
293
308
|
searchUnknownEnvelope({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
|
|
294
|
-
if (
|
|
295
|
-
this.chain.seenPayloadEnvelope(root) ||
|
|
296
|
-
this.awaitingMessagesByPayloadBlockRoot.has(root) ||
|
|
297
|
-
this.unknownEnvelopesBySlot.getOrDefault(slot).has(root)
|
|
298
|
-
) {
|
|
309
|
+
if (this.chain.seenPayloadEnvelope(root)) {
|
|
299
310
|
return;
|
|
300
311
|
}
|
|
301
|
-
this.unknownEnvelopesBySlot.getOrDefault(slot)
|
|
302
|
-
|
|
312
|
+
const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
|
|
313
|
+
// capture "already searching" BEFORE getOrDefault(root) creates the entry
|
|
314
|
+
const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
|
|
315
|
+
const forwardedPeers = peersForRoot.getOrDefault(root);
|
|
316
|
+
|
|
317
|
+
let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
|
|
318
|
+
if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
|
|
319
|
+
// First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
|
|
320
|
+
forwardedPeers.add(peer);
|
|
321
|
+
shouldEmit = true;
|
|
322
|
+
}
|
|
323
|
+
if (shouldEmit) {
|
|
324
|
+
this.chain.emitter.emit(ChainEvent.unknownEnvelopeBlockRoot, {rootHex: root, slot, peer, source});
|
|
325
|
+
}
|
|
303
326
|
}
|
|
304
327
|
|
|
305
328
|
private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
|
|
@@ -616,7 +639,7 @@ export class NetworkProcessor {
|
|
|
616
639
|
|
|
617
640
|
for (const [slot, roots] of this.unknownBlocksBySlot) {
|
|
618
641
|
if (slot > minSlot) continue;
|
|
619
|
-
for (const rootHex of roots) {
|
|
642
|
+
for (const rootHex of roots.keys()) {
|
|
620
643
|
const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
|
|
621
644
|
if (gossipMessages !== undefined) {
|
|
622
645
|
for (const message of gossipMessages) {
|
|
@@ -641,7 +664,7 @@ export class NetworkProcessor {
|
|
|
641
664
|
|
|
642
665
|
for (const [slot, roots] of this.unknownEnvelopesBySlot) {
|
|
643
666
|
if (slot > minSlot) continue;
|
|
644
|
-
for (const rootHex of roots) {
|
|
667
|
+
for (const rootHex of roots.keys()) {
|
|
645
668
|
const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
|
|
646
669
|
if (gossipMessages !== undefined) {
|
|
647
670
|
for (const message of gossipMessages) {
|
|
@@ -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",
|