Skip to content

Implement consensus context optimisation #2371

@michaelsproul

Description

@michaelsproul

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 the ConsensusContext
  • Plumb parameters around manually. Also messier, introduces friction for new optimisations, no memoisation logic.

Metadata

Metadata

Assignees

Labels

A1RFCRequest for commentconsensusAn issue/PR that touches consensus code, such as state_processing or block verification.optimizationSomething to make Lighthouse run more efficiently.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions