@@ -1199,48 +1199,72 @@ async fn attesting_to_optimistic_head() {
1199
1199
get_aggregated_by_slot_and_root ( ) . unwrap ( ) ;
1200
1200
}
1201
1201
1202
- #[ tokio:: test]
1203
- async fn recover_from_invalid_head ( ) {
1204
- let mut rig = InvalidPayloadRig :: new ( ) . enable_attestations ( ) ;
1205
- rig. move_to_terminal_block ( ) ;
1206
- rig. import_block ( Payload :: Valid ) . await ; // Import a valid transition block.
1202
+ /// Helper for running tests where we generate a chain with an invalid head and then some
1203
+ /// `fork_blocks` to recover it.
1204
+ struct InvalidHeadSetup {
1205
+ rig : InvalidPayloadRig ,
1206
+ fork_blocks : Vec < Arc < SignedBeaconBlock < E > > > ,
1207
+ invalid_head : CachedHead < E > ,
1208
+ }
1207
1209
1208
- // Import blocks until the first time the chain finalizes.
1209
- while rig. cached_head ( ) . finalized_checkpoint ( ) . epoch == 0 {
1210
- rig. import_block ( Payload :: Syncing ) . await ;
1211
- }
1210
+ impl InvalidHeadSetup {
1211
+ async fn new ( ) -> InvalidHeadSetup {
1212
+ let mut rig = InvalidPayloadRig :: new ( ) . enable_attestations ( ) ;
1213
+ rig. move_to_terminal_block ( ) ;
1214
+ rig. import_block ( Payload :: Valid ) . await ; // Import a valid transition block.
1212
1215
1213
- let invalid_head = rig. cached_head ( ) ;
1216
+ // Import blocks until the first time the chain finalizes.
1217
+ while rig. cached_head ( ) . finalized_checkpoint ( ) . epoch == 0 {
1218
+ rig. import_block ( Payload :: Syncing ) . await ;
1219
+ }
1214
1220
1215
- // Invalidate the head block.
1216
- rig. invalidate_manually ( invalid_head. head_block_root ( ) )
1217
- . await ;
1218
- assert ! ( rig
1219
- . canonical_head( )
1220
- . head_execution_status( )
1221
- . unwrap( )
1222
- . is_invalid( ) ) ;
1221
+ let invalid_head = rig. cached_head ( ) ;
1223
1222
1224
- // Finding a new head should fail since the only possible head is not valid.
1225
- rig. assert_get_head_error_contains ( "InvalidBestNode" ) ;
1223
+ // Invalidate the head block.
1224
+ rig. invalidate_manually ( invalid_head. head_block_root ( ) )
1225
+ . await ;
1226
+ assert ! ( rig
1227
+ . canonical_head( )
1228
+ . head_execution_status( )
1229
+ . unwrap( )
1230
+ . is_invalid( ) ) ;
1226
1231
1227
- // Build three "fork" blocks that conflict with the current canonical head. Don't apply them to
1228
- // the chain yet.
1229
- let mut fork_blocks = vec ! [ ] ;
1230
- let mut parent_state = rig
1231
- . harness
1232
- . chain
1233
- . state_at_slot (
1234
- invalid_head. head_slot ( ) - 3 ,
1235
- StateSkipConfig :: WithStateRoots ,
1236
- )
1237
- . unwrap ( ) ;
1238
- for _ in 0 ..3 {
1239
- let slot = parent_state. slot ( ) + 1 ;
1240
- let ( fork_block, post_state) = rig. harness . make_block ( parent_state, slot) . await ;
1241
- parent_state = post_state;
1242
- fork_blocks. push ( Arc :: new ( fork_block) )
1232
+ // Finding a new head should fail since the only possible head is not valid.
1233
+ rig. assert_get_head_error_contains ( "InvalidBestNode" ) ;
1234
+
1235
+ // Build three "fork" blocks that conflict with the current canonical head. Don't apply them to
1236
+ // the chain yet.
1237
+ let mut fork_blocks = vec ! [ ] ;
1238
+ let mut parent_state = rig
1239
+ . harness
1240
+ . chain
1241
+ . state_at_slot (
1242
+ invalid_head. head_slot ( ) - 3 ,
1243
+ StateSkipConfig :: WithStateRoots ,
1244
+ )
1245
+ . unwrap ( ) ;
1246
+ for _ in 0 ..3 {
1247
+ let slot = parent_state. slot ( ) + 1 ;
1248
+ let ( fork_block, post_state) = rig. harness . make_block ( parent_state, slot) . await ;
1249
+ parent_state = post_state;
1250
+ fork_blocks. push ( Arc :: new ( fork_block) )
1251
+ }
1252
+
1253
+ Self {
1254
+ rig,
1255
+ fork_blocks,
1256
+ invalid_head,
1257
+ }
1243
1258
}
1259
+ }
1260
+
1261
+ #[ tokio:: test]
1262
+ async fn recover_from_invalid_head_by_importing_blocks ( ) {
1263
+ let InvalidHeadSetup {
1264
+ rig,
1265
+ fork_blocks,
1266
+ invalid_head,
1267
+ } = InvalidHeadSetup :: new ( ) . await ;
1244
1268
1245
1269
// Import the first two blocks, they should not become the head.
1246
1270
for i in 0 ..2 {
@@ -1300,3 +1324,29 @@ async fn recover_from_invalid_head() {
1300
1324
. unwrap ( ) ;
1301
1325
assert_eq ! ( manual_get_head, new_head. head_block_root( ) , ) ;
1302
1326
}
1327
+
1328
+ #[ tokio:: test]
1329
+ async fn recover_from_invalid_head_after_reboot ( ) {
1330
+ let InvalidHeadSetup {
1331
+ rig,
1332
+ fork_blocks,
1333
+ invalid_head,
1334
+ } = InvalidHeadSetup :: new ( ) . await ;
1335
+
1336
+ // Forcefully persist the head and fork choice.
1337
+ rig. harness . chain . persist_head_and_fork_choice ( ) . unwrap ( ) ;
1338
+
1339
+ let resumed = BeaconChainHarness :: builder ( MainnetEthSpec )
1340
+ . default_spec ( )
1341
+ . deterministic_keypairs ( VALIDATOR_COUNT )
1342
+ . resumed_ephemeral_store ( rig. harness . chain . store . clone ( ) )
1343
+ . mock_execution_layer ( )
1344
+ . build ( ) ;
1345
+
1346
+ let resumed_head = resumed. chain . canonical_head . cached_head ( ) ;
1347
+ assert_eq ! (
1348
+ resumed_head. head_block_root( ) ,
1349
+ invalid_head. finalized_checkpoint( ) . root,
1350
+ "the resumed harness should have a head at the finalized checkpoint"
1351
+ ) ;
1352
+ }
0 commit comments