diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 7263bf0513e..0878a61211c 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -31,10 +31,7 @@ use rayon::prelude::*; use sensitive_url::SensitiveUrl; use slog::Logger; use slot_clock::TestingSlotClock; -use state_processing::{ - state_advance::{complete_state_advance, partial_state_advance}, - StateRootStrategy, -}; +use state_processing::{BlockSignatureStrategy, per_block_processing, state_advance::{complete_state_advance, partial_state_advance}, StateRootStrategy, VerifyBlockRoot}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::str::FromStr; @@ -1244,11 +1241,32 @@ where assert_ne!(slot, 0, "can't produce a block at slot 0"); assert!(slot >= state.slot()); - let (block, state) = self.make_block_return_pre_state(state, slot); + let (block, pre_state) = self.make_block_return_pre_state(state, slot); let (mut block, _) = block.deconstruct(); + let mut state = pre_state.clone(); + block_modifier(&mut block); + // Update the state root of the modified block to make sure it remains valid. + let mut signed_block = SignedBeaconBlock::from_block( + block, + // The block is not signed here, that is the task of a validator client. + Signature::empty(), + ); + + per_block_processing( + &mut state, + &signed_block, + None, + BlockSignatureStrategy::NoVerification, + VerifyBlockRoot::True, + &self.spec, + ).unwrap(); + + signed_block.message_altair_mut().unwrap().state_root = state.canonical_root(); + let (mut block, _) = signed_block.deconstruct(); + let proposer_index = state.get_beacon_proposer_index(slot, &self.spec).unwrap(); let signed_block = block.sign( @@ -1257,7 +1275,7 @@ where state.genesis_validators_root(), &self.spec, ); - (signed_block, state) + (signed_block, pre_state) } pub fn make_deposits<'a>( diff --git a/beacon_node/beacon_chain/tests/fork_choice.rs b/beacon_node/beacon_chain/tests/fork_choice.rs index 1700eeabe6b..c69894a1ea2 100644 --- a/beacon_node/beacon_chain/tests/fork_choice.rs +++ b/beacon_node/beacon_chain/tests/fork_choice.rs @@ -2,6 +2,7 @@ use beacon_chain::{ test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy}, WhenSlotSkipped, }; +use eth2::types::typenum::U16; use types::*; const VALIDATOR_COUNT: usize = 24; @@ -108,3 +109,129 @@ fn chooses_highest_justified_checkpoint() { "the fork block has not become the head" ); } + +#[test] +fn chooses_highest_justified_checkpoint_n_plus_2() { + let slots_per_epoch = MainnetEthSpec::slots_per_epoch(); + let mut spec = MainnetEthSpec::default_spec(); + spec.altair_fork_epoch = Some(Epoch::new(0)); + let harness = BeaconChainHarness::builder(MainnetEthSpec) + .spec(spec) + .deterministic_keypairs(VALIDATOR_COUNT) + .fresh_ephemeral_store() + .mock_execution_layer() + .build(); + + harness.advance_slot(); + + let head = harness.chain.head().unwrap(); + assert_eq!(head.beacon_block.slot(), 0, "the chain head is at genesis"); + assert_eq!( + head.beacon_state.finalized_checkpoint().epoch, + 0, + "there has been no finalization yet" + ); + + let slot_a = Slot::from(slots_per_epoch * 4 + slots_per_epoch - 1); + + // Extend the chain to the slot before `slot_a` + harness.extend_chain( + slot_a.as_usize() - 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ); + + // Make slashings to include in the block at `slot_a`. + let head = harness.chain.head().unwrap(); + let mut slashings = vec![]; + for i in 0..15 { + slashings.push(harness.make_proposer_slashing(i as u64)); + } + let (block, pre_state) = harness.make_block_with_modifier(head.beacon_state, slot_a, |block| { + block.body_altair_mut().unwrap().proposer_slashings = VariableList::::new(slashings).unwrap(); + }); + + // Process the block containing the slashings at the slot before the epoch transition and attest to it. + harness.process_block(slot_a, block).unwrap(); + let head = harness.chain.head().unwrap(); + let vals = (15..VALIDATOR_COUNT).collect::>(); + harness.attest_block( &head.beacon_state, head.beacon_state.canonical_root(), SignedBeaconBlockHash::from(head.beacon_block_root), &head.beacon_block, vals.as_slice()); + + assert_eq!(head.beacon_block.slot(), slot_a); + assert_eq!( + head.beacon_block.slot() % slots_per_epoch, + slots_per_epoch - 1, + "the chain is at the last slot of the epoch" + ); + + assert_eq!( + head.beacon_state.current_justified_checkpoint().epoch, + 3, + "the chain has justified" + ); + assert_eq!( + head.beacon_state.finalized_checkpoint().epoch, + 2, + "the chain has finalized" + ); + let slot_a_root = head.beacon_block_root; + + // Advance a full epoch. + for _ in 0..slots_per_epoch { + harness.advance_slot(); + } + + let reorg_distance = 9; + let fork_parent_slot = slot_a - reorg_distance; + let fork_parent_block = harness + .chain + .block_at_slot(fork_parent_slot, WhenSlotSkipped::None) + .unwrap() + .unwrap(); + let fork_parent_state = harness + .chain + .get_state(&fork_parent_block.state_root(), Some(fork_parent_slot)) + .unwrap() + .unwrap(); + let (fork_block, fork_state) = harness.make_block(fork_parent_state, slot_a + slots_per_epoch); + + assert_eq!( + fork_state.current_justified_checkpoint().epoch, + 4, + "the fork block has justifed further" + ); + assert_eq!( + fork_state.finalized_checkpoint().epoch, + 3, + "the fork block has finalized further" + ); + + let fork_block_root = fork_block.canonical_root(); + assert_eq!( + fork_block_root, + harness + .process_block(fork_block.slot(), fork_block) + .unwrap() + .into() + ); + + { + let fork_choice = harness.chain.fork_choice.read(); + let proto_array = fork_choice.proto_array(); + assert_eq!( + proto_array.get_weight(&fork_block_root).unwrap(), + 0, + "the fork block should have no votes" + ); + assert!( + proto_array.get_weight(&slot_a_root).unwrap() > 0, + "the slot_a block should have some votes" + ); + } + + let head = harness.chain.head().unwrap(); + assert_eq!( + head.beacon_block_root, slot_a_root, + "the fork block has not become the head" + ); +}