@lodestar/beacon-node 1.40.0-dev.1020f27ca9 → 1.40.0-dev.13ab628623
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 +8 -2
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.d.ts.map +1 -1
- package/lib/api/impl/lodestar/index.js +14 -0
- 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.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.d.ts +28 -0
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +36 -1
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/importBlock.js +1 -1
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
- package/lib/chain/blocks/writeBlockInputToDb.js +8 -0
- package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
- package/lib/chain/chain.d.ts +1 -1
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +12 -25
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/options.d.ts +0 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +0 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/regen/interface.d.ts +1 -1
- package/lib/chain/regen/queued.d.ts +1 -1
- package/lib/chain/regen/queued.d.ts.map +1 -1
- package/lib/chain/regen/queued.js.map +1 -1
- package/lib/chain/stateCache/index.d.ts +0 -2
- package/lib/chain/stateCache/index.d.ts.map +1 -1
- package/lib/chain/stateCache/index.js +0 -2
- package/lib/chain/stateCache/index.js.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +2 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
- package/lib/chain/stateCache/persistentCheckpointsCache.js +3 -0
- package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +1 -2
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/network/core/networkCore.d.ts +3 -0
- package/lib/network/core/networkCore.d.ts.map +1 -1
- package/lib/network/core/networkCore.js +9 -0
- package/lib/network/core/networkCore.js.map +1 -1
- package/lib/network/core/networkCoreWorker.js +3 -0
- package/lib/network/core/networkCoreWorker.js.map +1 -1
- package/lib/network/core/networkCoreWorkerHandler.d.ts +3 -0
- package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
- package/lib/network/core/networkCoreWorkerHandler.js +9 -0
- package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
- package/lib/network/core/types.d.ts +3 -0
- package/lib/network/core/types.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts +34 -0
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +123 -0
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/network.d.ts +3 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +9 -0
- package/lib/network/network.js.map +1 -1
- package/lib/network/options.d.ts +6 -0
- package/lib/network/options.d.ts.map +1 -1
- package/lib/network/options.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +9 -0
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/processor/gossipHandlers.js +1 -1
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/package.json +15 -15
- package/src/api/impl/beacon/blocks/index.ts +22 -12
- package/src/api/impl/lodestar/index.ts +17 -0
- package/src/api/impl/validator/index.ts +2 -1
- package/src/chain/blocks/blockInput/blockInput.ts +45 -2
- package/src/chain/blocks/importBlock.ts +1 -1
- package/src/chain/blocks/writeBlockInputToDb.ts +9 -0
- package/src/chain/chain.ts +17 -29
- package/src/chain/options.ts +0 -2
- package/src/chain/regen/interface.ts +1 -1
- package/src/chain/regen/queued.ts +1 -2
- package/src/chain/stateCache/index.ts +0 -2
- package/src/chain/stateCache/persistentCheckpointsCache.ts +6 -2
- package/src/chain/validation/block.ts +1 -2
- package/src/network/core/networkCore.ts +12 -0
- package/src/network/core/networkCoreWorker.ts +3 -0
- package/src/network/core/networkCoreWorkerHandler.ts +9 -0
- package/src/network/core/types.ts +6 -0
- package/src/network/gossip/gossipsub.ts +147 -1
- package/src/network/network.ts +12 -0
- package/src/network/options.ts +6 -0
- package/src/network/peers/peerManager.ts +11 -0
- package/src/network/processor/gossipHandlers.ts +1 -1
- package/lib/chain/stateCache/blockStateCacheImpl.d.ts +0 -54
- package/lib/chain/stateCache/blockStateCacheImpl.d.ts.map +0 -1
- package/lib/chain/stateCache/blockStateCacheImpl.js +0 -130
- package/lib/chain/stateCache/blockStateCacheImpl.js.map +0 -1
- package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts +0 -60
- package/lib/chain/stateCache/inMemoryCheckpointsCache.d.ts.map +0 -1
- package/lib/chain/stateCache/inMemoryCheckpointsCache.js +0 -156
- package/lib/chain/stateCache/inMemoryCheckpointsCache.js.map +0 -1
- package/src/chain/stateCache/blockStateCacheImpl.ts +0 -149
- package/src/chain/stateCache/inMemoryCheckpointsCache.ts +0 -192
package/src/chain/chain.ts
CHANGED
|
@@ -107,12 +107,10 @@ import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js";
|
|
|
107
107
|
import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
|
|
108
108
|
import {SeenBlockInput} from "./seenCache/seenGossipBlockInput.js";
|
|
109
109
|
import {ShufflingCache} from "./shufflingCache.js";
|
|
110
|
-
import {BlockStateCacheImpl} from "./stateCache/blockStateCacheImpl.js";
|
|
111
110
|
import {DbCPStateDatastore, checkpointToDatastoreKey} from "./stateCache/datastore/db.js";
|
|
112
111
|
import {FileCPStateDatastore} from "./stateCache/datastore/file.js";
|
|
113
112
|
import {CPStateDatastore} from "./stateCache/datastore/types.js";
|
|
114
113
|
import {FIFOBlockStateCache} from "./stateCache/fifoBlockStateCache.js";
|
|
115
|
-
import {InMemoryCheckpointStateCache} from "./stateCache/inMemoryCheckpointsCache.js";
|
|
116
114
|
import {PersistentCheckpointStateCache} from "./stateCache/persistentCheckpointsCache.js";
|
|
117
115
|
import {CheckpointStateCache} from "./stateCache/types.js";
|
|
118
116
|
import {ValidatorMonitor} from "./validatorMonitor.js";
|
|
@@ -142,7 +140,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
142
140
|
readonly logger: Logger;
|
|
143
141
|
readonly metrics: Metrics | null;
|
|
144
142
|
readonly validatorMonitor: ValidatorMonitor | null;
|
|
145
|
-
readonly bufferPool: BufferPool
|
|
143
|
+
readonly bufferPool: BufferPool;
|
|
146
144
|
|
|
147
145
|
readonly anchorStateLatestBlockSlot: Slot;
|
|
148
146
|
|
|
@@ -339,32 +337,22 @@ export class BeaconChain implements IBeaconChain {
|
|
|
339
337
|
this.index2pubkey = index2pubkey;
|
|
340
338
|
|
|
341
339
|
const fileDataStore = opts.nHistoricalStatesFileDataStore ?? true;
|
|
342
|
-
const blockStateCache = this.opts
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
this.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
clock,
|
|
359
|
-
blockStateCache,
|
|
360
|
-
bufferPool: this.bufferPool,
|
|
361
|
-
datastore: this.cpStateDatastore,
|
|
362
|
-
},
|
|
363
|
-
this.opts
|
|
364
|
-
);
|
|
365
|
-
} else {
|
|
366
|
-
checkpointStateCache = new InMemoryCheckpointStateCache({metrics});
|
|
367
|
-
}
|
|
340
|
+
const blockStateCache = new FIFOBlockStateCache(this.opts, {metrics});
|
|
341
|
+
this.bufferPool = new BufferPool(anchorState.type.tree_serializedSize(anchorState.node), metrics);
|
|
342
|
+
|
|
343
|
+
this.cpStateDatastore = fileDataStore ? new FileCPStateDatastore(dataDir) : new DbCPStateDatastore(this.db);
|
|
344
|
+
const checkpointStateCache: CheckpointStateCache = new PersistentCheckpointStateCache(
|
|
345
|
+
{
|
|
346
|
+
config,
|
|
347
|
+
metrics,
|
|
348
|
+
logger,
|
|
349
|
+
clock,
|
|
350
|
+
blockStateCache,
|
|
351
|
+
bufferPool: this.bufferPool,
|
|
352
|
+
datastore: this.cpStateDatastore,
|
|
353
|
+
},
|
|
354
|
+
this.opts
|
|
355
|
+
);
|
|
368
356
|
|
|
369
357
|
const {checkpoint} = computeAnchorCheckpoint(config, anchorState);
|
|
370
358
|
blockStateCache.add(anchorState);
|
package/src/chain/options.ts
CHANGED
|
@@ -45,7 +45,6 @@ export type IChainOptions = BlockProcessOpts &
|
|
|
45
45
|
broadcastValidationStrictness?: string;
|
|
46
46
|
minSameMessageSignatureSetsToBatch: number;
|
|
47
47
|
archiveDateEpochs?: number;
|
|
48
|
-
nHistoricalStates?: boolean;
|
|
49
48
|
nHistoricalStatesFileDataStore?: boolean;
|
|
50
49
|
};
|
|
51
50
|
|
|
@@ -119,7 +118,6 @@ export const defaultChainOptions: IChainOptions = {
|
|
|
119
118
|
// batching too much may block the I/O thread so if useWorker=false, suggest this value to be 32
|
|
120
119
|
// since this batch attestation work is designed to work with useWorker=true, make this the lowest value
|
|
121
120
|
minSameMessageSignatureSetsToBatch: 2,
|
|
122
|
-
nHistoricalStates: true,
|
|
123
121
|
// as of Feb 2025, this option turned out to be very useful:
|
|
124
122
|
// - it allows to share a persisted checkpoint state to other nodes
|
|
125
123
|
// - users can prune the persisted checkpoint state files manually to save disc space
|
|
@@ -2,7 +2,7 @@ import {routes} from "@lodestar/api";
|
|
|
2
2
|
import {ProtoBlock} from "@lodestar/fork-choice";
|
|
3
3
|
import {CachedBeaconStateAllForks} from "@lodestar/state-transition";
|
|
4
4
|
import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
|
|
5
|
-
import {CheckpointHex} from "../stateCache/
|
|
5
|
+
import {CheckpointHex} from "../stateCache/types.js";
|
|
6
6
|
|
|
7
7
|
export enum RegenCaller {
|
|
8
8
|
getDuties = "getDuties",
|
|
@@ -5,8 +5,7 @@ import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types";
|
|
|
5
5
|
import {Logger, toRootHex} from "@lodestar/utils";
|
|
6
6
|
import {Metrics} from "../../metrics/index.js";
|
|
7
7
|
import {JobItemQueue} from "../../util/queue/index.js";
|
|
8
|
-
import {CheckpointHex} from "../stateCache/
|
|
9
|
-
import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js";
|
|
8
|
+
import {BlockStateCache, CheckpointHex, CheckpointStateCache} from "../stateCache/types.js";
|
|
10
9
|
import {RegenError, RegenErrorCode} from "./errors.js";
|
|
11
10
|
import {
|
|
12
11
|
IStateRegenerator,
|
|
@@ -31,7 +31,7 @@ type PersistentCheckpointStateCacheModules = {
|
|
|
31
31
|
signal?: AbortSignal;
|
|
32
32
|
datastore: CPStateDatastore;
|
|
33
33
|
blockStateCache: BlockStateCache;
|
|
34
|
-
bufferPool?: BufferPool
|
|
34
|
+
bufferPool?: BufferPool;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/** checkpoint serialized as a string */
|
|
@@ -119,7 +119,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
|
|
|
119
119
|
private readonly maxEpochsOnDisk: number;
|
|
120
120
|
private readonly datastore: CPStateDatastore;
|
|
121
121
|
private readonly blockStateCache: BlockStateCache;
|
|
122
|
-
private readonly bufferPool?: BufferPool
|
|
122
|
+
private readonly bufferPool?: BufferPool;
|
|
123
123
|
|
|
124
124
|
constructor(
|
|
125
125
|
{
|
|
@@ -851,6 +851,10 @@ export function toCheckpointHex(checkpoint: phase0.Checkpoint): CheckpointHex {
|
|
|
851
851
|
};
|
|
852
852
|
}
|
|
853
853
|
|
|
854
|
+
export function toCheckpointKey(cp: CheckpointHex): string {
|
|
855
|
+
return `${cp.rootHex}:${cp.epoch}`;
|
|
856
|
+
}
|
|
857
|
+
|
|
854
858
|
function toCacheKey(cp: CheckpointHex | phase0.Checkpoint): CacheKey {
|
|
855
859
|
if (isCheckpointHex(cp)) {
|
|
856
860
|
return `${cp.rootHex}_${cp.epoch}`;
|
|
@@ -138,11 +138,10 @@ export async function validateGossipBlock(
|
|
|
138
138
|
// in forky condition, make sure to populate ShufflingCache with regened state
|
|
139
139
|
chain.shufflingCache.processState(blockState);
|
|
140
140
|
|
|
141
|
-
// Extra conditions for merge fork blocks
|
|
142
141
|
// [REJECT] The block's execution payload timestamp is correct with respect to the slot
|
|
143
142
|
// -- i.e. execution_payload.timestamp == compute_timestamp_at_slot(state, block.slot).
|
|
144
143
|
if (isForkPostBellatrix(fork) && !isForkPostGloas(fork)) {
|
|
145
|
-
if (!isExecutionBlockBodyType(block.body)) throw Error("Not
|
|
144
|
+
if (!isExecutionBlockBodyType(block.body)) throw Error("Not execution block body type");
|
|
146
145
|
const executionPayload = block.body.executionPayload;
|
|
147
146
|
if (isExecutionStateType(blockState) && isExecutionEnabled(blockState, block)) {
|
|
148
147
|
const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime);
|
|
@@ -454,6 +454,18 @@ export class NetworkCore implements INetworkCore {
|
|
|
454
454
|
await this.libp2p.hangUp(peerIdFromString(peerIdStr));
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
async addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
|
|
458
|
+
return this.gossip.addDirectPeer(peer);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async removeDirectPeer(peerIdStr: PeerIdStr): Promise<boolean> {
|
|
462
|
+
return this.gossip.removeDirectPeer(peerIdStr);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async getDirectPeers(): Promise<string[]> {
|
|
466
|
+
return this.gossip.getDirectPeers();
|
|
467
|
+
}
|
|
468
|
+
|
|
457
469
|
private _dumpPeer(peerIdStr: string, connections: Connection[]): routes.lodestar.LodestarNodePeer {
|
|
458
470
|
const peerData = this.peersData.connectedPeers.get(peerIdStr);
|
|
459
471
|
const fork = this.config.getForkName(this.clock.currentSlot);
|
|
@@ -153,6 +153,9 @@ const libp2pWorkerApi: NetworkWorkerApi = {
|
|
|
153
153
|
getConnectedPeerCount: () => core.getConnectedPeerCount(),
|
|
154
154
|
connectToPeer: (peer, multiaddr) => core.connectToPeer(peer, multiaddr),
|
|
155
155
|
disconnectPeer: (peer) => core.disconnectPeer(peer),
|
|
156
|
+
addDirectPeer: (peer) => core.addDirectPeer(peer),
|
|
157
|
+
removeDirectPeer: (peerId) => core.removeDirectPeer(peerId),
|
|
158
|
+
getDirectPeers: () => core.getDirectPeers(),
|
|
156
159
|
dumpPeers: () => core.dumpPeers(),
|
|
157
160
|
dumpPeer: (peerIdStr) => core.dumpPeer(peerIdStr),
|
|
158
161
|
dumpPeerScoreStats: () => core.dumpPeerScoreStats(),
|
|
@@ -247,6 +247,15 @@ export class WorkerNetworkCore implements INetworkCore {
|
|
|
247
247
|
disconnectPeer(peer: PeerIdStr): Promise<void> {
|
|
248
248
|
return this.getApi().disconnectPeer(peer);
|
|
249
249
|
}
|
|
250
|
+
addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
|
|
251
|
+
return this.getApi().addDirectPeer(peer);
|
|
252
|
+
}
|
|
253
|
+
removeDirectPeer(peerId: PeerIdStr): Promise<boolean> {
|
|
254
|
+
return this.getApi().removeDirectPeer(peerId);
|
|
255
|
+
}
|
|
256
|
+
getDirectPeers(): Promise<string[]> {
|
|
257
|
+
return this.getApi().getDirectPeers();
|
|
258
|
+
}
|
|
250
259
|
dumpPeers(): Promise<routes.lodestar.LodestarNodePeer[]> {
|
|
251
260
|
return this.getApi().dumpPeers();
|
|
252
261
|
}
|
|
@@ -30,6 +30,12 @@ export interface INetworkCorePublic {
|
|
|
30
30
|
// Debug
|
|
31
31
|
connectToPeer(peer: PeerIdStr, multiaddr: MultiaddrStr[]): Promise<void>;
|
|
32
32
|
disconnectPeer(peer: PeerIdStr): Promise<void>;
|
|
33
|
+
|
|
34
|
+
// Direct peers management
|
|
35
|
+
addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null>;
|
|
36
|
+
removeDirectPeer(peerId: PeerIdStr): Promise<boolean>;
|
|
37
|
+
getDirectPeers(): Promise<string[]>;
|
|
38
|
+
|
|
33
39
|
dumpPeers(): Promise<routes.lodestar.LodestarNodePeer[]>;
|
|
34
40
|
dumpPeer(peerIdStr: PeerIdStr): Promise<routes.lodestar.LodestarNodePeer | undefined>;
|
|
35
41
|
dumpPeerScoreStats(): Promise<PeerScoreStats>;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import {peerIdFromString} from "@libp2p/peer-id";
|
|
2
|
+
import {multiaddr} from "@multiformats/multiaddr";
|
|
3
|
+
import {ENR} from "@chainsafe/enr";
|
|
1
4
|
import {GossipSub, GossipsubEvents} from "@chainsafe/libp2p-gossipsub";
|
|
2
5
|
import {MetricsRegister, TopicLabel, TopicStrToLabel} from "@chainsafe/libp2p-gossipsub/metrics";
|
|
3
6
|
import {PeerScoreParams} from "@chainsafe/libp2p-gossipsub/score";
|
|
4
|
-
import {SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
|
|
7
|
+
import {AddrInfo, SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
|
|
8
|
+
import {routes} from "@lodestar/api";
|
|
5
9
|
import {BeaconConfig, ForkBoundary} from "@lodestar/config";
|
|
6
10
|
import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
|
|
7
11
|
import {SubnetID} from "@lodestar/types";
|
|
@@ -55,6 +59,12 @@ export type Eth2GossipsubOpts = {
|
|
|
55
59
|
disableFloodPublish?: boolean;
|
|
56
60
|
skipParamsLog?: boolean;
|
|
57
61
|
disableLightClientServer?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
|
|
64
|
+
* Supports multiaddr strings with peer ID (e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...")
|
|
65
|
+
* or ENR strings (e.g., "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...")
|
|
66
|
+
*/
|
|
67
|
+
directPeers?: string[];
|
|
58
68
|
};
|
|
59
69
|
|
|
60
70
|
export type ForkBoundaryLabel = string;
|
|
@@ -78,6 +88,7 @@ export class Eth2Gossipsub extends GossipSub {
|
|
|
78
88
|
private readonly logger: Logger;
|
|
79
89
|
private readonly peersData: PeersData;
|
|
80
90
|
private readonly events: NetworkEventBus;
|
|
91
|
+
private readonly libp2p: Libp2p;
|
|
81
92
|
|
|
82
93
|
// Internal caches
|
|
83
94
|
private readonly gossipTopicCache: GossipTopicCache;
|
|
@@ -97,6 +108,9 @@ export class Eth2Gossipsub extends GossipSub {
|
|
|
97
108
|
);
|
|
98
109
|
}
|
|
99
110
|
|
|
111
|
+
// Parse direct peers from multiaddr strings to AddrInfo objects
|
|
112
|
+
const directPeers = parseDirectPeers(opts.directPeers ?? [], logger);
|
|
113
|
+
|
|
100
114
|
// Gossipsub parameters defined here:
|
|
101
115
|
// https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-gossip-domain-gossipsub
|
|
102
116
|
super(modules.libp2p.services.components, {
|
|
@@ -106,6 +120,7 @@ export class Eth2Gossipsub extends GossipSub {
|
|
|
106
120
|
Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
|
|
107
121
|
Dhi: gossipsubDHigh ?? GOSSIP_D_HIGH,
|
|
108
122
|
Dlazy: 6,
|
|
123
|
+
directPeers,
|
|
109
124
|
heartbeatInterval: GOSSIPSUB_HEARTBEAT_INTERVAL,
|
|
110
125
|
fanoutTTL: 60 * 1000,
|
|
111
126
|
mcacheLength: 6,
|
|
@@ -146,6 +161,7 @@ export class Eth2Gossipsub extends GossipSub {
|
|
|
146
161
|
this.logger = logger;
|
|
147
162
|
this.peersData = peersData;
|
|
148
163
|
this.events = events;
|
|
164
|
+
this.libp2p = modules.libp2p;
|
|
149
165
|
this.gossipTopicCache = gossipTopicCache;
|
|
150
166
|
|
|
151
167
|
this.addEventListener("gossipsub:message", this.onGossipsubMessage.bind(this));
|
|
@@ -328,6 +344,64 @@ export class Eth2Gossipsub extends GossipSub {
|
|
|
328
344
|
this.reportMessageValidationResult(data.msgId, data.propagationSource, data.acceptance);
|
|
329
345
|
});
|
|
330
346
|
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Add a peer as a direct peer at runtime. Accepts multiaddr with peer ID or ENR string.
|
|
350
|
+
* Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
|
|
351
|
+
*/
|
|
352
|
+
async addDirectPeer(peerStr: routes.lodestar.DirectPeer): Promise<string | null> {
|
|
353
|
+
const parsed = parseDirectPeers([peerStr], this.logger);
|
|
354
|
+
if (parsed.length === 0) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const {id: peerId, addrs} = parsed[0];
|
|
359
|
+
const peerIdStr = peerId.toString();
|
|
360
|
+
|
|
361
|
+
// Prevent adding self as a direct peer
|
|
362
|
+
if (peerId.equals(this.libp2p.peerId)) {
|
|
363
|
+
this.logger.warn("Cannot add self as a direct peer", {peerId: peerIdStr});
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Direct peers need addresses to connect - reject if none provided
|
|
368
|
+
if (addrs.length === 0) {
|
|
369
|
+
this.logger.warn("Cannot add direct peer without addresses", {peerId: peerIdStr});
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Add addresses to peer store first so we can connect
|
|
374
|
+
try {
|
|
375
|
+
await this.libp2p.peerStore.merge(peerId, {multiaddrs: addrs});
|
|
376
|
+
} catch (e) {
|
|
377
|
+
this.logger.warn("Failed to add direct peer addresses to peer store", {peerId: peerIdStr}, e as Error);
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Add to direct peers set only after addresses are stored
|
|
382
|
+
this.direct.add(peerIdStr);
|
|
383
|
+
|
|
384
|
+
this.logger.info("Added direct peer via API", {peerId: peerIdStr});
|
|
385
|
+
return peerIdStr;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Remove a peer from direct peers.
|
|
390
|
+
*/
|
|
391
|
+
removeDirectPeer(peerIdStr: string): boolean {
|
|
392
|
+
const removed = this.direct.delete(peerIdStr);
|
|
393
|
+
if (removed) {
|
|
394
|
+
this.logger.info("Removed direct peer via API", {peerId: peerIdStr});
|
|
395
|
+
}
|
|
396
|
+
return removed;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Get list of current direct peer IDs.
|
|
401
|
+
*/
|
|
402
|
+
getDirectPeers(): string[] {
|
|
403
|
+
return Array.from(this.direct);
|
|
404
|
+
}
|
|
331
405
|
}
|
|
332
406
|
|
|
333
407
|
/**
|
|
@@ -381,3 +455,75 @@ function getForkBoundaryLabel(boundary: ForkBoundary): ForkBoundaryLabel {
|
|
|
381
455
|
|
|
382
456
|
return label;
|
|
383
457
|
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Parse direct peer strings into AddrInfo objects for GossipSub.
|
|
461
|
+
* Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
|
|
462
|
+
*
|
|
463
|
+
* Supported formats:
|
|
464
|
+
* - Multiaddr with peer ID: `/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...`
|
|
465
|
+
* - ENR: `enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...`
|
|
466
|
+
*
|
|
467
|
+
* For multiaddrs, the string must contain a /p2p/ component with the peer ID.
|
|
468
|
+
* For ENRs, the TCP multiaddr and peer ID are extracted from the encoded record.
|
|
469
|
+
*/
|
|
470
|
+
export function parseDirectPeers(directPeerStrs: routes.lodestar.DirectPeer[], logger: Logger): AddrInfo[] {
|
|
471
|
+
const directPeers: AddrInfo[] = [];
|
|
472
|
+
|
|
473
|
+
for (const peerStr of directPeerStrs) {
|
|
474
|
+
// Check if this is an ENR (starts with "enr:")
|
|
475
|
+
if (peerStr.startsWith("enr:")) {
|
|
476
|
+
try {
|
|
477
|
+
const enr = ENR.decodeTxt(peerStr);
|
|
478
|
+
const peerId = enr.peerId;
|
|
479
|
+
|
|
480
|
+
// Get TCP multiaddr from ENR
|
|
481
|
+
const multiaddrTCP = enr.getLocationMultiaddr("tcp");
|
|
482
|
+
if (!multiaddrTCP) {
|
|
483
|
+
logger.warn("ENR does not contain TCP multiaddr", {enr: peerStr});
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
directPeers.push({
|
|
488
|
+
id: peerId,
|
|
489
|
+
addrs: [multiaddrTCP],
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
logger.info("Added direct peer from ENR", {peerId: peerId.toString(), addr: multiaddrTCP.toString()});
|
|
493
|
+
} catch (e) {
|
|
494
|
+
logger.warn("Failed to parse direct peer ENR", {enr: peerStr}, e as Error);
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
// Parse as multiaddr
|
|
498
|
+
try {
|
|
499
|
+
const ma = multiaddr(peerStr);
|
|
500
|
+
|
|
501
|
+
const peerIdStr = ma.getPeerId();
|
|
502
|
+
if (!peerIdStr) {
|
|
503
|
+
logger.warn("Direct peer multiaddr must contain /p2p/ component with peer ID", {multiaddr: peerStr});
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
try {
|
|
508
|
+
const peerId = peerIdFromString(peerIdStr);
|
|
509
|
+
|
|
510
|
+
// Get the address without the /p2p/ component
|
|
511
|
+
const addr = ma.decapsulate("/p2p/" + peerIdStr);
|
|
512
|
+
|
|
513
|
+
directPeers.push({
|
|
514
|
+
id: peerId,
|
|
515
|
+
addrs: [addr],
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
logger.info("Added direct peer", {peerId: peerIdStr, addr: addr.toString()});
|
|
519
|
+
} catch (e) {
|
|
520
|
+
logger.warn("Invalid peer ID in direct peer multiaddr", {multiaddr: peerStr, peerId: peerIdStr}, e as Error);
|
|
521
|
+
}
|
|
522
|
+
} catch (e) {
|
|
523
|
+
logger.warn("Failed to parse direct peer multiaddr", {multiaddr: peerStr}, e as Error);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return directPeers;
|
|
529
|
+
}
|
package/src/network/network.ts
CHANGED
|
@@ -641,6 +641,18 @@ export class Network implements INetwork {
|
|
|
641
641
|
return this.core.disconnectPeer(peer);
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
+
addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
|
|
645
|
+
return this.core.addDirectPeer(peer);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
removeDirectPeer(peerId: string): Promise<boolean> {
|
|
649
|
+
return this.core.removeDirectPeer(peerId);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
getDirectPeers(): Promise<string[]> {
|
|
653
|
+
return this.core.getDirectPeers();
|
|
654
|
+
}
|
|
655
|
+
|
|
644
656
|
dumpPeer(peerIdStr: string): Promise<routes.lodestar.LodestarNodePeer | undefined> {
|
|
645
657
|
return this.core.dumpPeer(peerIdStr);
|
|
646
658
|
}
|
package/src/network/options.ts
CHANGED
|
@@ -15,6 +15,12 @@ export interface NetworkOptions
|
|
|
15
15
|
Omit<Eth2GossipsubOpts, "disableLightClientServer"> {
|
|
16
16
|
localMultiaddrs: string[];
|
|
17
17
|
bootMultiaddrs?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
|
|
20
|
+
* Format: multiaddr strings with peer ID, e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7..."
|
|
21
|
+
* Both peers must configure each other as direct peers for the feature to work properly.
|
|
22
|
+
*/
|
|
23
|
+
directPeers?: string[];
|
|
18
24
|
subscribeAllSubnets?: boolean;
|
|
19
25
|
mdns?: boolean;
|
|
20
26
|
connectToDiscv5Bootnodes?: boolean;
|
|
@@ -721,6 +721,17 @@ export class PeerManager {
|
|
|
721
721
|
// NOTE: libp2p may emit two "peer:connect" events: One for inbound, one for outbound
|
|
722
722
|
// If that happens, it's okay. Only the "outbound" connection triggers immediate action
|
|
723
723
|
const now = Date.now();
|
|
724
|
+
|
|
725
|
+
// Ethereum uses secp256k1 for node IDs, reject peers with other key types
|
|
726
|
+
if (remotePeer.type !== "secp256k1") {
|
|
727
|
+
this.logger.debug("Peer does not have secp256k1 key, disconnecting", {
|
|
728
|
+
peer: remotePeerPrettyStr,
|
|
729
|
+
type: remotePeer.type,
|
|
730
|
+
});
|
|
731
|
+
void this.goodbyeAndDisconnect(remotePeer, GoodByeReasonCode.IRRELEVANT_NETWORK);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
724
735
|
const nodeId = computeNodeId(remotePeer);
|
|
725
736
|
const peerData: PeerData = {
|
|
726
737
|
lastReceivedMsgUnixTsMs: direction === "outbound" ? 0 : now,
|
|
@@ -579,7 +579,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
579
579
|
break;
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
-
if (!blockInput.
|
|
582
|
+
if (!blockInput.hasComputedAllData()) {
|
|
583
583
|
// immediately attempt fetch of data columns from execution engine
|
|
584
584
|
chain.getBlobsTracker.triggerGetBlobs(blockInput);
|
|
585
585
|
// if we've received at least half of the columns, trigger reconstruction of the rest
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { routes } from "@lodestar/api";
|
|
2
|
-
import { CachedBeaconStateAllForks } from "@lodestar/state-transition";
|
|
3
|
-
import { Epoch, RootHex } from "@lodestar/types";
|
|
4
|
-
import { Metrics } from "../../metrics/index.js";
|
|
5
|
-
import { BlockStateCache } from "./types.js";
|
|
6
|
-
/**
|
|
7
|
-
* Old implementation of StateCache (used to call `StateContextCache`)
|
|
8
|
-
* - Prune per checkpoint so number of states ranges from 96 to 128
|
|
9
|
-
* - Keep a separate head state to make sure it is always available
|
|
10
|
-
*/
|
|
11
|
-
export declare class BlockStateCacheImpl implements BlockStateCache {
|
|
12
|
-
/**
|
|
13
|
-
* Max number of states allowed in the cache
|
|
14
|
-
*/
|
|
15
|
-
readonly maxStates: number;
|
|
16
|
-
private readonly cache;
|
|
17
|
-
/** Epoch -> Set<blockRoot> */
|
|
18
|
-
private readonly epochIndex;
|
|
19
|
-
private readonly metrics;
|
|
20
|
-
/**
|
|
21
|
-
* Strong reference to prevent head state from being pruned.
|
|
22
|
-
* null if head state is being regen and not available at the moment.
|
|
23
|
-
*/
|
|
24
|
-
private head;
|
|
25
|
-
constructor({ maxStates, metrics }: {
|
|
26
|
-
maxStates?: number;
|
|
27
|
-
metrics?: Metrics | null;
|
|
28
|
-
});
|
|
29
|
-
get(rootHex: RootHex): CachedBeaconStateAllForks | null;
|
|
30
|
-
add(item: CachedBeaconStateAllForks): void;
|
|
31
|
-
setHeadState(item: CachedBeaconStateAllForks | null): void;
|
|
32
|
-
/**
|
|
33
|
-
* Get a seed state for state reload.
|
|
34
|
-
* This is to conform to the api only as this cache is not used in n-historical state.
|
|
35
|
-
* See ./fifoBlockStateCache.ts for implementation
|
|
36
|
-
*/
|
|
37
|
-
getSeedState(): CachedBeaconStateAllForks;
|
|
38
|
-
clear(): void;
|
|
39
|
-
get size(): number;
|
|
40
|
-
/**
|
|
41
|
-
* TODO make this more robust.
|
|
42
|
-
* Without more thought, this currently breaks our assumptions about recent state availablity
|
|
43
|
-
*/
|
|
44
|
-
prune(headStateRootHex: RootHex): void;
|
|
45
|
-
/**
|
|
46
|
-
* Prune per finalized epoch.
|
|
47
|
-
*/
|
|
48
|
-
deleteAllBeforeEpoch(finalizedEpoch: Epoch): void;
|
|
49
|
-
/** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
|
|
50
|
-
dumpSummary(): routes.lodestar.StateCacheItem[];
|
|
51
|
-
getStates(): IterableIterator<CachedBeaconStateAllForks>;
|
|
52
|
-
private deleteAllEpochItems;
|
|
53
|
-
}
|
|
54
|
-
//# sourceMappingURL=blockStateCacheImpl.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"blockStateCacheImpl.d.ts","sourceRoot":"","sources":["../../../src/chain/stateCache/blockStateCacheImpl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,eAAe,CAAC;AACrC,OAAO,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAC,KAAK,EAAE,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAC,OAAO,EAAC,MAAM,wBAAwB,CAAC;AAE/C,OAAO,EAAC,eAAe,EAAC,MAAM,YAAY,CAAC;AAI3C;;;;GAIG;AACH,qBAAa,mBAAoB,YAAW,eAAe;IACzD;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgD;IACtE,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IACnE;;;OAGG;IACH,OAAO,CAAC,IAAI,CAAuE;gBAEvE,EAAC,SAAsB,EAAE,OAAO,EAAC,EAAE;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAC;IAS7F,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,yBAAyB,GAAG,IAAI;IAavD,GAAG,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI;IAgB1C,YAAY,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI,GAAG,IAAI;IAS1D;;;;OAIG;IACH,YAAY,IAAI,yBAAyB;IAIzC,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB,EAAE,OAAO,GAAG,IAAI;IAgBtC;;OAEG;IACH,oBAAoB,CAAC,cAAc,EAAE,KAAK,GAAG,IAAI;IAQjD,0DAA0D;IAC1D,WAAW,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE;IAU/C,SAAS,IAAI,gBAAgB,CAAC,yBAAyB,CAAC;IAIxD,OAAO,CAAC,mBAAmB;CAM5B"}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { toRootHex } from "@lodestar/utils";
|
|
2
|
-
import { MapTracker } from "./mapMetrics.js";
|
|
3
|
-
const MAX_STATES = 3 * 32;
|
|
4
|
-
/**
|
|
5
|
-
* Old implementation of StateCache (used to call `StateContextCache`)
|
|
6
|
-
* - Prune per checkpoint so number of states ranges from 96 to 128
|
|
7
|
-
* - Keep a separate head state to make sure it is always available
|
|
8
|
-
*/
|
|
9
|
-
export class BlockStateCacheImpl {
|
|
10
|
-
/**
|
|
11
|
-
* Max number of states allowed in the cache
|
|
12
|
-
*/
|
|
13
|
-
maxStates;
|
|
14
|
-
cache;
|
|
15
|
-
/** Epoch -> Set<blockRoot> */
|
|
16
|
-
epochIndex = new Map();
|
|
17
|
-
metrics;
|
|
18
|
-
/**
|
|
19
|
-
* Strong reference to prevent head state from being pruned.
|
|
20
|
-
* null if head state is being regen and not available at the moment.
|
|
21
|
-
*/
|
|
22
|
-
head = null;
|
|
23
|
-
constructor({ maxStates = MAX_STATES, metrics }) {
|
|
24
|
-
this.maxStates = maxStates;
|
|
25
|
-
this.cache = new MapTracker(metrics?.stateCache);
|
|
26
|
-
if (metrics) {
|
|
27
|
-
this.metrics = metrics.stateCache;
|
|
28
|
-
metrics.stateCache.size.addCollect(() => metrics.stateCache.size.set(this.cache.size));
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
get(rootHex) {
|
|
32
|
-
this.metrics?.lookups.inc();
|
|
33
|
-
const item = this.head?.stateRoot === rootHex ? this.head.state : this.cache.get(rootHex);
|
|
34
|
-
if (!item) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
this.metrics?.hits.inc();
|
|
38
|
-
this.metrics?.stateClonedCount.observe(item.clonedCount);
|
|
39
|
-
return item;
|
|
40
|
-
}
|
|
41
|
-
add(item) {
|
|
42
|
-
const key = toRootHex(item.hashTreeRoot());
|
|
43
|
-
if (this.cache.get(key)) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
this.metrics?.adds.inc();
|
|
47
|
-
this.cache.set(key, item);
|
|
48
|
-
const epoch = item.epochCtx.epoch;
|
|
49
|
-
const blockRoots = this.epochIndex.get(epoch);
|
|
50
|
-
if (blockRoots) {
|
|
51
|
-
blockRoots.add(key);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
this.epochIndex.set(epoch, new Set([key]));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
setHeadState(item) {
|
|
58
|
-
if (item) {
|
|
59
|
-
const key = toRootHex(item.hashTreeRoot());
|
|
60
|
-
this.head = { state: item, stateRoot: key };
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
this.head = null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Get a seed state for state reload.
|
|
68
|
-
* This is to conform to the api only as this cache is not used in n-historical state.
|
|
69
|
-
* See ./fifoBlockStateCache.ts for implementation
|
|
70
|
-
*/
|
|
71
|
-
getSeedState() {
|
|
72
|
-
throw Error("Not implemented for BlockStateCacheImpl");
|
|
73
|
-
}
|
|
74
|
-
clear() {
|
|
75
|
-
this.cache.clear();
|
|
76
|
-
this.epochIndex.clear();
|
|
77
|
-
}
|
|
78
|
-
get size() {
|
|
79
|
-
return this.cache.size;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* TODO make this more robust.
|
|
83
|
-
* Without more thought, this currently breaks our assumptions about recent state availablity
|
|
84
|
-
*/
|
|
85
|
-
prune(headStateRootHex) {
|
|
86
|
-
const keys = Array.from(this.cache.keys());
|
|
87
|
-
if (keys.length > this.maxStates) {
|
|
88
|
-
// object keys are stored in insertion order, delete keys starting from the front
|
|
89
|
-
for (const key of keys.slice(0, keys.length - this.maxStates)) {
|
|
90
|
-
if (key !== headStateRootHex) {
|
|
91
|
-
const item = this.cache.get(key);
|
|
92
|
-
if (item) {
|
|
93
|
-
this.epochIndex.get(item.epochCtx.epoch)?.delete(key);
|
|
94
|
-
this.cache.delete(key);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Prune per finalized epoch.
|
|
102
|
-
*/
|
|
103
|
-
deleteAllBeforeEpoch(finalizedEpoch) {
|
|
104
|
-
for (const epoch of this.epochIndex.keys()) {
|
|
105
|
-
if (epoch < finalizedEpoch) {
|
|
106
|
-
this.deleteAllEpochItems(epoch);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/** ONLY FOR DEBUGGING PURPOSES. For lodestar debug API */
|
|
111
|
-
dumpSummary() {
|
|
112
|
-
return Array.from(this.cache.entries()).map(([key, state]) => ({
|
|
113
|
-
slot: state.slot,
|
|
114
|
-
root: toRootHex(state.hashTreeRoot()),
|
|
115
|
-
reads: this.cache.readCount.get(key) ?? 0,
|
|
116
|
-
lastRead: this.cache.lastRead.get(key) ?? 0,
|
|
117
|
-
checkpointState: false,
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
getStates() {
|
|
121
|
-
return this.cache.values();
|
|
122
|
-
}
|
|
123
|
-
deleteAllEpochItems(epoch) {
|
|
124
|
-
for (const rootHex of this.epochIndex.get(epoch) || []) {
|
|
125
|
-
this.cache.delete(rootHex);
|
|
126
|
-
}
|
|
127
|
-
this.epochIndex.delete(epoch);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
//# sourceMappingURL=blockStateCacheImpl.js.map
|