1
1
import { ssz } from "@lodestar/types" ;
2
2
import { createBeaconConfig , BeaconConfig , ChainForkConfig } from "@lodestar/config" ;
3
- import { Logger } from "@lodestar/utils" ;
3
+ import { Logger , formatBytes } from "@lodestar/utils" ;
4
4
import {
5
5
isWithinWeakSubjectivityPeriod ,
6
6
ensureWithinWeakSubjectivityPeriod ,
7
7
BeaconStateAllForks ,
8
+ loadState ,
9
+ loadStateAndValidators ,
8
10
} from "@lodestar/state-transition" ;
9
11
import {
10
12
IBeaconDb ,
11
13
IBeaconNodeOptions ,
12
- initStateFromAnchorState ,
14
+ checkAndPersistAnchorState ,
13
15
initStateFromEth1 ,
14
16
getStateTypeFromBytes ,
15
17
} from "@lodestar/beacon-node" ;
@@ -25,32 +27,36 @@ import {
25
27
} from "../../networks/index.js" ;
26
28
import { BeaconArgs } from "./options.js" ;
27
29
30
+ type StateWithBytes = { state : BeaconStateAllForks ; stateBytes : Uint8Array } ;
31
+
28
32
async function initAndVerifyWeakSubjectivityState (
29
33
config : BeaconConfig ,
30
34
db : IBeaconDb ,
31
35
logger : Logger ,
32
- store : BeaconStateAllForks ,
33
- wsState : BeaconStateAllForks ,
36
+ dbStateBytes : StateWithBytes ,
37
+ wsStateBytes : StateWithBytes ,
34
38
wsCheckpoint : Checkpoint ,
35
39
opts : { ignoreWeakSubjectivityCheck ?: boolean } = { }
36
40
) : Promise < { anchorState : BeaconStateAllForks ; wsCheckpoint : Checkpoint } > {
41
+ const dbState = dbStateBytes . state ;
42
+ const wsState = wsStateBytes . state ;
37
43
// Check if the store's state and wsState are compatible
38
44
if (
39
- store . genesisTime !== wsState . genesisTime ||
40
- ! ssz . Root . equals ( store . genesisValidatorsRoot , wsState . genesisValidatorsRoot )
45
+ dbState . genesisTime !== wsState . genesisTime ||
46
+ ! ssz . Root . equals ( dbState . genesisValidatorsRoot , wsState . genesisValidatorsRoot )
41
47
) {
42
48
throw new Error (
43
49
"Db state and checkpoint state are not compatible, either clear the db or verify your checkpoint source"
44
50
) ;
45
51
}
46
52
47
53
// Pick the state which is ahead as an anchor to initialize the beacon chain
48
- let anchorState = wsState ;
54
+ let anchorState = wsStateBytes ;
49
55
let anchorCheckpoint = wsCheckpoint ;
50
56
let isCheckpointState = true ;
51
- if ( store . slot > wsState . slot ) {
52
- anchorState = store ;
53
- anchorCheckpoint = getCheckpointFromState ( store ) ;
57
+ if ( dbState . slot > wsState . slot ) {
58
+ anchorState = dbStateBytes ;
59
+ anchorCheckpoint = getCheckpointFromState ( dbState ) ;
54
60
isCheckpointState = false ;
55
61
logger . verbose (
56
62
"Db state is ahead of the provided checkpoint state, using the db state to initialize the beacon chain"
@@ -59,19 +65,19 @@ async function initAndVerifyWeakSubjectivityState(
59
65
60
66
// Throw error unless user explicitly asked not to, in testnets can happen that wss period is too small
61
67
// that even some epochs of non finalization can cause finalized checkpoint to be out of valid range
62
- const wssCheck = wrapFnError ( ( ) => ensureWithinWeakSubjectivityPeriod ( config , anchorState , anchorCheckpoint ) ) ;
68
+ const wssCheck = wrapFnError ( ( ) => ensureWithinWeakSubjectivityPeriod ( config , anchorState . state , anchorCheckpoint ) ) ;
63
69
const isWithinWeakSubjectivityPeriod = wssCheck . err === null ;
64
70
if ( ! isWithinWeakSubjectivityPeriod && ! opts . ignoreWeakSubjectivityCheck ) {
65
71
throw wssCheck . err ;
66
72
}
67
73
68
- anchorState = await initStateFromAnchorState ( config , db , logger , anchorState , {
74
+ await checkAndPersistAnchorState ( config , db , logger , anchorState . state , anchorState . stateBytes , {
69
75
isWithinWeakSubjectivityPeriod,
70
76
isCheckpointState,
71
77
} ) ;
72
78
73
79
// Return the latest anchorState but still return original wsCheckpoint to validate in backfill
74
- return { anchorState, wsCheckpoint} ;
80
+ return { anchorState : anchorState . state , wsCheckpoint} ;
75
81
}
76
82
77
83
/**
@@ -96,8 +102,20 @@ export async function initBeaconState(
96
102
}
97
103
// fetch the latest state stored in the db which will be used in all cases, if it exists, either
98
104
// i) used directly as the anchor state
99
- // ii) used during verification of a weak subjectivity state,
100
- const lastDbState = await db . stateArchive . lastValue ( ) ;
105
+ // ii) used to load and verify a weak subjectivity state,
106
+ const lastDbSlot = await db . stateArchive . lastKey ( ) ;
107
+ const stateBytes = lastDbSlot !== null ? await db . stateArchive . getBinary ( lastDbSlot ) : null ;
108
+ let lastDbState : BeaconStateAllForks | null = null ;
109
+ let lastDbValidatorsBytes : Uint8Array | null = null ;
110
+ let lastDbStateWithBytes : StateWithBytes | null = null ;
111
+ if ( stateBytes ) {
112
+ logger . verbose ( "Found the last archived state" , { slot : lastDbSlot , size : formatBytes ( stateBytes . length ) } ) ;
113
+ const { state, validatorsBytes} = loadStateAndValidators ( chainForkConfig , stateBytes ) ;
114
+ lastDbState = state ;
115
+ lastDbValidatorsBytes = validatorsBytes ;
116
+ lastDbStateWithBytes = { state, stateBytes : stateBytes } ;
117
+ }
118
+
101
119
if ( lastDbState ) {
102
120
const config = createBeaconConfig ( chainForkConfig , lastDbState . genesisValidatorsRoot ) ;
103
121
const wssCheck = isWithinWeakSubjectivityPeriod ( config , lastDbState , getCheckpointFromState ( lastDbState ) ) ;
@@ -107,27 +125,34 @@ export async function initBeaconState(
107
125
// Forcing to sync from checkpoint is only recommended if node is taking too long to sync from last db state.
108
126
// It is important to remind the user to remove this flag again unless it is absolutely necessary.
109
127
if ( wssCheck ) {
110
- logger . warn ( "Forced syncing from checkpoint even though db state is within weak subjectivity period" ) ;
128
+ logger . warn (
129
+ `Forced syncing from checkpoint even though db state at slot ${ lastDbState . slot } is within weak subjectivity period`
130
+ ) ;
111
131
logger . warn ( "Please consider removing --forceCheckpointSync flag unless absolutely necessary" ) ;
112
132
}
113
133
} else {
114
134
// All cases when we want to directly use lastDbState as the anchor state:
115
135
// - if no checkpoint sync args provided, or
116
136
// - the lastDbState is within weak subjectivity period:
117
137
if ( ( ! args . checkpointState && ! args . checkpointSyncUrl ) || wssCheck ) {
118
- const anchorState = await initStateFromAnchorState ( config , db , logger , lastDbState , {
138
+ if ( stateBytes === null ) {
139
+ // this never happens
140
+ throw Error ( `There is no stateBytes for the lastDbState at slot ${ lastDbState . slot } ` ) ;
141
+ }
142
+ await checkAndPersistAnchorState ( config , db , logger , lastDbState , stateBytes , {
119
143
isWithinWeakSubjectivityPeriod : wssCheck ,
120
144
isCheckpointState : false ,
121
145
} ) ;
122
- return { anchorState} ;
146
+ return { anchorState : lastDbState } ;
123
147
}
124
148
}
125
149
}
126
150
127
151
// See if we can sync state using checkpoint sync args or else start from genesis
128
152
if ( args . checkpointState ) {
129
153
return readWSState (
130
- lastDbState ,
154
+ lastDbStateWithBytes ,
155
+ lastDbValidatorsBytes ,
131
156
{
132
157
checkpointState : args . checkpointState ,
133
158
wssCheckpoint : args . wssCheckpoint ,
@@ -139,7 +164,8 @@ export async function initBeaconState(
139
164
) ;
140
165
} else if ( args . checkpointSyncUrl ) {
141
166
return fetchWSStateFromBeaconApi (
142
- lastDbState ,
167
+ lastDbStateWithBytes ,
168
+ lastDbValidatorsBytes ,
143
169
{
144
170
checkpointSyncUrl : args . checkpointSyncUrl ,
145
171
wssCheckpoint : args . wssCheckpoint ,
@@ -153,10 +179,10 @@ export async function initBeaconState(
153
179
const genesisStateFile = args . genesisStateFile || getGenesisFileUrl ( args . network || defaultNetwork ) ;
154
180
if ( genesisStateFile && ! args . forceGenesis ) {
155
181
const stateBytes = await downloadOrLoadFile ( genesisStateFile ) ;
156
- let anchorState = getStateTypeFromBytes ( chainForkConfig , stateBytes ) . deserializeToViewDU ( stateBytes ) ;
182
+ const anchorState = getStateTypeFromBytes ( chainForkConfig , stateBytes ) . deserializeToViewDU ( stateBytes ) ;
157
183
const config = createBeaconConfig ( chainForkConfig , anchorState . genesisValidatorsRoot ) ;
158
184
const wssCheck = isWithinWeakSubjectivityPeriod ( config , anchorState , getCheckpointFromState ( anchorState ) ) ;
159
- anchorState = await initStateFromAnchorState ( config , db , logger , anchorState , {
185
+ await checkAndPersistAnchorState ( config , db , logger , anchorState , stateBytes , {
160
186
isWithinWeakSubjectivityPeriod : wssCheck ,
161
187
isCheckpointState : true ,
162
188
} ) ;
@@ -170,7 +196,8 @@ export async function initBeaconState(
170
196
}
171
197
172
198
async function readWSState (
173
- lastDbState : BeaconStateAllForks | null ,
199
+ lastDbStateBytes : StateWithBytes | null ,
200
+ lastDbValidatorsBytes : Uint8Array | null ,
174
201
wssOpts : { checkpointState : string ; wssCheckpoint ?: string ; ignoreWeakSubjectivityCheck ?: boolean } ,
175
202
chainForkConfig : ChainForkConfig ,
176
203
db : IBeaconDb ,
@@ -180,19 +207,28 @@ async function readWSState(
180
207
// if a weak subjectivity checkpoint has been provided, it is used for additional verification
181
208
// otherwise, the state itself is used for verification (not bad, because the trusted state has been explicitly provided)
182
209
const { checkpointState, wssCheckpoint, ignoreWeakSubjectivityCheck} = wssOpts ;
210
+ const lastDbState = lastDbStateBytes ?. state ?? null ;
183
211
184
212
const stateBytes = await downloadOrLoadFile ( checkpointState ) ;
185
- const wsState = getStateTypeFromBytes ( chainForkConfig , stateBytes ) . deserializeToViewDU ( stateBytes ) ;
213
+ let wsState : BeaconStateAllForks ;
214
+ if ( lastDbState && lastDbValidatorsBytes ) {
215
+ // use lastDbState to load wsState if possible to share the same state tree
216
+ wsState = loadState ( chainForkConfig , lastDbState , stateBytes , lastDbValidatorsBytes ) . state ;
217
+ } else {
218
+ wsState = getStateTypeFromBytes ( chainForkConfig , stateBytes ) . deserializeToViewDU ( stateBytes ) ;
219
+ }
186
220
const config = createBeaconConfig ( chainForkConfig , wsState . genesisValidatorsRoot ) ;
187
- const store = lastDbState ?? wsState ;
221
+ const wsStateBytes = { state : wsState , stateBytes} ;
222
+ const store = lastDbStateBytes ?? wsStateBytes ;
188
223
const checkpoint = wssCheckpoint ? getCheckpointFromArg ( wssCheckpoint ) : getCheckpointFromState ( wsState ) ;
189
- return initAndVerifyWeakSubjectivityState ( config , db , logger , store , wsState , checkpoint , {
224
+ return initAndVerifyWeakSubjectivityState ( config , db , logger , store , wsStateBytes , checkpoint , {
190
225
ignoreWeakSubjectivityCheck,
191
226
} ) ;
192
227
}
193
228
194
229
async function fetchWSStateFromBeaconApi (
195
- lastDbState : BeaconStateAllForks | null ,
230
+ lastDbStateBytes : StateWithBytes | null ,
231
+ lastDbValidatorsBytes : Uint8Array | null ,
196
232
wssOpts : { checkpointSyncUrl : string ; wssCheckpoint ?: string ; ignoreWeakSubjectivityCheck ?: boolean } ,
197
233
chainForkConfig : ChainForkConfig ,
198
234
db : IBeaconDb ,
@@ -213,10 +249,15 @@ async function fetchWSStateFromBeaconApi(
213
249
throw e ;
214
250
}
215
251
216
- const { wsState, wsCheckpoint} = await fetchWeakSubjectivityState ( chainForkConfig , logger , wssOpts ) ;
252
+ const { wsState, wsStateBytes, wsCheckpoint} = await fetchWeakSubjectivityState ( chainForkConfig , logger , wssOpts , {
253
+ lastDbState : lastDbStateBytes ?. state ?? null ,
254
+ lastDbValidatorsBytes,
255
+ } ) ;
256
+
217
257
const config = createBeaconConfig ( chainForkConfig , wsState . genesisValidatorsRoot ) ;
218
- const store = lastDbState ?? wsState ;
219
- return initAndVerifyWeakSubjectivityState ( config , db , logger , store , wsState , wsCheckpoint , {
258
+ const wsStateWithBytes = { state : wsState , stateBytes : wsStateBytes } ;
259
+ const store = lastDbStateBytes ?? wsStateWithBytes ;
260
+ return initAndVerifyWeakSubjectivityState ( config , db , logger , store , wsStateWithBytes , wsCheckpoint , {
220
261
ignoreWeakSubjectivityCheck : wssOpts . ignoreWeakSubjectivityCheck ,
221
262
} ) ;
222
263
}
0 commit comments