use crate::common::get_indexed_attestation; use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError}; use crate::{EpochCache, EpochCacheError}; use std::borrow::Cow; use std::collections::{hash_map::Entry, HashMap}; use std::marker::PhantomData; use tree_hash::TreeHash; use types::{ AbstractExecPayload, Attestation, AttestationData, BeaconState, BeaconStateError, BitList, ChainSpec, Epoch, EthSpec, Hash256, IndexedAttestation, SignedBeaconBlock, Slot, }; #[derive(Debug, Clone)] pub struct ConsensusContext { /// Slot to act as an identifier/safeguard slot: Slot, /// Proposer index of the block at `slot`. proposer_index: Option, /// Block root of the block at `slot`. current_block_root: Option, /// Epoch cache of values that are useful for block processing that are static over an epoch. epoch_cache: Option, /// Cache of indexed attestations constructed during block processing. indexed_attestations: HashMap<(AttestationData, BitList), IndexedAttestation>, _phantom: PhantomData, } #[derive(Debug, PartialEq, Clone)] pub enum ContextError { BeaconState(BeaconStateError), EpochCache(EpochCacheError), SlotMismatch { slot: Slot, expected: Slot }, EpochMismatch { epoch: Epoch, expected: Epoch }, } impl From for ContextError { fn from(e: BeaconStateError) -> Self { Self::BeaconState(e) } } impl From for ContextError { fn from(e: EpochCacheError) -> Self { Self::EpochCache(e) } } impl ConsensusContext { pub fn new(slot: Slot) -> Self { Self { slot, proposer_index: None, current_block_root: None, epoch_cache: None, indexed_attestations: HashMap::new(), _phantom: PhantomData, } } pub fn set_proposer_index(mut self, proposer_index: u64) -> Self { self.proposer_index = Some(proposer_index); self } /// Strict method for fetching the proposer index. /// /// Gets the proposer index for `self.slot` while ensuring that it matches `state.slot()`. This /// method should be used in block processing and almost everywhere the proposer index is /// required. If the slot check is too restrictive, see `get_proposer_index_from_epoch_state`. pub fn get_proposer_index( &mut self, state: &BeaconState, spec: &ChainSpec, ) -> Result { self.check_slot(state.slot())?; self.get_proposer_index_no_checks(state, spec) } /// More liberal method for fetching the proposer index. /// /// Fetches the proposer index for `self.slot` but does not require the state to be from an /// exactly matching slot (merely a matching epoch). This is useful in batch verification where /// we want to extract the proposer index from a single state for every slot in the epoch. pub fn get_proposer_index_from_epoch_state( &mut self, state: &BeaconState, spec: &ChainSpec, ) -> Result { self.check_epoch(state.current_epoch())?; self.get_proposer_index_no_checks(state, spec) } fn get_proposer_index_no_checks( &mut self, state: &BeaconState, spec: &ChainSpec, ) -> Result { if let Some(proposer_index) = self.proposer_index { return Ok(proposer_index); } let proposer_index = state.get_beacon_proposer_index(self.slot, spec)? as u64; self.proposer_index = Some(proposer_index); Ok(proposer_index) } pub fn set_current_block_root(mut self, block_root: Hash256) -> Self { self.current_block_root = Some(block_root); self } pub fn get_current_block_root>( &mut self, block: &SignedBeaconBlock, ) -> Result { self.check_slot(block.slot())?; if let Some(current_block_root) = self.current_block_root { return Ok(current_block_root); } let current_block_root = block.message().tree_hash_root(); self.current_block_root = Some(current_block_root); Ok(current_block_root) } fn check_slot(&self, slot: Slot) -> Result<(), ContextError> { if slot == self.slot { Ok(()) } else { Err(ContextError::SlotMismatch { slot, expected: self.slot, }) } } fn check_epoch(&self, epoch: Epoch) -> Result<(), ContextError> { let expected = self.slot.epoch(T::slots_per_epoch()); if epoch == expected { Ok(()) } else { Err(ContextError::EpochMismatch { epoch, expected }) } } pub fn set_epoch_cache(mut self, epoch_cache: EpochCache) -> Self { self.epoch_cache = Some(epoch_cache); self } pub fn get_base_reward( &mut self, state: &BeaconState, validator_index: usize, spec: &ChainSpec, ) -> Result { self.check_slot(state.slot())?; // Build epoch cache if not already built. let epoch_cache = if let Some(ref cache) = self.epoch_cache { Cow::Borrowed(cache) } else { let cache = EpochCache::new(state, spec)?; self.epoch_cache = Some(cache.clone()); Cow::Owned(cache) }; Ok(epoch_cache.get_base_reward(validator_index)?) } pub fn get_indexed_attestation( &mut self, state: &BeaconState, attestation: &Attestation, ) -> Result<&IndexedAttestation, BlockOperationError> { let key = ( attestation.data.clone(), attestation.aggregation_bits.clone(), ); match self.indexed_attestations.entry(key) { Entry::Occupied(occupied) => Ok(occupied.into_mut()), Entry::Vacant(vacant) => { let committee = state.get_beacon_committee(attestation.data.slot, attestation.data.index)?; let indexed_attestation = get_indexed_attestation(committee.committee, attestation)?; Ok(vacant.insert(indexed_attestation)) } } } pub fn num_cached_indexed_attestations(&self) -> usize { self.indexed_attestations.len() } }