mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-27 01:33:33 +00:00
Cache target attester balances for unrealized FFG progression calculation (#4362)
## Issue Addressed #4118 ## Proposed Changes This PR introduces a "progressive balances" cache on the `BeaconState`, which keeps track of the accumulated target attestation balance for the current & previous epochs. The cached values are utilised by fork choice to calculate unrealized justification and finalization (instead of converting epoch participation arrays to balances for each block we receive). This optimization will be rolled out gradually to allow for more testing. A new `--progressive-balances disabled|checked|strict|fast` flag is introduced to support this: - `checked`: enabled with checks against participation cache, and falls back to the existing epoch processing calculation if there is a total target attester balance mismatch. There is no performance gain from this as the participation cache still needs to be computed. **This is the default mode for now.** - `strict`: enabled with checks against participation cache, returns error if there is a mismatch. **Used for testing only**. - `fast`: enabled with no comparative checks and without computing the participation cache. This mode gives us the performance gains from the optimization. This is still experimental and not currently recommended for production usage, but will become the default mode in a future release. - `disabled`: disable the usage of progressive cache, and use the existing method for FFG progression calculation. This mode may be useful if we find a bug and want to stop the frequent error logs. ### Tasks - [x] Initial cache implementation in `BeaconState` - [x] Perform checks in fork choice to compare the progressive balances cache against results from `ParticipationCache` - [x] Add CLI flag, and disable the optimization by default - [x] Testing on Goerli & Benchmarking - [x] Move caching logic from state processing to the `ProgressiveBalancesCache` (see [this comment](https://github.com/sigp/lighthouse/pull/4362#discussion_r1230877001)) - [x] Add attesting balance metrics Co-authored-by: Jimmy Chen <jimmy@sigmaprime.io>
This commit is contained in:
@@ -17,12 +17,13 @@ use fork_choice::{
|
||||
use store::MemoryStore;
|
||||
use types::{
|
||||
test_utils::generate_deterministic_keypair, BeaconBlockRef, BeaconState, ChainSpec, Checkpoint,
|
||||
Epoch, EthSpec, Hash256, IndexedAttestation, MainnetEthSpec, SignedBeaconBlock, Slot, SubnetId,
|
||||
Epoch, EthSpec, ForkName, Hash256, IndexedAttestation, MainnetEthSpec, ProgressiveBalancesMode,
|
||||
RelativeEpoch, SignedBeaconBlock, Slot, SubnetId,
|
||||
};
|
||||
|
||||
pub type E = MainnetEthSpec;
|
||||
|
||||
pub const VALIDATOR_COUNT: usize = 32;
|
||||
pub const VALIDATOR_COUNT: usize = 64;
|
||||
|
||||
/// Defines some delay between when an attestation is created and when it is mutated.
|
||||
pub enum MutationDelay {
|
||||
@@ -68,6 +69,24 @@ impl ForkChoiceTest {
|
||||
Self { harness }
|
||||
}
|
||||
|
||||
/// Creates a new tester with the specified `ProgressiveBalancesMode` and genesis from latest fork.
|
||||
fn new_with_progressive_balances_mode(mode: ProgressiveBalancesMode) -> ForkChoiceTest {
|
||||
// genesis with latest fork (at least altair required to test the cache)
|
||||
let spec = ForkName::latest().make_genesis_spec(ChainSpec::default());
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
||||
.spec(spec)
|
||||
.chain_config(ChainConfig {
|
||||
progressive_balances_mode: mode,
|
||||
..ChainConfig::default()
|
||||
})
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
Self { harness }
|
||||
}
|
||||
|
||||
/// Get a value from the `ForkChoice` instantiation.
|
||||
fn get<T, U>(&self, func: T) -> U
|
||||
where
|
||||
@@ -212,6 +231,39 @@ impl ForkChoiceTest {
|
||||
self
|
||||
}
|
||||
|
||||
/// Slash a validator from the previous epoch committee.
|
||||
pub async fn add_previous_epoch_attester_slashing(self) -> Self {
|
||||
let state = self.harness.get_current_state();
|
||||
let previous_epoch_shuffling = state.get_shuffling(RelativeEpoch::Previous).unwrap();
|
||||
let validator_indices = previous_epoch_shuffling
|
||||
.iter()
|
||||
.map(|idx| *idx as u64)
|
||||
.take(1)
|
||||
.collect();
|
||||
|
||||
self.harness
|
||||
.add_attester_slashing(validator_indices)
|
||||
.unwrap();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Slash the proposer of a block in the previous epoch.
|
||||
pub async fn add_previous_epoch_proposer_slashing(self, slots_per_epoch: u64) -> Self {
|
||||
let previous_epoch_slot = self.harness.get_current_slot() - slots_per_epoch;
|
||||
let previous_epoch_block = self
|
||||
.harness
|
||||
.chain
|
||||
.block_at_slot(previous_epoch_slot, WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let proposer_index: u64 = previous_epoch_block.message().proposer_index();
|
||||
|
||||
self.harness.add_proposer_slashing(proposer_index).unwrap();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Apply `count` blocks to the chain (without attestations).
|
||||
pub async fn apply_blocks_without_new_attestations(self, count: usize) -> Self {
|
||||
self.harness.advance_slot();
|
||||
@@ -286,7 +338,9 @@ impl ForkChoiceTest {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
self.harness.chain.config.progressive_balances_mode,
|
||||
&self.harness.chain.spec,
|
||||
self.harness.logger(),
|
||||
)
|
||||
.unwrap();
|
||||
self
|
||||
@@ -328,7 +382,9 @@ impl ForkChoiceTest {
|
||||
Duration::from_secs(0),
|
||||
&state,
|
||||
PayloadVerificationStatus::Verified,
|
||||
self.harness.chain.config.progressive_balances_mode,
|
||||
&self.harness.chain.spec,
|
||||
self.harness.logger(),
|
||||
)
|
||||
.err()
|
||||
.expect("on_block did not return an error");
|
||||
@@ -1287,3 +1343,49 @@ async fn weak_subjectivity_check_epoch_boundary_is_skip_slot_failure() {
|
||||
.assert_finalized_epoch_is_less_than(checkpoint.epoch)
|
||||
.assert_shutdown_signal_sent();
|
||||
}
|
||||
|
||||
/// Checks that `ProgressiveBalancesCache` is updated correctly after an attester slashing event,
|
||||
/// where the slashed validator is a target attester in previous / current epoch.
|
||||
#[tokio::test]
|
||||
async fn progressive_balances_cache_attester_slashing() {
|
||||
ForkChoiceTest::new_with_progressive_balances_mode(ProgressiveBalancesMode::Strict)
|
||||
// first two epochs
|
||||
.apply_blocks_while(|_, state| state.finalized_checkpoint().epoch == 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.add_previous_epoch_attester_slashing()
|
||||
.await
|
||||
// expect fork choice to import blocks successfully after a previous epoch attester is
|
||||
// slashed, i.e. the slashed attester's balance is correctly excluded from
|
||||
// the previous epoch total balance in `ProgressiveBalancesCache`.
|
||||
.apply_blocks(1)
|
||||
.await
|
||||
// expect fork choice to import another epoch of blocks successfully - the slashed
|
||||
// attester's balance should be excluded from the current epoch total balance in
|
||||
// `ProgressiveBalancesCache` as well.
|
||||
.apply_blocks(MainnetEthSpec::slots_per_epoch() as usize)
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Checks that `ProgressiveBalancesCache` is updated correctly after a proposer slashing event,
|
||||
/// where the slashed validator is a target attester in previous / current epoch.
|
||||
#[tokio::test]
|
||||
async fn progressive_balances_cache_proposer_slashing() {
|
||||
ForkChoiceTest::new_with_progressive_balances_mode(ProgressiveBalancesMode::Strict)
|
||||
// first two epochs
|
||||
.apply_blocks_while(|_, state| state.finalized_checkpoint().epoch == 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.add_previous_epoch_proposer_slashing(MainnetEthSpec::slots_per_epoch())
|
||||
.await
|
||||
// expect fork choice to import blocks successfully after a previous epoch proposer is
|
||||
// slashed, i.e. the slashed proposer's balance is correctly excluded from
|
||||
// the previous epoch total balance in `ProgressiveBalancesCache`.
|
||||
.apply_blocks(1)
|
||||
.await
|
||||
// expect fork choice to import another epoch of blocks successfully - the slashed
|
||||
// proposer's balance should be excluded from the current epoch total balance in
|
||||
// `ProgressiveBalancesCache` as well.
|
||||
.apply_blocks(MainnetEthSpec::slots_per_epoch() as usize)
|
||||
.await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user