Skip to content

Commit 2df714e

Browse files
authored
Tree states optimization using EpochCache (sigp#4429)
* Relocate epoch cache to BeaconState * Optimize per block processing by pulling previous epoch & current epoch calculation up. * Revert `get_cow` change (no performance improvement) * Initialize `EpochCache` in epoch processing and load it from state when getting base rewards. * Initialize `EpochCache` at start of block processing if required. * Initialize `EpochCache` in `transition_blocks` if `exclude_cache_builds` is enabled * Fix epoch cache initialization logic * Remove FIXME comment. * Cache previous & current epochs in `consensus_context.rs`. * Move `get_base_rewards` from `ConsensusContext` to `BeaconState`. * Update Milhouse version
1 parent 160bbde commit 2df714e

File tree

19 files changed

+239
-198
lines changed

19 files changed

+239
-198
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/beacon_chain/src/beacon_block_reward.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use state_processing::{
88
per_block_processing::{
99
altair::sync_committee::compute_sync_aggregate_rewards, get_slashable_indices,
1010
},
11-
ConsensusContext,
1211
};
1312
use store::{
1413
consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR},
@@ -177,8 +176,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
177176
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
178177
state: &mut BeaconState<T::EthSpec>,
179178
) -> Result<BeaconBlockSubRewardValue, BeaconChainError> {
180-
let mut ctxt = ConsensusContext::new(block.slot());
181-
182179
let mut total_proposer_reward = 0;
183180

184181
let proposer_reward_denominator = WEIGHT_DENOMINATOR
@@ -202,8 +199,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
202199
for index in attesting_indices {
203200
let index = index as usize;
204201
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
205-
let epoch_participation =
206-
state.get_epoch_participation_mut(data.target.epoch)?;
202+
let previous_epoch = state.previous_epoch();
203+
let current_epoch = state.current_epoch();
204+
let epoch_participation = state.get_epoch_participation_mut(
205+
data.target.epoch,
206+
previous_epoch,
207+
current_epoch,
208+
)?;
207209
let validator_participation = epoch_participation
208210
.get_mut(index)
209211
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
@@ -213,7 +215,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
213215
{
214216
validator_participation.add_flag(flag_index)?;
215217
proposer_reward_numerator.safe_add_assign(
216-
ctxt.get_base_reward(state, index, &self.spec)
218+
state
219+
.get_base_reward(index)
217220
.map_err(|_| BeaconChainError::BlockRewardAttestationError)?
218221
.safe_mul(weight)?,
219222
)?;

consensus/state_processing/src/consensus_context.rs

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::common::get_indexed_attestation;
22
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};
3-
use crate::{EpochCache, EpochCacheError};
4-
use std::borrow::Cow;
3+
use crate::EpochCacheError;
54
use std::collections::{hash_map::Entry, HashMap};
65
use std::marker::PhantomData;
76
use tree_hash::TreeHash;
@@ -14,12 +13,14 @@ use types::{
1413
pub struct ConsensusContext<T: EthSpec> {
1514
/// Slot to act as an identifier/safeguard
1615
slot: Slot,
16+
/// Previous epoch of the `slot` precomputed for optimization purpose.
17+
pub(crate) previous_epoch: Epoch,
18+
/// Current epoch of the `slot` precomputed for optimization purpose.
19+
pub(crate) current_epoch: Epoch,
1720
/// Proposer index of the block at `slot`.
1821
proposer_index: Option<u64>,
1922
/// Block root of the block at `slot`.
2023
current_block_root: Option<Hash256>,
21-
/// Epoch cache of values that are useful for block processing that are static over an epoch.
22-
epoch_cache: Option<EpochCache>,
2324
/// Cache of indexed attestations constructed during block processing.
2425
indexed_attestations:
2526
HashMap<(AttestationData, BitList<T::MaxValidatorsPerCommittee>), IndexedAttestation<T>>,
@@ -48,11 +49,14 @@ impl From<EpochCacheError> for ContextError {
4849

4950
impl<T: EthSpec> ConsensusContext<T> {
5051
pub fn new(slot: Slot) -> Self {
52+
let current_epoch = slot.epoch(T::slots_per_epoch());
53+
let previous_epoch = current_epoch.saturating_sub(1u64);
5154
Self {
5255
slot,
56+
previous_epoch,
57+
current_epoch,
5358
proposer_index: None,
5459
current_block_root: None,
55-
epoch_cache: None,
5660
indexed_attestations: HashMap::new(),
5761
_phantom: PhantomData,
5862
}
@@ -145,31 +149,6 @@ impl<T: EthSpec> ConsensusContext<T> {
145149
}
146150
}
147151

148-
pub fn set_epoch_cache(mut self, epoch_cache: EpochCache) -> Self {
149-
self.epoch_cache = Some(epoch_cache);
150-
self
151-
}
152-
153-
pub fn get_base_reward(
154-
&mut self,
155-
state: &BeaconState<T>,
156-
validator_index: usize,
157-
spec: &ChainSpec,
158-
) -> Result<u64, ContextError> {
159-
self.check_slot(state.slot())?;
160-
161-
// Build epoch cache if not already built.
162-
let epoch_cache = if let Some(ref cache) = self.epoch_cache {
163-
Cow::Borrowed(cache)
164-
} else {
165-
let cache = EpochCache::new(state, spec)?;
166-
self.epoch_cache = Some(cache.clone());
167-
Cow::Owned(cache)
168-
};
169-
170-
Ok(epoch_cache.get_base_reward(validator_index)?)
171-
}
172-
173152
pub fn get_indexed_attestation(
174153
&mut self,
175154
state: &BeaconState<T>,
Lines changed: 49 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,55 @@
1-
use crate::common::{
2-
altair::{self, BaseRewardPerIncrement},
3-
base::{self, SqrtTotalActiveBalance},
4-
};
5-
use safe_arith::ArithError;
6-
use std::sync::Arc;
7-
use types::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, Hash256, Slot};
8-
9-
/// Cache of values which are uniquely determined at the start of an epoch.
10-
///
11-
/// The values are fixed with respect to the last block of the _prior_ epoch, which we refer
12-
/// to as the "decision block". This cache is very similar to the `BeaconProposerCache` in that
13-
/// beacon proposers are determined at exactly the same time as the values in this cache, so
14-
/// the keys for the two caches are identical.
15-
#[derive(Debug, PartialEq, Eq, Clone)]
16-
pub struct EpochCache {
17-
inner: Arc<Inner>,
18-
}
19-
20-
#[derive(Debug, PartialEq, Eq, Clone)]
21-
struct Inner {
22-
/// Unique identifier for this cache, which can be used to check its validity before use
23-
/// with any `BeaconState`.
24-
key: EpochCacheKey,
25-
/// Base reward for every validator in this epoch.
26-
base_rewards: Vec<u64>,
27-
}
28-
29-
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
30-
pub struct EpochCacheKey {
31-
pub epoch: Epoch,
32-
pub decision_block_root: Hash256,
33-
}
34-
35-
#[derive(Debug, PartialEq, Clone)]
36-
pub enum EpochCacheError {
37-
IncorrectEpoch { cache: Epoch, state: Epoch },
38-
IncorrectDecisionBlock { cache: Hash256, state: Hash256 },
39-
ValidatorIndexOutOfBounds { validator_index: usize },
40-
InvalidSlot { slot: Slot },
41-
Arith(ArithError),
42-
BeaconState(BeaconStateError),
43-
}
44-
45-
impl From<BeaconStateError> for EpochCacheError {
46-
fn from(e: BeaconStateError) -> Self {
47-
Self::BeaconState(e)
1+
use crate::common::altair::BaseRewardPerIncrement;
2+
use crate::common::base::SqrtTotalActiveBalance;
3+
use crate::common::{altair, base};
4+
use types::epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey};
5+
use types::{BeaconState, ChainSpec, Epoch, EthSpec, Hash256};
6+
7+
pub fn initialize_epoch_cache<E: EthSpec>(
8+
state: &mut BeaconState<E>,
9+
epoch: Epoch,
10+
spec: &ChainSpec,
11+
) -> Result<(), EpochCacheError> {
12+
let epoch_cache: &EpochCache = state.epoch_cache();
13+
let decision_block_root = state
14+
.proposer_shuffling_decision_root(Hash256::zero())
15+
.map_err(EpochCacheError::BeaconState)?;
16+
17+
if epoch_cache
18+
.check_validity::<E>(epoch, decision_block_root)
19+
.is_ok()
20+
{
21+
// `EpochCache` has already been initialized and is valid, no need to initialize.
22+
return Ok(());
4823
}
49-
}
5024

51-
impl From<ArithError> for EpochCacheError {
52-
fn from(e: ArithError) -> Self {
53-
Self::Arith(e)
25+
// Compute base rewards.
26+
let total_active_balance = state.get_total_active_balance_at_epoch(epoch)?;
27+
let sqrt_total_active_balance = SqrtTotalActiveBalance::new(total_active_balance);
28+
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
29+
30+
let mut base_rewards = Vec::with_capacity(state.validators().len());
31+
32+
for validator in state.validators().iter() {
33+
let effective_balance = validator.effective_balance();
34+
35+
let base_reward = if spec
36+
.altair_fork_epoch
37+
.map_or(false, |altair_epoch| epoch < altair_epoch)
38+
{
39+
base::get_base_reward(effective_balance, sqrt_total_active_balance, spec)?
40+
} else {
41+
altair::get_base_reward(effective_balance, base_reward_per_increment, spec)?
42+
};
43+
base_rewards.push(base_reward);
5444
}
55-
}
5645

57-
impl EpochCache {
58-
pub fn new<E: EthSpec>(
59-
state: &BeaconState<E>,
60-
spec: &ChainSpec,
61-
) -> Result<Self, EpochCacheError> {
62-
let epoch = state.current_epoch();
63-
let decision_block_root = state
64-
.proposer_shuffling_decision_root(Hash256::zero())
65-
.map_err(EpochCacheError::BeaconState)?;
46+
*state.epoch_cache_mut() = EpochCache::new(
47+
EpochCacheKey {
48+
epoch,
49+
decision_block_root,
50+
},
51+
base_rewards,
52+
);
6653

67-
// The cache should never be constructed at slot 0 because it should only be used for
68-
// block processing (which implies slot > 0) or epoch processing (which implies slot >= 32).
69-
/* FIXME(sproul): EF tests like this
70-
if decision_block_root.is_zero() {
71-
return Err(EpochCacheError::InvalidSlot { slot: state.slot() });
72-
}
73-
*/
74-
75-
// Compute base rewards.
76-
let total_active_balance = state.get_total_active_balance()?;
77-
let sqrt_total_active_balance = SqrtTotalActiveBalance::new(total_active_balance);
78-
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
79-
80-
let mut base_rewards = Vec::with_capacity(state.validators().len());
81-
82-
for validator in state.validators().iter() {
83-
let effective_balance = validator.effective_balance();
84-
85-
let base_reward = if spec
86-
.altair_fork_epoch
87-
.map_or(false, |altair_epoch| epoch < altair_epoch)
88-
{
89-
base::get_base_reward(effective_balance, sqrt_total_active_balance, spec)?
90-
} else {
91-
altair::get_base_reward(effective_balance, base_reward_per_increment, spec)?
92-
};
93-
base_rewards.push(base_reward);
94-
}
95-
96-
Ok(Self {
97-
inner: Arc::new(Inner {
98-
key: EpochCacheKey {
99-
epoch,
100-
decision_block_root,
101-
},
102-
base_rewards,
103-
}),
104-
})
105-
}
106-
107-
pub fn check_validity<E: EthSpec>(
108-
&self,
109-
state: &BeaconState<E>,
110-
) -> Result<(), EpochCacheError> {
111-
if self.inner.key.epoch != state.current_epoch() {
112-
return Err(EpochCacheError::IncorrectEpoch {
113-
cache: self.inner.key.epoch,
114-
state: state.current_epoch(),
115-
});
116-
}
117-
let state_decision_root = state
118-
.proposer_shuffling_decision_root(Hash256::zero())
119-
.map_err(EpochCacheError::BeaconState)?;
120-
if self.inner.key.decision_block_root != state_decision_root {
121-
return Err(EpochCacheError::IncorrectDecisionBlock {
122-
cache: self.inner.key.decision_block_root,
123-
state: state_decision_root,
124-
});
125-
}
126-
Ok(())
127-
}
128-
129-
#[inline]
130-
pub fn get_base_reward(&self, validator_index: usize) -> Result<u64, EpochCacheError> {
131-
self.inner
132-
.base_rewards
133-
.get(validator_index)
134-
.copied()
135-
.ok_or(EpochCacheError::ValidatorIndexOutOfBounds { validator_index })
136-
}
54+
Ok(())
13755
}

consensus/state_processing/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ pub mod verify_operation;
3030

3131
pub use block_replayer::{BlockReplayError, BlockReplayer, StateProcessingStrategy};
3232
pub use consensus_context::{ConsensusContext, ContextError};
33-
pub use epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey};
3433
pub use genesis::{
3534
eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state,
3635
process_activations,
@@ -43,4 +42,5 @@ pub use per_epoch_processing::{
4342
errors::EpochProcessingError, process_epoch as per_epoch_processing,
4443
};
4544
pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError};
45+
pub use types::{EpochCache, EpochCacheError, EpochCacheKey};
4646
pub use verify_operation::{SigVerifiedOp, VerifyOperation, VerifyOperationAt};

consensus/state_processing/src/per_block_processing.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod verify_proposer_slashing;
4141
use crate::common::decrease_balance;
4242
use crate::StateProcessingStrategy;
4343

44+
use crate::epoch_cache::initialize_epoch_cache;
4445
#[cfg(feature = "arbitrary-fuzz")]
4546
use arbitrary::Arbitrary;
4647

@@ -114,6 +115,9 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
114115
.fork_name(spec)
115116
.map_err(BlockProcessingError::InconsistentStateFork)?;
116117

118+
// Build epoch cache if it hasn't already been built, or if it is no longer valid
119+
initialize_epoch_cache(state, state.current_epoch(), spec)?;
120+
117121
let verify_signatures = match block_signature_strategy {
118122
BlockSignatureStrategy::VerifyBulk => {
119123
// Verify all signatures in the block at once.

consensus/state_processing/src/per_block_processing/process_operations.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ pub mod altair {
113113
})
114114
}
115115

116+
#[allow(clippy::too_many_arguments)]
116117
pub fn process_attestation<T: EthSpec>(
117118
state: &mut BeaconState<T>,
118119
attestation: &Attestation<T>,
@@ -149,18 +150,22 @@ pub mod altair {
149150
let index = *index as usize;
150151

151152
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
152-
let epoch_participation = state.get_epoch_participation_mut(data.target.epoch)?;
153-
let validator_participation = epoch_participation
154-
.get_mut(index)
155-
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
156-
157-
if participation_flag_indices.contains(&flag_index)
158-
&& !validator_participation.has_flag(flag_index)?
159-
{
160-
validator_participation.add_flag(flag_index)?;
161-
proposer_reward_numerator.safe_add_assign(
162-
ctxt.get_base_reward(state, index, spec)?.safe_mul(weight)?,
163-
)?;
153+
let epoch_participation = state.get_epoch_participation_mut(
154+
data.target.epoch,
155+
ctxt.previous_epoch,
156+
ctxt.current_epoch,
157+
)?;
158+
159+
if participation_flag_indices.contains(&flag_index) {
160+
let validator_participation = epoch_participation
161+
.get_mut(index)
162+
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
163+
164+
if !validator_participation.has_flag(flag_index)? {
165+
validator_participation.add_flag(flag_index)?;
166+
proposer_reward_numerator
167+
.safe_add_assign(state.get_base_reward(index)?.safe_mul(weight)?)?;
168+
}
164169
}
165170
}
166171
}

0 commit comments

Comments
 (0)