Skip to content

Commit 42a3867

Browse files
Simplify exit cache (#5280)
1 parent 8540914 commit 42a3867

File tree

2 files changed

+39
-14
lines changed

2 files changed

+39
-14
lines changed

consensus/types/src/beacon_state.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ pub enum Error {
105105
},
106106
RelativeEpochError(RelativeEpochError),
107107
ExitCacheUninitialized,
108+
ExitCacheInvalidEpoch {
109+
max_exit_epoch: Epoch,
110+
request_epoch: Epoch,
111+
},
108112
SlashingsCacheUninitialized {
109113
initialized_slot: Option<Slot>,
110114
latest_block_slot: Slot,

consensus/types/src/beacon_state/exit_cache.rs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
use super::{BeaconStateError, ChainSpec, Epoch, Validator};
2-
use safe_arith::SafeArith;
32
use serde::{Deserialize, Serialize};
4-
use std::collections::HashMap;
3+
use std::cmp::Ordering;
54

65
/// Map from exit epoch to the number of validators with that exit epoch.
76
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
87
pub struct ExitCache {
8+
/// True if the cache has been initialized.
99
initialized: bool,
10-
exit_epoch_counts: HashMap<Epoch, u64>,
10+
/// Maximum `exit_epoch` of any validator.
11+
max_exit_epoch: Epoch,
12+
/// Number of validators known to be exiting at `max_exit_epoch`.
13+
max_exit_epoch_churn: u64,
1114
}
1215

1316
impl ExitCache {
1417
/// Initialize a new cache for the given list of validators.
1518
pub fn new(validators: &[Validator], spec: &ChainSpec) -> Result<Self, BeaconStateError> {
1619
let mut exit_cache = ExitCache {
1720
initialized: true,
18-
..ExitCache::default()
21+
max_exit_epoch: Epoch::new(0),
22+
max_exit_epoch_churn: 0,
1923
};
2024
// Add all validators with a non-default exit epoch to the cache.
2125
validators
@@ -37,27 +41,44 @@ impl ExitCache {
3741
/// Record the exit epoch of a validator. Must be called only once per exiting validator.
3842
pub fn record_validator_exit(&mut self, exit_epoch: Epoch) -> Result<(), BeaconStateError> {
3943
self.check_initialized()?;
40-
self.exit_epoch_counts
41-
.entry(exit_epoch)
42-
.or_insert(0)
43-
.safe_add_assign(1)?;
44+
match exit_epoch.cmp(&self.max_exit_epoch) {
45+
// Update churn for the current maximum epoch.
46+
Ordering::Equal => {
47+
self.max_exit_epoch_churn += 1;
48+
}
49+
// Increase the max exit epoch, reset the churn to 1.
50+
Ordering::Greater => {
51+
self.max_exit_epoch = exit_epoch;
52+
self.max_exit_epoch_churn = 1;
53+
}
54+
// Older exit epochs are not relevant.
55+
Ordering::Less => (),
56+
}
4457
Ok(())
4558
}
4659

4760
/// Get the largest exit epoch with a non-zero exit epoch count.
4861
pub fn max_epoch(&self) -> Result<Option<Epoch>, BeaconStateError> {
4962
self.check_initialized()?;
50-
Ok(self.exit_epoch_counts.keys().max().cloned())
63+
Ok((self.max_exit_epoch_churn > 0).then_some(self.max_exit_epoch))
5164
}
5265

5366
/// Get number of validators with the given exit epoch. (Return 0 for the default exit epoch.)
5467
pub fn get_churn_at(&self, exit_epoch: Epoch) -> Result<u64, BeaconStateError> {
5568
self.check_initialized()?;
56-
Ok(self
57-
.exit_epoch_counts
58-
.get(&exit_epoch)
59-
.cloned()
60-
.unwrap_or(0))
69+
match exit_epoch.cmp(&self.max_exit_epoch) {
70+
// Epochs are equal, we know the churn exactly.
71+
Ordering::Equal => Ok(self.max_exit_epoch_churn),
72+
// If exiting at an epoch later than the cached epoch then the churn is 0. This is a
73+
// common case which happens when there are no exits for an epoch.
74+
Ordering::Greater => Ok(0),
75+
// Consensus code should never require the churn at an epoch prior to the cached epoch.
76+
// That's a bug.
77+
Ordering::Less => Err(BeaconStateError::ExitCacheInvalidEpoch {
78+
max_exit_epoch: self.max_exit_epoch,
79+
request_epoch: exit_epoch,
80+
}),
81+
}
6182
}
6283
}
6384

0 commit comments

Comments
 (0)