Use THC for state.inactivity_scores (#2504)

## Issue Addressed

- Resolves #2502

## Proposed Changes

Adds tree-hash caching (THC 🍁) for `state.inactivity_scores`, as per #2502.

Since the `inactivity_scores` field is introduced during Altair, the cache must be optional (i.e., not present pre-Altair). The mechanism for optional caches was already implemented via the `ParticipationTreeHashCache`, albeit not quite generically enough. To this end, I made the `ParticipationTreeHashCache` more generic and renamed it to `OptionalTreeHashCache`. This made the code a little more verbose around the previous/current epoch participation fields, but overall less verbose when the needs of `inactivity_scores` are considered.

All changes to `ParticipationTreeHashCache` should be *non-substantial*.

## Additional Info

NA
This commit is contained in:
Paul Hauner 2021-08-09 04:58:17 +00:00
parent 7b46c4bb7a
commit 54f92cc263

View File

@ -3,9 +3,7 @@
#![allow(clippy::indexing_slicing)] #![allow(clippy::indexing_slicing)]
use super::Error; use super::Error;
use crate::{ use crate::{BeaconState, EthSpec, Hash256, ParticipationList, Slot, Unsigned, Validator};
BeaconState, EthSpec, Hash256, ParticipationFlags, ParticipationList, Slot, Unsigned, Validator,
};
use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache}; use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, TreeHashCache};
use rayon::prelude::*; use rayon::prelude::*;
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
@ -141,9 +139,10 @@ pub struct BeaconTreeHashCacheInner<T: EthSpec> {
randao_mixes: TreeHashCache, randao_mixes: TreeHashCache,
slashings: TreeHashCache, slashings: TreeHashCache,
eth1_data_votes: Eth1DataVotesTreeHashCache<T>, eth1_data_votes: Eth1DataVotesTreeHashCache<T>,
inactivity_scores: OptionalTreeHashCache,
// Participation caches // Participation caches
previous_epoch_participation: ParticipationTreeHashCache, previous_epoch_participation: OptionalTreeHashCache,
current_epoch_participation: ParticipationTreeHashCache, current_epoch_participation: OptionalTreeHashCache,
} }
impl<T: EthSpec> BeaconTreeHashCacheInner<T> { impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
@ -168,10 +167,22 @@ impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
let mut slashings_arena = CacheArena::default(); let mut slashings_arena = CacheArena::default();
let slashings = state.slashings().new_tree_hash_cache(&mut slashings_arena); let slashings = state.slashings().new_tree_hash_cache(&mut slashings_arena);
let previous_epoch_participation = let inactivity_scores = OptionalTreeHashCache::new(state.inactivity_scores().ok());
ParticipationTreeHashCache::new(state, BeaconState::previous_epoch_participation);
let current_epoch_participation = let previous_epoch_participation = OptionalTreeHashCache::new(
ParticipationTreeHashCache::new(state, BeaconState::current_epoch_participation); state
.previous_epoch_participation()
.ok()
.map(ParticipationList::new)
.as_ref(),
);
let current_epoch_participation = OptionalTreeHashCache::new(
state
.current_epoch_participation()
.ok()
.map(ParticipationList::new)
.as_ref(),
);
Self { Self {
previous_state: None, previous_state: None,
@ -185,6 +196,7 @@ impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
balances, balances,
randao_mixes, randao_mixes,
slashings, slashings,
inactivity_scores,
eth1_data_votes: Eth1DataVotesTreeHashCache::new(state), eth1_data_votes: Eth1DataVotesTreeHashCache::new(state),
previous_epoch_participation, previous_epoch_participation,
current_epoch_participation, current_epoch_participation,
@ -287,12 +299,16 @@ impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
} else { } else {
hasher.write( hasher.write(
self.previous_epoch_participation self.previous_epoch_participation
.recalculate_tree_hash_root(state.previous_epoch_participation()?)? .recalculate_tree_hash_root(&ParticipationList::new(
state.previous_epoch_participation()?,
))?
.as_bytes(), .as_bytes(),
)?; )?;
hasher.write( hasher.write(
self.current_epoch_participation self.current_epoch_participation
.recalculate_tree_hash_root(state.current_epoch_participation()?)? .recalculate_tree_hash_root(&ParticipationList::new(
state.current_epoch_participation()?,
))?
.as_bytes(), .as_bytes(),
)?; )?;
} }
@ -314,8 +330,11 @@ impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
// Inactivity & light-client sync committees // Inactivity & light-client sync committees
if let BeaconState::Altair(ref state) = state { if let BeaconState::Altair(ref state) = state {
// FIXME(altair): add cache for this field hasher.write(
hasher.write(state.inactivity_scores.tree_hash_root().as_bytes())?; self.inactivity_scores
.recalculate_tree_hash_root(&state.inactivity_scores)?
.as_bytes(),
)?;
hasher.write(state.current_sync_committee.tree_hash_root().as_bytes())?; hasher.write(state.current_sync_committee.tree_hash_root().as_bytes())?;
hasher.write(state.next_sync_committee.tree_hash_root().as_bytes())?; hasher.write(state.next_sync_committee.tree_hash_root().as_bytes())?;
@ -513,53 +532,43 @@ impl ParallelValidatorTreeHash {
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ParticipationTreeHashCache { pub struct OptionalTreeHashCache {
inner: Option<ParticipationTreeHashCacheInner>, inner: Option<OptionalTreeHashCacheInner>,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct ParticipationTreeHashCacheInner { pub struct OptionalTreeHashCacheInner {
arena: CacheArena, arena: CacheArena,
tree_hash_cache: TreeHashCache, tree_hash_cache: TreeHashCache,
} }
impl ParticipationTreeHashCache { impl OptionalTreeHashCache {
/// Initialize a new cache for the participation list returned by `field` (if any). /// Initialize a new cache if `item.is_some()`.
fn new<T: EthSpec>( fn new<C: CachedTreeHash<TreeHashCache>>(item: Option<&C>) -> Self {
state: &BeaconState<T>, let inner = item.map(OptionalTreeHashCacheInner::new);
field: impl FnOnce(
&BeaconState<T>,
) -> Result<
&VariableList<ParticipationFlags, T::ValidatorRegistryLimit>,
Error,
>,
) -> Self {
let inner = field(state).map(ParticipationTreeHashCacheInner::new).ok();
Self { inner } Self { inner }
} }
/// Compute the tree hash root for the given `epoch_participation`. /// Compute the tree hash root for the given `item`.
/// ///
/// This function will initialize the inner cache if necessary (e.g. when crossing the fork). /// This function will initialize the inner cache if necessary (e.g. when crossing the fork).
fn recalculate_tree_hash_root<N: Unsigned>( fn recalculate_tree_hash_root<C: CachedTreeHash<TreeHashCache>>(
&mut self, &mut self,
epoch_participation: &VariableList<ParticipationFlags, N>, item: &C,
) -> Result<Hash256, Error> { ) -> Result<Hash256, Error> {
let cache = self let cache = self
.inner .inner
.get_or_insert_with(|| ParticipationTreeHashCacheInner::new(epoch_participation)); .get_or_insert_with(|| OptionalTreeHashCacheInner::new(item));
ParticipationList::new(epoch_participation) item.recalculate_tree_hash_root(&mut cache.arena, &mut cache.tree_hash_cache)
.recalculate_tree_hash_root(&mut cache.arena, &mut cache.tree_hash_cache)
.map_err(Into::into) .map_err(Into::into)
} }
} }
impl ParticipationTreeHashCacheInner { impl OptionalTreeHashCacheInner {
fn new<N: Unsigned>(epoch_participation: &VariableList<ParticipationFlags, N>) -> Self { fn new<C: CachedTreeHash<TreeHashCache>>(item: &C) -> Self {
let mut arena = CacheArena::default(); let mut arena = CacheArena::default();
let tree_hash_cache = let tree_hash_cache = item.new_tree_hash_cache(&mut arena);
ParticipationList::new(epoch_participation).new_tree_hash_cache(&mut arena); OptionalTreeHashCacheInner {
ParticipationTreeHashCacheInner {
arena, arena,
tree_hash_cache, tree_hash_cache,
} }
@ -576,7 +585,7 @@ impl<T: EthSpec> arbitrary::Arbitrary for BeaconTreeHashCache<T> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::MainnetEthSpec; use crate::{MainnetEthSpec, ParticipationFlags};
#[test] #[test]
fn validator_node_count() { fn validator_node_count() {
@ -594,13 +603,13 @@ mod test {
test_flag.add_flag(0).unwrap(); test_flag.add_flag(0).unwrap();
let epoch_participation = VariableList::<_, N>::new(vec![test_flag; len]).unwrap(); let epoch_participation = VariableList::<_, N>::new(vec![test_flag; len]).unwrap();
let mut cache = ParticipationTreeHashCache { inner: None }; let mut cache = OptionalTreeHashCache { inner: None };
let cache_root = cache let cache_root = cache
.recalculate_tree_hash_root(&epoch_participation) .recalculate_tree_hash_root(&ParticipationList::new(&epoch_participation))
.unwrap(); .unwrap();
let recalc_root = cache let recalc_root = cache
.recalculate_tree_hash_root(&epoch_participation) .recalculate_tree_hash_root(&ParticipationList::new(&epoch_participation))
.unwrap(); .unwrap();
assert_eq!(cache_root, recalc_root, "recalculated root should match"); assert_eq!(cache_root, recalc_root, "recalculated root should match");