Skip to content

Commit ffb83c8

Browse files
committed
Add failing test
1 parent 17cf507 commit ffb83c8

File tree

2 files changed

+100
-36
lines changed

2 files changed

+100
-36
lines changed

beacon_node/beacon_chain/src/test_utils.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,20 @@ impl<E: EthSpec> Builder<EphemeralHarnessType<E>> {
209209
self.store = Some(store);
210210
self.store_mutator(Box::new(mutator))
211211
}
212+
213+
/// Disk store, resume.
214+
pub fn resumed_ephemeral_store(
215+
mut self,
216+
store: Arc<HotColdDB<E, MemoryStore<E>, MemoryStore<E>>>,
217+
) -> Self {
218+
let mutator = move |builder: BeaconChainBuilder<_>| {
219+
builder
220+
.resume_from_db()
221+
.expect("should resume from database")
222+
};
223+
self.store = Some(store);
224+
self.store_mutator(Box::new(mutator))
225+
}
212226
}
213227

214228
impl<E: EthSpec> Builder<DiskHarnessType<E>> {

beacon_node/beacon_chain/tests/payload_invalidation.rs

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,48 +1199,72 @@ async fn attesting_to_optimistic_head() {
11991199
get_aggregated_by_slot_and_root().unwrap();
12001200
}
12011201

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+
}
12071209

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.
12121215

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+
}
12141220

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();
12231222

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());
12261231

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+
}
12431258
}
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;
12441268

12451269
// Import the first two blocks, they should not become the head.
12461270
for i in 0..2 {
@@ -1300,3 +1324,29 @@ async fn recover_from_invalid_head() {
13001324
.unwrap();
13011325
assert_eq!(manual_get_head, new_head.head_block_root(),);
13021326
}
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

Comments
 (0)