@lodestar/beacon-node 1.36.0-dev.6f46a8bd20 → 1.36.0-dev.797fa46c1b
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 +41 -22
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/lodestar/index.d.ts +5 -0
- package/lib/api/impl/lodestar/index.d.ts.map +1 -1
- package/lib/api/impl/lodestar/index.js +29 -1
- package/lib/api/impl/lodestar/index.js.map +1 -1
- package/lib/api/impl/node/utils.js +1 -1
- package/lib/api/impl/node/utils.js.map +1 -1
- package/lib/chain/chain.d.ts +5 -2
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +32 -16
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts +9 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +109 -4
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -0
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/options.d.ts +0 -2
- 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/stateCache/datastore/db.d.ts +12 -0
- package/lib/chain/stateCache/datastore/db.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/db.js +70 -0
- package/lib/chain/stateCache/datastore/db.js.map +1 -1
- package/lib/chain/stateCache/datastore/file.d.ts +1 -0
- package/lib/chain/stateCache/datastore/file.d.ts.map +1 -1
- package/lib/chain/stateCache/datastore/file.js +7 -0
- package/lib/chain/stateCache/datastore/file.js.map +1 -1
- package/lib/chain/stateCache/datastore/types.d.ts +1 -0
- package/lib/chain/stateCache/datastore/types.d.ts.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/network/core/networkCore.d.ts.map +1 -1
- package/lib/network/core/networkCore.js +5 -1
- package/lib/network/core/networkCore.js.map +1 -1
- package/lib/network/gossip/encoding.js +1 -1
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/snappy_bun.d.ts +3 -0
- package/lib/network/gossip/snappy_bun.d.ts.map +1 -0
- package/lib/network/gossip/snappy_bun.js +3 -0
- package/lib/network/gossip/snappy_bun.js.map +1 -0
- package/lib/network/metadata.d.ts +1 -1
- package/lib/network/metadata.d.ts.map +1 -1
- package/lib/network/metadata.js +1 -0
- package/lib/network/metadata.js.map +1 -1
- package/lib/network/options.d.ts +0 -1
- package/lib/network/options.d.ts.map +1 -1
- package/lib/network/options.js.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js +3 -1
- package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
- package/lib/node/nodejs.d.ts +2 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +2 -1
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +2 -1
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/utils/remoteSyncType.d.ts +2 -1
- package/lib/sync/utils/remoteSyncType.d.ts.map +1 -1
- package/lib/sync/utils/remoteSyncType.js +19 -4
- package/lib/sync/utils/remoteSyncType.js.map +1 -1
- package/lib/util/blobs.d.ts +1 -1
- package/lib/util/blobs.d.ts.map +1 -1
- package/lib/util/blobs.js +53 -20
- package/lib/util/blobs.js.map +1 -1
- package/lib/util/sszBytes.d.ts +2 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +25 -0
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +19 -14
- package/src/api/impl/beacon/blocks/index.ts +47 -25
- package/src/api/impl/lodestar/index.ts +36 -1
- package/src/api/impl/node/utils.ts +1 -1
- package/src/chain/chain.ts +48 -23
- package/src/chain/forkChoice/index.ts +178 -2
- package/src/chain/interface.ts +2 -0
- package/src/chain/options.ts +0 -3
- package/src/chain/stateCache/datastore/db.ts +89 -1
- package/src/chain/stateCache/datastore/file.ts +8 -0
- package/src/chain/stateCache/datastore/types.ts +1 -0
- package/src/index.ts +2 -0
- package/src/network/core/networkCore.ts +5 -1
- package/src/network/gossip/encoding.ts +1 -1
- package/src/network/gossip/snappy_bun.ts +2 -0
- package/src/network/metadata.ts +3 -1
- package/src/network/options.ts +0 -1
- package/src/network/reqresp/handlers/beaconBlocksByRange.ts +3 -1
- package/src/node/nodejs.ts +3 -0
- package/src/sync/range/range.ts +2 -1
- package/src/sync/utils/remoteSyncType.ts +23 -4
- package/src/util/blobs.ts +64 -20
- package/src/util/sszBytes.ts +30 -0
package/src/chain/chain.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
ValidatorIndex,
|
|
38
38
|
Wei,
|
|
39
39
|
isBlindedBeaconBlock,
|
|
40
|
+
phase0,
|
|
40
41
|
} from "@lodestar/types";
|
|
41
42
|
import {Logger, fromHex, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils";
|
|
42
43
|
import {ProcessShutdownCallback} from "@lodestar/validator";
|
|
@@ -95,11 +96,13 @@ import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js";
|
|
|
95
96
|
import {SeenBlockInput} from "./seenCache/seenGossipBlockInput.js";
|
|
96
97
|
import {ShufflingCache} from "./shufflingCache.js";
|
|
97
98
|
import {BlockStateCacheImpl} from "./stateCache/blockStateCacheImpl.js";
|
|
98
|
-
import {DbCPStateDatastore} from "./stateCache/datastore/db.js";
|
|
99
|
+
import {DbCPStateDatastore, checkpointToDatastoreKey} from "./stateCache/datastore/db.js";
|
|
99
100
|
import {FileCPStateDatastore} from "./stateCache/datastore/file.js";
|
|
101
|
+
import {CPStateDatastore} from "./stateCache/datastore/types.js";
|
|
100
102
|
import {FIFOBlockStateCache} from "./stateCache/fifoBlockStateCache.js";
|
|
101
103
|
import {InMemoryCheckpointStateCache} from "./stateCache/inMemoryCheckpointsCache.js";
|
|
102
104
|
import {PersistentCheckpointStateCache} from "./stateCache/persistentCheckpointsCache.js";
|
|
105
|
+
import {CheckpointStateCache} from "./stateCache/types.js";
|
|
103
106
|
import {ValidatorMonitor} from "./validatorMonitor.js";
|
|
104
107
|
|
|
105
108
|
/**
|
|
@@ -181,6 +184,8 @@ export class BeaconChain implements IBeaconChain {
|
|
|
181
184
|
|
|
182
185
|
protected readonly blockProcessor: BlockProcessor;
|
|
183
186
|
protected readonly db: IBeaconDb;
|
|
187
|
+
// this is only available if nHistoricalStates is enabled
|
|
188
|
+
private readonly cpStateDatastore?: CPStateDatastore;
|
|
184
189
|
private abortController = new AbortController();
|
|
185
190
|
private processShutdownCallback: ProcessShutdownCallback;
|
|
186
191
|
private _earliestAvailableSlot: Slot;
|
|
@@ -210,6 +215,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
210
215
|
metrics,
|
|
211
216
|
validatorMonitor,
|
|
212
217
|
anchorState,
|
|
218
|
+
isAnchorStateFinalized,
|
|
213
219
|
eth1,
|
|
214
220
|
executionEngine,
|
|
215
221
|
executionBuilder,
|
|
@@ -226,6 +232,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
226
232
|
metrics: Metrics | null;
|
|
227
233
|
validatorMonitor: ValidatorMonitor | null;
|
|
228
234
|
anchorState: BeaconStateAllForks;
|
|
235
|
+
isAnchorStateFinalized: boolean;
|
|
229
236
|
eth1: IEth1ForBlockProduction;
|
|
230
237
|
executionEngine: IExecutionEngine;
|
|
231
238
|
executionBuilder?: IExecutionBuilder;
|
|
@@ -264,8 +271,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
264
271
|
this.seenAttestationDatas = new SeenAttestationDatas(metrics, this.opts?.attDataCacheSlotDistance);
|
|
265
272
|
|
|
266
273
|
const nodeId = computeNodeIdFromPrivateKey(privateKey);
|
|
267
|
-
const initialCustodyGroupCount =
|
|
268
|
-
opts.initialCustodyGroupCount ?? (opts.supernode ? config.NUMBER_OF_CUSTODY_GROUPS : config.CUSTODY_REQUIREMENT);
|
|
274
|
+
const initialCustodyGroupCount = opts.initialCustodyGroupCount ?? config.CUSTODY_REQUIREMENT;
|
|
269
275
|
this.metrics?.peerDas.targetCustodyGroupCount.set(initialCustodyGroupCount);
|
|
270
276
|
this.custodyConfig = new CustodyConfig({
|
|
271
277
|
nodeId,
|
|
@@ -326,23 +332,26 @@ export class BeaconChain implements IBeaconChain {
|
|
|
326
332
|
this.bufferPool = this.opts.nHistoricalStates
|
|
327
333
|
? new BufferPool(anchorState.type.tree_serializedSize(anchorState.node), metrics)
|
|
328
334
|
: null;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
335
|
+
|
|
336
|
+
let checkpointStateCache: CheckpointStateCache;
|
|
337
|
+
this.cpStateDatastore = undefined;
|
|
338
|
+
if (this.opts.nHistoricalStates) {
|
|
339
|
+
this.cpStateDatastore = fileDataStore ? new FileCPStateDatastore(dataDir) : new DbCPStateDatastore(this.db);
|
|
340
|
+
checkpointStateCache = new PersistentCheckpointStateCache(
|
|
341
|
+
{
|
|
342
|
+
metrics,
|
|
343
|
+
logger,
|
|
344
|
+
clock,
|
|
345
|
+
blockStateCache,
|
|
346
|
+
bufferPool: this.bufferPool,
|
|
347
|
+
datastore: this.cpStateDatastore,
|
|
348
|
+
},
|
|
349
|
+
this.opts
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
checkpointStateCache = new InMemoryCheckpointStateCache({metrics});
|
|
353
|
+
}
|
|
354
|
+
|
|
346
355
|
const {checkpoint} = computeAnchorCheckpoint(config, anchorState);
|
|
347
356
|
blockStateCache.add(cachedState);
|
|
348
357
|
blockStateCache.setHeadState(cachedState);
|
|
@@ -353,6 +362,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
353
362
|
emitter,
|
|
354
363
|
clock.currentSlot,
|
|
355
364
|
cachedState,
|
|
365
|
+
isAnchorStateFinalized,
|
|
356
366
|
opts,
|
|
357
367
|
this.justifiedBalancesGetter.bind(this),
|
|
358
368
|
metrics,
|
|
@@ -604,6 +614,20 @@ export class BeaconChain implements IBeaconChain {
|
|
|
604
614
|
return data && {state: data, executionOptimistic: false, finalized: true};
|
|
605
615
|
}
|
|
606
616
|
|
|
617
|
+
async getPersistedCheckpointState(checkpoint?: phase0.Checkpoint): Promise<Uint8Array | null> {
|
|
618
|
+
if (!this.cpStateDatastore) {
|
|
619
|
+
throw new Error("n-historical-state flag is not enabled");
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
if (checkpoint == null) {
|
|
623
|
+
// return the last safe checkpoint state by default
|
|
624
|
+
return this.cpStateDatastore.readLatestSafe();
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const persistedKey = checkpointToDatastoreKey(checkpoint);
|
|
628
|
+
return this.cpStateDatastore.read(persistedKey);
|
|
629
|
+
}
|
|
630
|
+
|
|
607
631
|
getStateByCheckpoint(
|
|
608
632
|
checkpoint: CheckpointWithHex
|
|
609
633
|
): {state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null {
|
|
@@ -1216,9 +1240,10 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1216
1240
|
}
|
|
1217
1241
|
|
|
1218
1242
|
private async updateValidatorsCustodyRequirement(finalizedCheckpoint: CheckpointWithHex): Promise<void> {
|
|
1219
|
-
if (this.
|
|
1220
|
-
//
|
|
1221
|
-
// of all custody groups
|
|
1243
|
+
if (this.custodyConfig.targetCustodyGroupCount === this.config.NUMBER_OF_CUSTODY_GROUPS) {
|
|
1244
|
+
// Custody requirements can only be increased, we can disable dynamic custody updates
|
|
1245
|
+
// if the node already maintains custody of all custody groups in case it is configured
|
|
1246
|
+
// as a supernode or has validators attached with a total effective balance of at least 4096 ETH.
|
|
1222
1247
|
return;
|
|
1223
1248
|
}
|
|
1224
1249
|
|
|
@@ -5,17 +5,22 @@ import {
|
|
|
5
5
|
ForkChoiceStore,
|
|
6
6
|
JustifiedBalancesGetter,
|
|
7
7
|
ProtoArray,
|
|
8
|
+
ProtoBlock,
|
|
8
9
|
ForkChoiceOpts as RawForkChoiceOpts,
|
|
9
10
|
} from "@lodestar/fork-choice";
|
|
11
|
+
import {ZERO_HASH_HEX} from "@lodestar/params";
|
|
10
12
|
import {
|
|
11
13
|
CachedBeaconStateAllForks,
|
|
12
14
|
DataAvailabilityStatus,
|
|
13
15
|
computeAnchorCheckpoint,
|
|
16
|
+
computeEpochAtSlot,
|
|
17
|
+
computeStartSlotAtEpoch,
|
|
18
|
+
getBlockRootAtSlot,
|
|
14
19
|
getEffectiveBalanceIncrementsZeroInactive,
|
|
15
20
|
isExecutionStateType,
|
|
16
21
|
isMergeTransitionComplete,
|
|
17
22
|
} from "@lodestar/state-transition";
|
|
18
|
-
import {Slot} from "@lodestar/types";
|
|
23
|
+
import {Slot, ssz} from "@lodestar/types";
|
|
19
24
|
import {Logger, toRootHex} from "@lodestar/utils";
|
|
20
25
|
import {GENESIS_SLOT} from "../../constants/index.js";
|
|
21
26
|
import {Metrics} from "../../metrics/index.js";
|
|
@@ -35,6 +40,43 @@ export enum ForkchoiceCaller {
|
|
|
35
40
|
* Fork Choice extended with a ChainEventEmitter
|
|
36
41
|
*/
|
|
37
42
|
export function initializeForkChoice(
|
|
43
|
+
config: ChainForkConfig,
|
|
44
|
+
emitter: ChainEventEmitter,
|
|
45
|
+
currentSlot: Slot,
|
|
46
|
+
state: CachedBeaconStateAllForks,
|
|
47
|
+
isFinalizedState: boolean,
|
|
48
|
+
opts: ForkChoiceOpts,
|
|
49
|
+
justifiedBalancesGetter: JustifiedBalancesGetter,
|
|
50
|
+
metrics: Metrics | null,
|
|
51
|
+
logger?: Logger
|
|
52
|
+
): ForkChoice {
|
|
53
|
+
return isFinalizedState
|
|
54
|
+
? initializeForkChoiceFromFinalizedState(
|
|
55
|
+
config,
|
|
56
|
+
emitter,
|
|
57
|
+
currentSlot,
|
|
58
|
+
state,
|
|
59
|
+
opts,
|
|
60
|
+
justifiedBalancesGetter,
|
|
61
|
+
metrics,
|
|
62
|
+
logger
|
|
63
|
+
)
|
|
64
|
+
: initializeForkChoiceFromUnfinalizedState(
|
|
65
|
+
config,
|
|
66
|
+
emitter,
|
|
67
|
+
currentSlot,
|
|
68
|
+
state,
|
|
69
|
+
opts,
|
|
70
|
+
justifiedBalancesGetter,
|
|
71
|
+
metrics,
|
|
72
|
+
logger
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initialize forkchoice from a finalized state.
|
|
78
|
+
*/
|
|
79
|
+
export function initializeForkChoiceFromFinalizedState(
|
|
38
80
|
config: ChainForkConfig,
|
|
39
81
|
emitter: ChainEventEmitter,
|
|
40
82
|
currentSlot: Slot,
|
|
@@ -82,7 +124,7 @@ export function initializeForkChoice(
|
|
|
82
124
|
parentRoot: toRootHex(blockHeader.parentRoot),
|
|
83
125
|
stateRoot: toRootHex(blockHeader.stateRoot),
|
|
84
126
|
blockRoot: toRootHex(checkpoint.root),
|
|
85
|
-
timeliness: true, //
|
|
127
|
+
timeliness: true, // Optimistically assume is timely
|
|
86
128
|
|
|
87
129
|
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
88
130
|
justifiedRoot: toRootHex(justifiedCheckpoint.root),
|
|
@@ -105,6 +147,140 @@ export function initializeForkChoice(
|
|
|
105
147
|
},
|
|
106
148
|
currentSlot
|
|
107
149
|
),
|
|
150
|
+
state.validators.length,
|
|
151
|
+
metrics,
|
|
152
|
+
opts,
|
|
153
|
+
logger
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Initialize forkchoice from an unfinalized state.
|
|
159
|
+
*/
|
|
160
|
+
export function initializeForkChoiceFromUnfinalizedState(
|
|
161
|
+
config: ChainForkConfig,
|
|
162
|
+
emitter: ChainEventEmitter,
|
|
163
|
+
currentSlot: Slot,
|
|
164
|
+
unfinalizedState: CachedBeaconStateAllForks,
|
|
165
|
+
opts: ForkChoiceOpts,
|
|
166
|
+
justifiedBalancesGetter: JustifiedBalancesGetter,
|
|
167
|
+
metrics: Metrics | null,
|
|
168
|
+
logger?: Logger
|
|
169
|
+
): ForkChoice {
|
|
170
|
+
const {blockHeader} = computeAnchorCheckpoint(config, unfinalizedState);
|
|
171
|
+
const finalizedCheckpoint = unfinalizedState.finalizedCheckpoint.toValue();
|
|
172
|
+
const justifiedCheckpoint = unfinalizedState.currentJustifiedCheckpoint.toValue();
|
|
173
|
+
const headRoot = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader));
|
|
174
|
+
|
|
175
|
+
const logCtx = {
|
|
176
|
+
currentSlot: currentSlot,
|
|
177
|
+
stateSlot: unfinalizedState.slot,
|
|
178
|
+
headSlot: blockHeader.slot,
|
|
179
|
+
headRoot: headRoot,
|
|
180
|
+
finalizedEpoch: finalizedCheckpoint.epoch,
|
|
181
|
+
finalizedRoot: toRootHex(finalizedCheckpoint.root),
|
|
182
|
+
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
183
|
+
justifiedRoot: toRootHex(justifiedCheckpoint.root),
|
|
184
|
+
};
|
|
185
|
+
logger?.warn("Initializing fork choice from unfinalized state", logCtx);
|
|
186
|
+
|
|
187
|
+
// this is not the justified state, but there is no other ways to get justified balances
|
|
188
|
+
const justifiedBalances = getEffectiveBalanceIncrementsZeroInactive(unfinalizedState);
|
|
189
|
+
const store = new ForkChoiceStore(
|
|
190
|
+
currentSlot,
|
|
191
|
+
justifiedCheckpoint,
|
|
192
|
+
finalizedCheckpoint,
|
|
193
|
+
justifiedBalances,
|
|
194
|
+
justifiedBalancesGetter,
|
|
195
|
+
{
|
|
196
|
+
onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
|
|
197
|
+
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// this is the same to the finalized state
|
|
202
|
+
const headBlock: ProtoBlock = {
|
|
203
|
+
slot: blockHeader.slot,
|
|
204
|
+
parentRoot: toRootHex(blockHeader.parentRoot),
|
|
205
|
+
stateRoot: toRootHex(blockHeader.stateRoot),
|
|
206
|
+
blockRoot: headRoot,
|
|
207
|
+
targetRoot: headRoot,
|
|
208
|
+
timeliness: true, // Optimistically assume is timely
|
|
209
|
+
|
|
210
|
+
justifiedEpoch: justifiedCheckpoint.epoch,
|
|
211
|
+
justifiedRoot: toRootHex(justifiedCheckpoint.root),
|
|
212
|
+
finalizedEpoch: finalizedCheckpoint.epoch,
|
|
213
|
+
finalizedRoot: toRootHex(finalizedCheckpoint.root),
|
|
214
|
+
unrealizedJustifiedEpoch: justifiedCheckpoint.epoch,
|
|
215
|
+
unrealizedJustifiedRoot: toRootHex(justifiedCheckpoint.root),
|
|
216
|
+
unrealizedFinalizedEpoch: finalizedCheckpoint.epoch,
|
|
217
|
+
unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root),
|
|
218
|
+
|
|
219
|
+
...(isExecutionStateType(unfinalizedState) && isMergeTransitionComplete(unfinalizedState)
|
|
220
|
+
? {
|
|
221
|
+
executionPayloadBlockHash: toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash),
|
|
222
|
+
executionPayloadNumber: unfinalizedState.latestExecutionPayloadHeader.blockNumber,
|
|
223
|
+
executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing,
|
|
224
|
+
}
|
|
225
|
+
: {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}),
|
|
226
|
+
|
|
227
|
+
dataAvailabilityStatus: DataAvailabilityStatus.PreData,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const parentSlot = blockHeader.slot - 1;
|
|
231
|
+
const parentEpoch = computeEpochAtSlot(parentSlot);
|
|
232
|
+
// parent of head block
|
|
233
|
+
const parentBlock: ProtoBlock = {
|
|
234
|
+
...headBlock,
|
|
235
|
+
slot: parentSlot,
|
|
236
|
+
// link this to the dummy justified block
|
|
237
|
+
parentRoot: toRootHex(justifiedCheckpoint.root),
|
|
238
|
+
// dummy data, we're not able to regen state before headBlock
|
|
239
|
+
stateRoot: ZERO_HASH_HEX,
|
|
240
|
+
blockRoot: headBlock.parentRoot,
|
|
241
|
+
targetRoot: toRootHex(getBlockRootAtSlot(unfinalizedState, computeStartSlotAtEpoch(parentEpoch))),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const justifiedBlock: ProtoBlock = {
|
|
245
|
+
...headBlock,
|
|
246
|
+
slot: computeStartSlotAtEpoch(justifiedCheckpoint.epoch),
|
|
247
|
+
// link this to the finalized root so that getAncestors can find the finalized block
|
|
248
|
+
parentRoot: toRootHex(finalizedCheckpoint.root),
|
|
249
|
+
// dummy data, we're not able to regen state before headBlock
|
|
250
|
+
stateRoot: ZERO_HASH_HEX,
|
|
251
|
+
blockRoot: toRootHex(justifiedCheckpoint.root),
|
|
252
|
+
// same to blockRoot
|
|
253
|
+
targetRoot: toRootHex(justifiedCheckpoint.root),
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const finalizedBlock: ProtoBlock = {
|
|
257
|
+
...headBlock,
|
|
258
|
+
slot: computeStartSlotAtEpoch(finalizedCheckpoint.epoch),
|
|
259
|
+
// we don't care parent of finalized block
|
|
260
|
+
parentRoot: ZERO_HASH_HEX,
|
|
261
|
+
// dummy data, we're not able to regen state before headBlock
|
|
262
|
+
stateRoot: ZERO_HASH_HEX,
|
|
263
|
+
blockRoot: toRootHex(finalizedCheckpoint.root),
|
|
264
|
+
// same to blockRoot
|
|
265
|
+
targetRoot: toRootHex(finalizedCheckpoint.root),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const protoArray = ProtoArray.initialize(finalizedBlock, currentSlot);
|
|
269
|
+
protoArray.onBlock(justifiedBlock, currentSlot);
|
|
270
|
+
protoArray.onBlock(parentBlock, currentSlot);
|
|
271
|
+
protoArray.onBlock(headBlock, currentSlot);
|
|
272
|
+
|
|
273
|
+
logger?.verbose("Initialized protoArray successfully", {...logCtx, length: protoArray.length()});
|
|
274
|
+
|
|
275
|
+
// forkchoiceConstructor is only used for some test cases
|
|
276
|
+
// production code use ForkChoice constructor directly
|
|
277
|
+
const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice;
|
|
278
|
+
|
|
279
|
+
return new forkchoiceConstructor(
|
|
280
|
+
config,
|
|
281
|
+
store,
|
|
282
|
+
protoArray,
|
|
283
|
+
unfinalizedState.validators.length,
|
|
108
284
|
metrics,
|
|
109
285
|
opts,
|
|
110
286
|
logger
|
package/src/chain/interface.ts
CHANGED
|
@@ -178,6 +178,8 @@ export interface IBeaconChain {
|
|
|
178
178
|
stateRoot: RootHex,
|
|
179
179
|
opts?: StateGetOpts
|
|
180
180
|
): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null>;
|
|
181
|
+
/** Return serialized bytes of a persisted checkpoint state */
|
|
182
|
+
getPersistedCheckpointState(checkpoint?: phase0.Checkpoint): Promise<Uint8Array | null>;
|
|
181
183
|
/** Returns a cached state by checkpoint */
|
|
182
184
|
getStateByCheckpoint(
|
|
183
185
|
checkpoint: CheckpointWithHex
|
package/src/chain/options.ts
CHANGED
|
@@ -41,8 +41,6 @@ export type IChainOptions = BlockProcessOpts &
|
|
|
41
41
|
maxCachedBlobSidecars?: number;
|
|
42
42
|
/** Max number of produced block roots (blinded or full) cached for broadcast validations */
|
|
43
43
|
maxCachedProducedRoots?: number;
|
|
44
|
-
/** Subscribe to and custody all data column sidecar subnets */
|
|
45
|
-
supernode?: boolean;
|
|
46
44
|
initialCustodyGroupCount?: number;
|
|
47
45
|
broadcastValidationStrictness?: string;
|
|
48
46
|
minSameMessageSignatureSetsToBatch: number;
|
|
@@ -118,7 +116,6 @@ export const defaultChainOptions: IChainOptions = {
|
|
|
118
116
|
archiveMode: DEFAULT_ARCHIVE_MODE,
|
|
119
117
|
pruneHistory: false,
|
|
120
118
|
emitPayloadAttributes: false,
|
|
121
|
-
supernode: false,
|
|
122
119
|
// for gossip block validation, it's unlikely we see a reorg with 32 slots
|
|
123
120
|
// for attestation validation, having this value ensures we don't have to regen states most of the time
|
|
124
121
|
maxSkipSlots: 32,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {SLOTS_PER_EPOCH} from "@lodestar/params";
|
|
2
|
+
import {Epoch, phase0, ssz} from "@lodestar/types";
|
|
3
|
+
import {MapDef} from "@lodestar/utils";
|
|
2
4
|
import {IBeaconDb} from "../../../db/interface.js";
|
|
5
|
+
import {
|
|
6
|
+
getLastProcessedSlotFromBeaconStateSerialized,
|
|
7
|
+
getSlotFromBeaconStateSerialized,
|
|
8
|
+
} from "../../../util/sszBytes.js";
|
|
3
9
|
import {CPStateDatastore, DatastoreKey} from "./types.js";
|
|
4
10
|
|
|
5
11
|
/**
|
|
@@ -22,6 +28,13 @@ export class DbCPStateDatastore implements CPStateDatastore {
|
|
|
22
28
|
return this.db.checkpointState.getBinary(serializedCheckpoint);
|
|
23
29
|
}
|
|
24
30
|
|
|
31
|
+
async readLatestSafe(): Promise<Uint8Array | null> {
|
|
32
|
+
const allKeys = await this.readKeys();
|
|
33
|
+
if (allKeys.length === 0) return null;
|
|
34
|
+
|
|
35
|
+
return getLatestSafeDatastoreKey(allKeys, this.read.bind(this));
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
async readKeys(): Promise<DatastoreKey[]> {
|
|
26
39
|
return this.db.checkpointState.keys();
|
|
27
40
|
}
|
|
@@ -34,3 +47,78 @@ export function datastoreKeyToCheckpoint(key: DatastoreKey): phase0.Checkpoint {
|
|
|
34
47
|
export function checkpointToDatastoreKey(cp: phase0.Checkpoint): DatastoreKey {
|
|
35
48
|
return ssz.phase0.Checkpoint.serialize(cp);
|
|
36
49
|
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the latest safe checkpoint state the node can use to boot from
|
|
53
|
+
* - it should be the checkpoint state that's unique in its epoch
|
|
54
|
+
* - its last processed block slot should be at epoch boundary or last slot of previous epoch
|
|
55
|
+
* - state slot should be at epoch boundary
|
|
56
|
+
* - state slot should be equal to epoch * SLOTS_PER_EPOCH
|
|
57
|
+
*
|
|
58
|
+
* return the serialized data of Current Root Checkpoint State (CRCS) or Previous Root Checkpoint State (PRCS)
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
export async function getLatestSafeDatastoreKey(
|
|
62
|
+
allKeys: DatastoreKey[],
|
|
63
|
+
readFn: (key: DatastoreKey) => Promise<Uint8Array | null>
|
|
64
|
+
): Promise<Uint8Array | null> {
|
|
65
|
+
const checkpointsByEpoch = new MapDef<Epoch, DatastoreKey[]>(() => []);
|
|
66
|
+
for (const key of allKeys) {
|
|
67
|
+
const cp = datastoreKeyToCheckpoint(key);
|
|
68
|
+
checkpointsByEpoch.getOrDefault(cp.epoch).push(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const dataStoreKeyByEpoch: Map<Epoch, DatastoreKey> = new Map();
|
|
72
|
+
for (const [epoch, keys] of checkpointsByEpoch.entries()) {
|
|
73
|
+
// only consider epochs with a single checkpoint to avoid ambiguity from forks
|
|
74
|
+
if (keys.length === 1) {
|
|
75
|
+
dataStoreKeyByEpoch.set(epoch, keys[0]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const epochsDesc = Array.from(dataStoreKeyByEpoch.keys()).sort((a, b) => b - a);
|
|
80
|
+
for (const epoch of epochsDesc) {
|
|
81
|
+
const datastoreKey = dataStoreKeyByEpoch.get(epoch);
|
|
82
|
+
if (datastoreKey == null) {
|
|
83
|
+
// should not happen
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const stateBytes = await readFn(datastoreKey);
|
|
88
|
+
if (stateBytes == null) {
|
|
89
|
+
// should not happen
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const lastProcessedSlot = getLastProcessedSlotFromBeaconStateSerialized(stateBytes);
|
|
94
|
+
if (lastProcessedSlot == null) {
|
|
95
|
+
// cannot extract last processed slot from serialized state, skip
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const stateSlot = getSlotFromBeaconStateSerialized(stateBytes);
|
|
100
|
+
if (stateSlot == null) {
|
|
101
|
+
// cannot extract slot from serialized state, skip
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (lastProcessedSlot !== stateSlot && lastProcessedSlot !== stateSlot - 1) {
|
|
106
|
+
// not CRCS or PRCS, skip
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (stateSlot % SLOTS_PER_EPOCH !== 0) {
|
|
111
|
+
// not at epoch boundary, skip
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (stateSlot !== SLOTS_PER_EPOCH * epoch) {
|
|
116
|
+
// should not happen after above checks, but just to be safe
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return stateBytes;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import {phase0, ssz} from "@lodestar/types";
|
|
3
3
|
import {fromHex, toHex} from "@lodestar/utils";
|
|
4
4
|
import {ensureDir, readFile, readFileNames, removeFile, writeIfNotExist} from "../../../util/file.js";
|
|
5
|
+
import {getLatestSafeDatastoreKey} from "./db.js";
|
|
5
6
|
import {CPStateDatastore, DatastoreKey} from "./types.js";
|
|
6
7
|
|
|
7
8
|
const CHECKPOINT_STATES_FOLDER = "checkpoint_states";
|
|
@@ -44,6 +45,13 @@ export class FileCPStateDatastore implements CPStateDatastore {
|
|
|
44
45
|
return readFile(filePath);
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
async readLatestSafe(): Promise<Uint8Array | null> {
|
|
49
|
+
const allKeys = await this.readKeys();
|
|
50
|
+
if (allKeys.length === 0) return null;
|
|
51
|
+
|
|
52
|
+
return getLatestSafeDatastoreKey(allKeys, this.read.bind(this));
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
async readKeys(): Promise<DatastoreKey[]> {
|
|
48
56
|
const fileNames = await readFileNames(this.folderPath);
|
|
49
57
|
return fileNames
|
|
@@ -8,6 +8,7 @@ export interface CPStateDatastore {
|
|
|
8
8
|
write: (cpKey: phase0.Checkpoint, stateBytes: Uint8Array) => Promise<DatastoreKey>;
|
|
9
9
|
remove: (key: DatastoreKey) => Promise<void>;
|
|
10
10
|
read: (key: DatastoreKey) => Promise<Uint8Array | null>;
|
|
11
|
+
readLatestSafe: () => Promise<Uint8Array | null>;
|
|
11
12
|
readKeys: () => Promise<DatastoreKey[]>;
|
|
12
13
|
init?: () => Promise<void>;
|
|
13
14
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
export type {RestApiServerMetrics, RestApiServerModules, RestApiServerOpts} from "./api/rest/base.js";
|
|
4
4
|
export {RestApiServer} from "./api/rest/base.js";
|
|
5
5
|
export {checkAndPersistAnchorState, initStateFromDb, initStateFromEth1} from "./chain/index.js";
|
|
6
|
+
export {DbCPStateDatastore} from "./chain/stateCache/datastore/db.js";
|
|
7
|
+
export {FileCPStateDatastore} from "./chain/stateCache/datastore/file.js";
|
|
6
8
|
export {BeaconDb, type IBeaconDb} from "./db/index.js";
|
|
7
9
|
export {Eth1Provider, type IEth1Provider} from "./eth1/index.js";
|
|
8
10
|
// Export metrics utilities to de-duplicate validator metrics
|
|
@@ -553,7 +553,11 @@ export class NetworkCore implements INetworkCore {
|
|
|
553
553
|
// On fork boundary transition
|
|
554
554
|
if (epoch === nextBoundaryEpoch) {
|
|
555
555
|
// updateEth2Field() MUST be called with clock epoch, onEpoch event is emitted in response to clock events
|
|
556
|
-
this.metadata.updateEth2Field(epoch);
|
|
556
|
+
const {forkDigest} = this.metadata.updateEth2Field(epoch);
|
|
557
|
+
// Update local status to reflect the new fork digest, otherwise we will disconnect peers that re-status us
|
|
558
|
+
// right after the fork transition due to incompatible forks as our fork digest is stale since we only
|
|
559
|
+
// update it once we import a new head or when emitting update status event.
|
|
560
|
+
this.statusCache.update({...this.statusCache.get(), forkDigest});
|
|
557
561
|
this.reqResp.registerProtocolsAtBoundary(nextBoundary);
|
|
558
562
|
}
|
|
559
563
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {Message} from "@libp2p/interface";
|
|
2
|
-
import {compress, uncompress} from "snappyjs";
|
|
3
2
|
import xxhashFactory from "xxhash-wasm";
|
|
3
|
+
import {compress, uncompress} from "#snappy";
|
|
4
4
|
import {digest} from "@chainsafe/as-sha256";
|
|
5
5
|
import {RPC} from "@chainsafe/libp2p-gossipsub/message";
|
|
6
6
|
import {DataTransform} from "@chainsafe/libp2p-gossipsub/types";
|
package/src/network/metadata.ts
CHANGED
|
@@ -126,7 +126,7 @@ export class MetadataController {
|
|
|
126
126
|
* 2. Network MUST call this method on fork transition.
|
|
127
127
|
* Current Clock implementation ensures no race conditions, epoch is correct if re-fetched
|
|
128
128
|
*/
|
|
129
|
-
updateEth2Field(epoch: Epoch):
|
|
129
|
+
updateEth2Field(epoch: Epoch): phase0.ENRForkID {
|
|
130
130
|
const config = this.networkConfig.config;
|
|
131
131
|
const enrForkId = getENRForkID(config, epoch);
|
|
132
132
|
const {forkDigest, nextForkVersion, nextForkEpoch} = enrForkId;
|
|
@@ -143,6 +143,8 @@ export class MetadataController {
|
|
|
143
143
|
: ssz.ForkDigest.defaultValue();
|
|
144
144
|
this.onSetValue(ENRKey.nfd, nextForkDigest);
|
|
145
145
|
this.logger.debug("Updated nfd field in ENR", {nextForkDigest: toHex(nextForkDigest)});
|
|
146
|
+
|
|
147
|
+
return enrForkId;
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
|
package/src/network/options.ts
CHANGED
|
@@ -19,7 +19,9 @@ export async function* onBeaconBlocksByRange(
|
|
|
19
19
|
|
|
20
20
|
const finalized = db.blockArchive;
|
|
21
21
|
const unfinalized = db.block;
|
|
22
|
-
|
|
22
|
+
// in the case of initializing from a non-finalized state, we don't have the finalized block so this api does not work
|
|
23
|
+
// chain.forkChoice.getFinalizeBlock().slot
|
|
24
|
+
const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
|
|
23
25
|
|
|
24
26
|
// Finalized range of blocks
|
|
25
27
|
if (startSlot <= finalizedSlot) {
|
package/src/node/nodejs.ts
CHANGED
|
@@ -53,6 +53,7 @@ export type BeaconNodeInitModules = {
|
|
|
53
53
|
dataDir: string;
|
|
54
54
|
peerStoreDir?: string;
|
|
55
55
|
anchorState: BeaconStateAllForks;
|
|
56
|
+
isAnchorStateFinalized: boolean;
|
|
56
57
|
wsCheckpoint?: phase0.Checkpoint;
|
|
57
58
|
metricsRegistries?: Registry[];
|
|
58
59
|
};
|
|
@@ -154,6 +155,7 @@ export class BeaconNode {
|
|
|
154
155
|
dataDir,
|
|
155
156
|
peerStoreDir,
|
|
156
157
|
anchorState,
|
|
158
|
+
isAnchorStateFinalized,
|
|
157
159
|
wsCheckpoint,
|
|
158
160
|
metricsRegistries = [],
|
|
159
161
|
}: BeaconNodeInitModules): Promise<T> {
|
|
@@ -217,6 +219,7 @@ export class BeaconNode {
|
|
|
217
219
|
metrics,
|
|
218
220
|
validatorMonitor,
|
|
219
221
|
anchorState,
|
|
222
|
+
isAnchorStateFinalized,
|
|
220
223
|
eth1: initializeEth1ForBlockProduction(opts.eth1, {
|
|
221
224
|
config,
|
|
222
225
|
db,
|
package/src/sync/range/range.ts
CHANGED
|
@@ -114,13 +114,14 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
|
|
|
114
114
|
*/
|
|
115
115
|
addPeer(peerId: PeerIdStr, localStatus: Status, peerStatus: Status): void {
|
|
116
116
|
// Compute if we should do a Finalized or Head sync with this peer
|
|
117
|
-
const {syncType, startEpoch, target} = getRangeSyncTarget(localStatus, peerStatus, this.chain
|
|
117
|
+
const {syncType, startEpoch, target} = getRangeSyncTarget(localStatus, peerStatus, this.chain);
|
|
118
118
|
this.logger.debug("Sync peer joined", {
|
|
119
119
|
peer: peerId,
|
|
120
120
|
syncType,
|
|
121
121
|
startEpoch,
|
|
122
122
|
targetSlot: target.slot,
|
|
123
123
|
targetRoot: toRootHex(target.root),
|
|
124
|
+
localHeadSlot: localStatus.headSlot,
|
|
124
125
|
earliestAvailableSlot: (peerStatus as fulu.Status).earliestAvailableSlot ?? Infinity,
|
|
125
126
|
});
|
|
126
127
|
|