-
Notifications
You must be signed in to change notification settings - Fork 905
Description
Description
While testing Deneb + tree-states, I found a bug in our current progressive balances cache implementation: we update the unslashed participating balances incorrectly for slashed validators. First we remove the balance with on_slashing
:
/// When a validator is slashed, we reduce the `current_epoch_target_attesting_balance` by the | |
/// validator's effective balance to exclude the validator weight. | |
pub fn on_slashing( | |
&mut self, | |
is_previous_epoch_target_attester: bool, | |
is_current_epoch_target_attester: bool, | |
effective_balance: u64, | |
) -> Result<(), BeaconStateError> { |
Later, when processing effective balance changes, we update the total by the difference in the slashed validator's effective balance, which is incorrect, because the validator's balance has already been removed:
lighthouse/consensus/state_processing/src/per_epoch_processing/effective_balance_updates.rs
Lines 58 to 66 in 441fc16
if old_effective_balance != new_effective_balance { | |
let is_current_epoch_target_attester = | |
participation_cache.is_current_epoch_timely_target_attester(index)?; | |
progressive_balances_cache.on_effective_balance_change( | |
is_current_epoch_target_attester, | |
old_effective_balance, | |
new_effective_balance, | |
)?; | |
} |
The result is that the total unslashed participating balance can be off by O(num_slashed_validators)
. This is unlikely to cause issues in practice, because:
- The progressive balances optimisation is disabled by default currently. If this bug were triggered, we'd get an error log and nothing more.
- If the user has opted into
--progressive-balances fast
, the worst-case is a temporary view split, which would require a lot of validators to be slashed. If the slashing is small, it's unlikely to cause a view split, because the attesting balances will only be off by a small amount.
Version
Lighthouse v4.5.0
Steps to resolve
Guard the on_effective_balance_change
call by !validator.slashed()
. On tree-states, I've forced the caller to pass the validator's is_slashed
status to on_effective_balance_change
:
lighthouse/consensus/state_processing/src/per_epoch_processing/single_pass.rs
Lines 623 to 630 in b77de69
// Update progressive balances cache for the *current* epoch, which will soon become the | |
// previous epoch once the epoch transition completes. | |
progressive_balances.on_effective_balance_change( | |
validator.slashed(), | |
validator_info.current_epoch_participation, | |
old_effective_balance, | |
new_effective_balance, | |
)?; |
We should make a similar change on unstable
, with an accompanying regression test. The conditions necessary to trigger this are:
- Validator's balance gets lowered substantially as a result of the amount they are slashed (e.g. validator gets slashed 1 ETH and goes from balance 32 ETH to 31 ETH).
- Epoch transition occurs after the slashing which applies the balance reduction to their effective balance (e.g. eff. balance 32 -> 31).