Skip to content

Commit b414c32

Browse files
committed
Implement activation queue cache
1 parent f631b51 commit b414c32

File tree

6 files changed

+95
-17
lines changed

6 files changed

+95
-17
lines changed

consensus/state_processing/src/epoch_cache.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::common::altair::BaseRewardPerIncrement;
22
use crate::common::base::SqrtTotalActiveBalance;
33
use crate::common::{altair, base};
44
use types::epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey};
5-
use types::{BeaconState, ChainSpec, Epoch, EthSpec, Hash256};
5+
use types::{ActivationQueue, BeaconState, ChainSpec, Epoch, EthSpec, Hash256};
66

77
pub fn initialize_epoch_cache<E: EthSpec>(
88
state: &mut BeaconState<E>,
@@ -23,13 +23,17 @@ pub fn initialize_epoch_cache<E: EthSpec>(
2323
}
2424

2525
// Compute base rewards.
26+
state.build_total_active_balance_cache_at(epoch, spec)?;
2627
let total_active_balance = state.get_total_active_balance_at_epoch(epoch)?;
2728
let sqrt_total_active_balance = SqrtTotalActiveBalance::new(total_active_balance);
2829
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
2930

3031
let mut base_rewards = Vec::with_capacity(state.validators().len());
3132

32-
for validator in state.validators().iter() {
33+
// Compute activation queue.
34+
let mut activation_queue = ActivationQueue::default();
35+
36+
for (index, validator) in state.validators().iter().enumerate() {
3337
let effective_balance = validator.effective_balance();
3438

3539
let base_reward = if spec
@@ -41,6 +45,9 @@ pub fn initialize_epoch_cache<E: EthSpec>(
4145
altair::get_base_reward(effective_balance, base_reward_per_increment, spec)?
4246
};
4347
base_rewards.push(base_reward);
48+
49+
// Add to speculative activation queue.
50+
activation_queue.add_if_could_be_eligible_for_activation(index, validator, epoch, spec);
4451
}
4552

4653
*state.epoch_cache_mut() = EpochCache::new(
@@ -49,6 +56,7 @@ pub fn initialize_epoch_cache<E: EthSpec>(
4956
decision_block_root,
5057
},
5158
base_rewards,
59+
activation_queue,
5260
);
5361

5462
Ok(())

consensus/state_processing/src/per_epoch_processing/registry_updates.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
2-
use itertools::Itertools;
32
use safe_arith::SafeArith;
43
use types::{BeaconState, ChainSpec, EthSpec, Validator};
54

@@ -40,19 +39,16 @@ pub fn process_registry_updates<T: EthSpec>(
4039
}
4140

4241
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
43-
let activation_queue = state
44-
.validators()
45-
.iter()
46-
.enumerate()
47-
.filter(|(_, validator)| validator.is_eligible_for_activation(state, spec))
48-
.sorted_by_key(|(index, validator)| (validator.activation_eligibility_epoch(), *index))
49-
.map(|(index, _)| index)
50-
.collect_vec();
51-
5242
// Dequeue validators for activation up to churn limit
5343
let churn_limit = state.get_churn_limit(spec)? as usize;
44+
45+
let epoch_cache = state.epoch_cache().clone();
46+
let activation_queue = epoch_cache
47+
.activation_queue()?
48+
.get_validators_eligible_for_activation(state.finalized_checkpoint().epoch, churn_limit);
49+
5450
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
55-
for index in activation_queue.into_iter().take(churn_limit) {
51+
for index in activation_queue {
5652
state.get_validator_mut(index)?.mutable.activation_epoch = delayed_activation_epoch;
5753
}
5854

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use crate::{ChainSpec, Epoch, Validator};
2+
use std::collections::BTreeSet;
3+
4+
/// Activation queue computed during epoch processing for use in the *next* epoch.
5+
#[derive(Debug, PartialEq, Eq, Default, Clone, arbitrary::Arbitrary)]
6+
pub struct ActivationQueue {
7+
/// Validators represented by `(activation_eligibility_epoch, index)` in sorted order.
8+
queue: BTreeSet<(Epoch, usize)>,
9+
}
10+
11+
impl ActivationQueue {
12+
pub fn add_if_could_be_eligible_for_activation(
13+
&mut self,
14+
index: usize,
15+
validator: &Validator,
16+
next_epoch: Epoch,
17+
spec: &ChainSpec,
18+
) {
19+
if validator.could_be_eligible_for_activation_at(next_epoch, spec) {
20+
self.queue
21+
.insert((validator.activation_eligibility_epoch(), index));
22+
}
23+
}
24+
25+
pub fn get_validators_eligible_for_activation(
26+
&self,
27+
finalized_epoch: Epoch,
28+
churn_limit: usize,
29+
) -> BTreeSet<usize> {
30+
self.queue
31+
.iter()
32+
.filter_map(|&(eligibility_epoch, index)| {
33+
(eligibility_epoch <= finalized_epoch).then_some(index)
34+
})
35+
.take(churn_limit)
36+
.collect()
37+
}
38+
}

consensus/types/src/epoch_cache.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{BeaconStateError, Epoch, EthSpec, Hash256, Slot};
1+
use crate::{ActivationQueue, BeaconStateError, Epoch, EthSpec, Hash256, Slot};
22
use safe_arith::ArithError;
33
use std::sync::Arc;
44

@@ -20,6 +20,8 @@ struct Inner {
2020
key: EpochCacheKey,
2121
/// Base reward for every validator in this epoch.
2222
base_rewards: Vec<u64>,
23+
/// Validator activation queue.
24+
activation_queue: ActivationQueue,
2325
}
2426

2527
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, arbitrary::Arbitrary)]
@@ -52,9 +54,17 @@ impl From<ArithError> for EpochCacheError {
5254
}
5355

5456
impl EpochCache {
55-
pub fn new(key: EpochCacheKey, base_rewards: Vec<u64>) -> EpochCache {
57+
pub fn new(
58+
key: EpochCacheKey,
59+
base_rewards: Vec<u64>,
60+
activation_queue: ActivationQueue,
61+
) -> EpochCache {
5662
Self {
57-
inner: Some(Arc::new(Inner { key, base_rewards })),
63+
inner: Some(Arc::new(Inner {
64+
key,
65+
base_rewards,
66+
activation_queue,
67+
})),
5868
}
5969
}
6070

@@ -92,4 +102,12 @@ impl EpochCache {
92102
.copied()
93103
.ok_or(EpochCacheError::ValidatorIndexOutOfBounds { validator_index })
94104
}
105+
106+
pub fn activation_queue(&self) -> Result<&ActivationQueue, EpochCacheError> {
107+
let inner = self
108+
.inner
109+
.as_ref()
110+
.ok_or(EpochCacheError::CacheNotInitialized)?;
111+
Ok(&inner.activation_queue)
112+
}
95113
}

consensus/types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub mod validator_subscription;
7373
pub mod voluntary_exit;
7474
#[macro_use]
7575
pub mod slot_epoch_macros;
76+
pub mod activation_queue;
7677
pub mod config_and_preset;
7778
pub mod execution_block_header;
7879
pub mod fork_context;
@@ -99,6 +100,7 @@ pub mod sqlite;
99100

100101
use ethereum_types::{H160, H256};
101102

103+
pub use crate::activation_queue::ActivationQueue;
102104
pub use crate::aggregate_and_proof::AggregateAndProof;
103105
pub use crate::attestation::{Attestation, Error as AttestationError};
104106
pub use crate::attestation_data::AttestationData;

consensus/types/src/validator.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,26 @@ impl Validator {
159159
state: &BeaconState<E>,
160160
spec: &ChainSpec,
161161
) -> bool {
162+
// Has not yet been activated
163+
self.activation_epoch() == spec.far_future_epoch &&
162164
// Placement in queue is finalized
163165
self.activation_eligibility_epoch() <= state.finalized_checkpoint().epoch
166+
}
167+
168+
/// Returns `true` if the validator *could* be eligible for activation at `epoch`.
169+
///
170+
/// Eligibility depends on finalization, so we assume best-possible finalization. This function
171+
/// returning true is a necessary but *not sufficient* condition for a validator to activate in
172+
/// the epoch transition at the end of `epoch`.
173+
pub fn could_be_eligible_for_activation_at(&self, epoch: Epoch, spec: &ChainSpec) -> bool {
164174
// Has not yet been activated
165-
&& self.activation_epoch() == spec.far_future_epoch
175+
self.activation_epoch() == spec.far_future_epoch
176+
// Placement in queue could be finalized.
177+
//
178+
// NOTE: it's +1 rather than +2 because we consider the activations that occur at the *end*
179+
// of `epoch`, after `process_justification_and_finalization` has already updated the
180+
// state's checkpoint.
181+
&& self.activation_eligibility_epoch() + 1 <= epoch
166182
}
167183

168184
fn tree_hash_root_internal(&self) -> Result<Hash256, tree_hash::Error> {

0 commit comments

Comments
 (0)