-
Notifications
You must be signed in to change notification settings - Fork 902
Description
Description
Throughout state processing, we quite often make use of a pattern where we pass in a pre-computed value in order to avoid recomputing it, e.g. the state_root
passed to per_slot_processing
or the total_active_balance
passed to various rewards processing functions. There are more opportunities for such optimisations, including the proposer index (#598) and other properties of the validator set (unslashed_participating_indices
for the 3 different flags).
My idea for neatly generalising this pattern and enabling more of these optimisations is to add a single context struct to contain these values, and pass it around between all relevant state transition functions:
pub struct ConsensusContext<T: EthSpec> {
/// Slot to act as an identifier/safeguard
slot: Slot,
state_root: Option<Hash256>,
proposer_index: Option<u64>,
...
}
The signature of per_slot_processing
would change as follows:
pub fn per_slot_processing<T: EthSpec>(
state: &mut BeaconState<T>,
context: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<Option<EpochProcessingSummary>, Error>;
The context would have constructors/builders for making a context with 0 or more pre-computed values. If you have the state_root
handy, you feed it in, or if you have the proposer index from the BeaconChain
's proposer index cache, you supply that.
let mut ctxt = ConsensusContext::new(slot)
.set_state_root(state_root)
.set_proposer_index(proposer_index);
Then in the consensus code when these values are required they are fetched via memoising accessors that either a) use the cached value if it exists, or b) compute the value from scratch and add it to the context for future use.
impl<T: EthSpec> ConsensusContext<T> {
pub fn get_beacon_proposer_index(
&mut self,
state: &BeaconState<T>,
spec: &ChainSpec
) -> Result<usize, Error> {
if let Some(proposer_index) = self.proposer_index {
return Ok(proposer_index);
}
let proposer_index = state.get_beacon_proposer_index(self.slot, spec)?;
self.proposer_index = Some(proposer_index)
Ok(proposer_index)
}
}
Alternatives
- As an alternative we could instead add these optional memoising caches to the
BeaconState
, but I think that would be messier, and would introduce cache invalidation bugs that don't exist with an ephemeral cache like theConsensusContext
- Plumb parameters around manually. Also messier, introduces friction for new optimisations, no memoisation logic.