diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 13d167936..9d91378e9 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -608,10 +608,20 @@ where beacon_block_root: indexed_attestation.data.beacon_block_root, })?; - if block.target_root != target.root { + // If an attestation points to a block that is from an earlier slot than the attestation, + // then all slots between the block and attestation must be skipped. Therefore if the block + // is from a prior epoch to the attestation, then the target root must be equal to the root + // of the block that is being attested to. + let expected_target = if target.epoch > block.slot.epoch(E::slots_per_epoch()) { + indexed_attestation.data.beacon_block_root + } else { + block.target_root + }; + + if expected_target != target.root { return Err(InvalidAttestation::InvalidTarget { attestation: target.root, - local: block.target_root, + local: expected_target, }); } diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index 0ef4d0edb..7897c01a4 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -106,6 +106,14 @@ impl ForkChoiceTest { self } + /// Skips `count` slots, without producing a block. + pub fn skip_slots(self, count: usize) -> Self { + for _ in 0..count { + self.harness.advance_slot(); + } + self + } + /// Build the chain whilst `predicate` returns `true`. pub fn apply_blocks_while(self, mut predicate: F) -> Self where @@ -810,3 +818,22 @@ fn invalid_attestation_delayed_slot() { .skip_slot() .inspect_queued_attestations(|queue| assert_eq!(queue.len(), 0)); } + +/// Tests that the correct target root is used when the attested-to block is in a prior epoch to +/// the attestation. +#[test] +fn valid_attestation_skip_across_epoch() { + ForkChoiceTest::new() + .apply_blocks(E::slots_per_epoch() as usize - 1) + .skip_slots(2) + .apply_attestation_to_chain( + MutationDelay::NoDelay, + |attestation, _chain| { + assert_eq!( + attestation.data.target.root, + attestation.data.beacon_block_root + ) + }, + |result| result.unwrap(), + ); +}