Update invalid head tests (#3400)
## Proposed Changes Update the invalid head tests so that they work with the current default fork choice configuration. Thanks @realbigsean for fixing the persistence test and the EF tests. Co-authored-by: realbigsean <sean@sigmaprime.io>
This commit is contained in:
parent
83666e04fd
commit
6bc4a2cc91
@ -51,7 +51,7 @@ impl Default for ChainConfig {
|
||||
builder_fallback_skips_per_epoch: 8,
|
||||
builder_fallback_epochs_since_finalization: 3,
|
||||
builder_fallback_disable_checks: false,
|
||||
count_unrealized: false,
|
||||
count_unrealized: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +157,7 @@ pub struct Builder<T: BeaconChainTypes> {
|
||||
execution_layer: Option<ExecutionLayer<T::EthSpec>>,
|
||||
mock_execution_layer: Option<MockExecutionLayer<T::EthSpec>>,
|
||||
mock_builder: Option<TestingBuilder<T::EthSpec>>,
|
||||
testing_slot_clock: Option<TestingSlotClock>,
|
||||
runtime: TestRuntime,
|
||||
log: Logger,
|
||||
}
|
||||
@ -289,6 +290,7 @@ where
|
||||
execution_layer: None,
|
||||
mock_execution_layer: None,
|
||||
mock_builder: None,
|
||||
testing_slot_clock: None,
|
||||
runtime,
|
||||
log,
|
||||
}
|
||||
@ -435,6 +437,11 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn testing_slot_clock(mut self, slot_clock: TestingSlotClock) -> Self {
|
||||
self.testing_slot_clock = Some(slot_clock);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> BeaconChainHarness<BaseHarnessType<E, Hot, Cold>> {
|
||||
let (shutdown_tx, shutdown_receiver) = futures::channel::mpsc::channel(1);
|
||||
|
||||
@ -475,7 +482,9 @@ where
|
||||
};
|
||||
|
||||
// Initialize the slot clock only if it hasn't already been initialized.
|
||||
builder = if builder.get_slot_clock().is_none() {
|
||||
builder = if let Some(testing_slot_clock) = self.testing_slot_clock {
|
||||
builder.slot_clock(testing_slot_clock)
|
||||
} else if builder.get_slot_clock().is_none() {
|
||||
builder
|
||||
.testing_slot_clock(Duration::from_secs(seconds_per_slot))
|
||||
.expect("should configure testing slot clock")
|
||||
|
@ -327,6 +327,9 @@ async fn assert_invalid_signature(
|
||||
item
|
||||
);
|
||||
|
||||
// Call fork choice to update cached head (including finalization).
|
||||
harness.chain.recompute_head_at_current_slot().await;
|
||||
|
||||
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
||||
let ancestor_blocks = chain_segment
|
||||
.iter()
|
||||
@ -339,19 +342,20 @@ async fn assert_invalid_signature(
|
||||
.chain
|
||||
.process_chain_segment(ancestor_blocks, CountUnrealized::True)
|
||||
.await;
|
||||
harness.chain.recompute_head_at_current_slot().await;
|
||||
|
||||
let process_res = harness
|
||||
.chain
|
||||
.process_block(
|
||||
snapshots[block_index].beacon_block.clone(),
|
||||
CountUnrealized::True,
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
matches!(
|
||||
harness
|
||||
.chain
|
||||
.process_block(
|
||||
snapshots[block_index].beacon_block.clone(),
|
||||
CountUnrealized::True
|
||||
)
|
||||
.await,
|
||||
Err(BlockError::InvalidSignature)
|
||||
),
|
||||
"should not import individual block with an invalid {} signature",
|
||||
item
|
||||
matches!(process_res, Err(BlockError::InvalidSignature)),
|
||||
"should not import individual block with an invalid {} signature, got: {:?}",
|
||||
item,
|
||||
process_res
|
||||
);
|
||||
|
||||
// NOTE: we choose not to check gossip verification here. It only checks one signature
|
||||
|
@ -179,7 +179,7 @@ impl InvalidPayloadRig {
|
||||
|
||||
/// Import a block while setting the newPayload and forkchoiceUpdated responses to `is_valid`.
|
||||
async fn import_block(&mut self, is_valid: Payload) -> Hash256 {
|
||||
self.import_block_parametric(is_valid, is_valid, |error| {
|
||||
self.import_block_parametric(is_valid, is_valid, None, |error| {
|
||||
matches!(
|
||||
error,
|
||||
BlockError::ExecutionPayloadError(
|
||||
@ -210,13 +210,14 @@ impl InvalidPayloadRig {
|
||||
&mut self,
|
||||
new_payload_response: Payload,
|
||||
forkchoice_response: Payload,
|
||||
slot_override: Option<Slot>,
|
||||
evaluate_error: F,
|
||||
) -> Hash256 {
|
||||
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
|
||||
|
||||
let head = self.harness.chain.head_snapshot();
|
||||
let state = head.beacon_state.clone_with_only_committee_caches();
|
||||
let slot = state.slot() + 1;
|
||||
let slot = slot_override.unwrap_or(state.slot() + 1);
|
||||
let (block, post_state) = self.harness.make_block(state, slot).await;
|
||||
let block_root = block.canonical_root();
|
||||
|
||||
@ -445,9 +446,12 @@ async fn immediate_forkchoice_update_invalid_test(
|
||||
|
||||
// Import a block which returns syncing when supplied via newPayload, and then
|
||||
// invalid when the forkchoice update is sent.
|
||||
rig.import_block_parametric(Payload::Syncing, invalid_payload(latest_valid_hash), |_| {
|
||||
false
|
||||
})
|
||||
rig.import_block_parametric(
|
||||
Payload::Syncing,
|
||||
invalid_payload(latest_valid_hash),
|
||||
None,
|
||||
|_| false,
|
||||
)
|
||||
.await;
|
||||
|
||||
// The head should be the latest valid block.
|
||||
@ -497,7 +501,7 @@ async fn justified_checkpoint_becomes_invalid() {
|
||||
let is_valid = Payload::Invalid {
|
||||
latest_valid_hash: Some(parent_hash_of_justified),
|
||||
};
|
||||
rig.import_block_parametric(is_valid, is_valid, |error| {
|
||||
rig.import_block_parametric(is_valid, is_valid, None, |error| {
|
||||
matches!(
|
||||
error,
|
||||
// The block import should fail since the beacon chain knows the justified payload
|
||||
@ -1757,11 +1761,11 @@ async fn optimistic_transition_block_invalid_finalized() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper for running tests where we generate a chain with an invalid head and then some
|
||||
/// `fork_blocks` to recover it.
|
||||
/// Helper for running tests where we generate a chain with an invalid head and then a
|
||||
/// `fork_block` to recover it.
|
||||
struct InvalidHeadSetup {
|
||||
rig: InvalidPayloadRig,
|
||||
fork_blocks: Vec<Arc<SignedBeaconBlock<E>>>,
|
||||
fork_block: Arc<SignedBeaconBlock<E>>,
|
||||
invalid_head: CachedHead<E>,
|
||||
}
|
||||
|
||||
@ -1776,11 +1780,59 @@ impl InvalidHeadSetup {
|
||||
rig.import_block(Payload::Syncing).await;
|
||||
}
|
||||
|
||||
let slots_per_epoch = E::slots_per_epoch();
|
||||
let start_slot = rig.cached_head().head_slot() + 1;
|
||||
let mut opt_fork_block = None;
|
||||
|
||||
assert_eq!(start_slot % slots_per_epoch, 1);
|
||||
for i in 0..slots_per_epoch - 1 {
|
||||
let slot = start_slot + i;
|
||||
let slot_offset = slot.as_u64() % slots_per_epoch;
|
||||
|
||||
rig.harness.set_current_slot(slot);
|
||||
|
||||
if slot_offset == slots_per_epoch - 1 {
|
||||
// Optimistic head block right before epoch boundary.
|
||||
let is_valid = Payload::Syncing;
|
||||
rig.import_block_parametric(is_valid, is_valid, Some(slot), |error| {
|
||||
matches!(
|
||||
error,
|
||||
BlockError::ExecutionPayloadError(
|
||||
ExecutionPayloadError::RejectedByExecutionEngine { .. }
|
||||
)
|
||||
)
|
||||
})
|
||||
.await;
|
||||
} else if 3 * slot_offset < 2 * slots_per_epoch {
|
||||
// Valid block in previous epoch.
|
||||
rig.import_block(Payload::Valid).await;
|
||||
} else if slot_offset == slots_per_epoch - 2 {
|
||||
// Fork block one slot prior to invalid head, not applied immediately.
|
||||
let parent_state = rig
|
||||
.harness
|
||||
.chain
|
||||
.state_at_slot(slot - 1, StateSkipConfig::WithStateRoots)
|
||||
.unwrap();
|
||||
let (fork_block, _) = rig.harness.make_block(parent_state, slot).await;
|
||||
opt_fork_block = Some(Arc::new(fork_block));
|
||||
} else {
|
||||
// Skipped slot.
|
||||
};
|
||||
}
|
||||
|
||||
let invalid_head = rig.cached_head();
|
||||
assert_eq!(
|
||||
invalid_head.head_slot() % slots_per_epoch,
|
||||
slots_per_epoch - 1
|
||||
);
|
||||
|
||||
// Advance clock to new epoch to realize the justification of soon-to-be-invalid head block.
|
||||
rig.harness.set_current_slot(invalid_head.head_slot() + 1);
|
||||
|
||||
// Invalidate the head block.
|
||||
rig.invalidate_manually(invalid_head.head_block_root())
|
||||
.await;
|
||||
|
||||
assert!(rig
|
||||
.canonical_head()
|
||||
.head_execution_status()
|
||||
@ -1790,27 +1842,9 @@ impl InvalidHeadSetup {
|
||||
// Finding a new head should fail since the only possible head is not valid.
|
||||
rig.assert_get_head_error_contains("InvalidBestNode");
|
||||
|
||||
// Build three "fork" blocks that conflict with the current canonical head. Don't apply them to
|
||||
// the chain yet.
|
||||
let mut fork_blocks = vec![];
|
||||
let mut parent_state = rig
|
||||
.harness
|
||||
.chain
|
||||
.state_at_slot(
|
||||
invalid_head.head_slot() - 3,
|
||||
StateSkipConfig::WithStateRoots,
|
||||
)
|
||||
.unwrap();
|
||||
for _ in 0..3 {
|
||||
let slot = parent_state.slot() + 1;
|
||||
let (fork_block, post_state) = rig.harness.make_block(parent_state, slot).await;
|
||||
parent_state = post_state;
|
||||
fork_blocks.push(Arc::new(fork_block))
|
||||
}
|
||||
|
||||
Self {
|
||||
rig,
|
||||
fork_blocks,
|
||||
fork_block: opt_fork_block.unwrap(),
|
||||
invalid_head,
|
||||
}
|
||||
}
|
||||
@ -1820,57 +1854,22 @@ impl InvalidHeadSetup {
|
||||
async fn recover_from_invalid_head_by_importing_blocks() {
|
||||
let InvalidHeadSetup {
|
||||
rig,
|
||||
fork_blocks,
|
||||
invalid_head,
|
||||
fork_block,
|
||||
invalid_head: _,
|
||||
} = InvalidHeadSetup::new().await;
|
||||
|
||||
// Import the first two blocks, they should not become the head.
|
||||
for i in 0..2 {
|
||||
if i == 0 {
|
||||
// The first block should be `VALID` during import.
|
||||
rig.harness
|
||||
.mock_execution_layer
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.server
|
||||
.all_payloads_valid_on_new_payload();
|
||||
} else {
|
||||
// All blocks after the first block should return `SYNCING`.
|
||||
rig.harness
|
||||
.mock_execution_layer
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.server
|
||||
.all_payloads_syncing_on_new_payload(true);
|
||||
}
|
||||
|
||||
rig.harness
|
||||
.chain
|
||||
.process_block(fork_blocks[i].clone(), CountUnrealized::True)
|
||||
.await
|
||||
.unwrap();
|
||||
rig.recompute_head().await;
|
||||
rig.assert_get_head_error_contains("InvalidBestNode");
|
||||
let new_head = rig.cached_head();
|
||||
assert_eq!(
|
||||
new_head.head_block_root(),
|
||||
invalid_head.head_block_root(),
|
||||
"the head should not change"
|
||||
);
|
||||
}
|
||||
|
||||
// Import the third block, it should become the head.
|
||||
// Import the fork block, it should become the head.
|
||||
rig.harness
|
||||
.chain
|
||||
.process_block(fork_blocks[2].clone(), CountUnrealized::True)
|
||||
.process_block(fork_block.clone(), CountUnrealized::True)
|
||||
.await
|
||||
.unwrap();
|
||||
rig.recompute_head().await;
|
||||
let new_head = rig.cached_head();
|
||||
assert_eq!(
|
||||
new_head.head_block_root(),
|
||||
fork_blocks[2].canonical_root(),
|
||||
"the third block should become the head"
|
||||
fork_block.canonical_root(),
|
||||
"the fork block should become the head"
|
||||
);
|
||||
|
||||
let manual_get_head = rig
|
||||
@ -1880,17 +1879,19 @@ async fn recover_from_invalid_head_by_importing_blocks() {
|
||||
.fork_choice_write_lock()
|
||||
.get_head(rig.harness.chain.slot().unwrap(), &rig.harness.chain.spec)
|
||||
.unwrap();
|
||||
assert_eq!(manual_get_head, new_head.head_block_root(),);
|
||||
assert_eq!(manual_get_head, new_head.head_block_root());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recover_from_invalid_head_after_persist_and_reboot() {
|
||||
let InvalidHeadSetup {
|
||||
rig,
|
||||
fork_blocks: _,
|
||||
fork_block: _,
|
||||
invalid_head,
|
||||
} = InvalidHeadSetup::new().await;
|
||||
|
||||
let slot_clock = rig.harness.chain.slot_clock.clone();
|
||||
|
||||
// Forcefully persist the head and fork choice.
|
||||
rig.harness.chain.persist_head_and_fork_choice().unwrap();
|
||||
|
||||
@ -1899,6 +1900,7 @@ async fn recover_from_invalid_head_after_persist_and_reboot() {
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.resumed_ephemeral_store(rig.harness.chain.store.clone())
|
||||
.mock_execution_layer()
|
||||
.testing_slot_clock(slot_clock)
|
||||
.build();
|
||||
|
||||
// Forget the original rig so we don't accidentally use it again.
|
||||
|
@ -341,7 +341,7 @@ impl<E: EthSpec> Tester<E> {
|
||||
let result = self.block_on_dangerous(
|
||||
self.harness
|
||||
.chain
|
||||
.process_block(block.clone(), CountUnrealized::True),
|
||||
.process_block(block.clone(), CountUnrealized::False),
|
||||
)?;
|
||||
if result.is_ok() != valid {
|
||||
return Err(Error::DidntFail(format!(
|
||||
|
Loading…
Reference in New Issue
Block a user