mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-30 03:14:25 +00:00
Correct unrealized justification for blocks with slashings (#9471)
Fix a bug in fork choice whereby the unrealized justified and finalized checkpoints from a parent block would be incorrectly carried over to a child block in the same epoch. The documented optimisation in `fork_choice.rs` was wrong because it failed to account for slashings. This bug is not considered to be sensitive due to the difficulty of triggering it, and the low payoff for doing so (fleeting divergence). Keep the optimisation, updating it to correctly skip reusing the parent checkpoints when slashings are present. A more minimal alternative would be to scrap the optimisation altogether (always compute the checkpoints), however this would come with a minor performance downside. I think the updated optimisation is simple enough to be worth retaining. There are 3 regression tests added which confirm the correct behaviour. Temporarily setting `has_slashings` to `false` causes all 3 tests to fail. Co-Authored-By: Michael Sproul <michael@sigmaprime.io> Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com>
This commit is contained in:
@@ -894,22 +894,29 @@ where
|
||||
// Update unrealized justified/finalized checkpoints.
|
||||
let block_epoch = block.slot().epoch(E::slots_per_epoch());
|
||||
|
||||
// If the parent checkpoints are already at the same epoch as the block being imported,
|
||||
// it's impossible for the unrealized checkpoints to differ from the parent's. This
|
||||
// holds true because:
|
||||
// If the block has no slashings and the parent checkpoints are already at the same epoch as
|
||||
// the block being imported, it's impossible for the unrealized checkpoints to differ from
|
||||
// the parent's. This holds true because:
|
||||
//
|
||||
// 1. A child block cannot have lower FFG checkpoints than its parent.
|
||||
// 2. A block in epoch `N` cannot contain attestations which would justify an epoch higher than `N`.
|
||||
// 3. A block in epoch `N` cannot contain attestations which would finalize an epoch higher than `N - 1`.
|
||||
//
|
||||
// Slashings are excluded from this optimization because they can reduce unslashed
|
||||
// participation in the child state and therefore lower the child's unrealized checkpoints.
|
||||
//
|
||||
// This is an optimization. It should reduce the amount of times we run
|
||||
// `process_justification_and_finalization` by approximately 1/3rd when the chain is
|
||||
// performing optimally.
|
||||
let has_slashings = !block.body().proposer_slashings().is_empty()
|
||||
|| block.body().attester_slashings_len() > 0;
|
||||
let parent_checkpoints = parent_block
|
||||
.unrealized_justified_checkpoint
|
||||
.zip(parent_block.unrealized_finalized_checkpoint)
|
||||
.filter(|(parent_justified, parent_finalized)| {
|
||||
parent_justified.epoch == block_epoch && parent_finalized.epoch + 1 == block_epoch
|
||||
!has_slashings
|
||||
&& parent_justified.epoch == block_epoch
|
||||
&& parent_finalized.epoch.saturating_add(1u64) == block_epoch
|
||||
});
|
||||
|
||||
let (unrealized_justified_checkpoint, unrealized_finalized_checkpoint) =
|
||||
|
||||
Reference in New Issue
Block a user