mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-06 18:21:45 +00:00
Add early attester cache (#2872)
## Issue Addressed NA ## Proposed Changes Introduces a cache to attestation to produce atop blocks which will become the head, but are not fully imported (e.g., not inserted into the database). Whilst attesting to a block before it's imported is rather easy, if we're going to produce that attestation then we also need to be able to: 1. Verify that attestation. 1. Respond to RPC requests for the `beacon_block_root`. Attestation verification (1) is *partially* covered. Since we prime the shuffling cache before we insert the block into the early attester cache, we should be fine for all typical use-cases. However, it is possible that the cache is washed out before we've managed to insert the state into the database and then attestation verification will fail with a "missing beacon state"-type error. Providing the block via RPC (2) is also partially covered, since we'll check the database *and* the early attester cache when responding a blocks-by-root request. However, we'll still omit the block from blocks-by-range requests (until the block lands in the DB). I *think* this is fine, since there's no guarantee that we return all blocks for those responses. Another important consideration is whether or not the *parent* of the early attester block is available in the databse. If it were not, we might fail to respond to blocks-by-root request that are iterating backwards to collect a chain of blocks. I argue that *we will always have the parent of the early attester block in the database.* This is because we are holding the fork-choice write-lock when inserting the block into the early attester cache and we do not drop that until the block is in the database.
This commit is contained in:
161
beacon_node/beacon_chain/src/early_attester_cache.rs
Normal file
161
beacon_node/beacon_chain/src/early_attester_cache.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::{
|
||||
attester_cache::{CommitteeLengths, Error},
|
||||
metrics,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use proto_array::Block as ProtoBlock;
|
||||
use types::*;
|
||||
|
||||
pub struct CacheItem<E: EthSpec> {
|
||||
/*
|
||||
* Values used to create attestations.
|
||||
*/
|
||||
epoch: Epoch,
|
||||
committee_lengths: CommitteeLengths,
|
||||
beacon_block_root: Hash256,
|
||||
source: Checkpoint,
|
||||
target: Checkpoint,
|
||||
/*
|
||||
* Values used to make the block available.
|
||||
*/
|
||||
block: SignedBeaconBlock<E>,
|
||||
proto_block: ProtoBlock,
|
||||
}
|
||||
|
||||
/// Provides a single-item cache which allows for attesting to blocks before those blocks have
|
||||
/// reached the database.
|
||||
///
|
||||
/// This cache stores enough information to allow Lighthouse to:
|
||||
///
|
||||
/// - Produce an attestation without using `chain.canonical_head`.
|
||||
/// - Verify that a block root exists (i.e., will be imported in the future) during attestation
|
||||
/// verification.
|
||||
/// - Provide a block which can be sent to peers via RPC.
|
||||
#[derive(Default)]
|
||||
pub struct EarlyAttesterCache<E: EthSpec> {
|
||||
item: RwLock<Option<CacheItem<E>>>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec> EarlyAttesterCache<E> {
|
||||
/// Removes the cached item, meaning that all future calls to `Self::try_attest` will return
|
||||
/// `None` until a new cache item is added.
|
||||
pub fn clear(&self) {
|
||||
*self.item.write() = None
|
||||
}
|
||||
|
||||
/// Updates the cache item, so that `Self::try_attest` with return `Some` when given suitable
|
||||
/// parameters.
|
||||
pub fn add_head_block(
|
||||
&self,
|
||||
beacon_block_root: Hash256,
|
||||
block: SignedBeaconBlock<E>,
|
||||
proto_block: ProtoBlock,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let epoch = state.current_epoch();
|
||||
let committee_lengths = CommitteeLengths::new(state, spec)?;
|
||||
let source = state.current_justified_checkpoint();
|
||||
let target_slot = epoch.start_slot(E::slots_per_epoch());
|
||||
let target = Checkpoint {
|
||||
epoch,
|
||||
root: if state.slot() <= target_slot {
|
||||
beacon_block_root
|
||||
} else {
|
||||
*state.get_block_root(target_slot)?
|
||||
},
|
||||
};
|
||||
|
||||
let item = CacheItem {
|
||||
epoch,
|
||||
committee_lengths,
|
||||
beacon_block_root,
|
||||
source,
|
||||
target,
|
||||
block,
|
||||
proto_block,
|
||||
};
|
||||
|
||||
*self.item.write() = Some(item);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Will return `Some(attestation)` if all the following conditions are met:
|
||||
///
|
||||
/// - There is a cache `item` present.
|
||||
/// - If `request_slot` is in the same epoch as `item.epoch`.
|
||||
/// - If `request_index` does not exceed `item.comittee_count`.
|
||||
pub fn try_attest(
|
||||
&self,
|
||||
request_slot: Slot,
|
||||
request_index: CommitteeIndex,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<Attestation<E>>, Error> {
|
||||
let lock = self.item.read();
|
||||
let item = if let Some(item) = lock.as_ref() {
|
||||
item
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let request_epoch = request_slot.epoch(E::slots_per_epoch());
|
||||
if request_epoch != item.epoch {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let committee_count = item
|
||||
.committee_lengths
|
||||
.get_committee_count_per_slot::<E>(spec)?;
|
||||
if request_index >= committee_count as u64 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let committee_len =
|
||||
item.committee_lengths
|
||||
.get_committee_length::<E>(request_slot, request_index, spec)?;
|
||||
|
||||
let attestation = Attestation {
|
||||
aggregation_bits: BitList::with_capacity(committee_len)
|
||||
.map_err(BeaconStateError::from)?,
|
||||
data: AttestationData {
|
||||
slot: request_slot,
|
||||
index: request_index,
|
||||
beacon_block_root: item.beacon_block_root,
|
||||
source: item.source,
|
||||
target: item.target,
|
||||
},
|
||||
signature: AggregateSignature::empty(),
|
||||
};
|
||||
|
||||
metrics::inc_counter(&metrics::BEACON_EARLY_ATTESTER_CACHE_HITS);
|
||||
|
||||
Ok(Some(attestation))
|
||||
}
|
||||
|
||||
/// Returns `true` if `block_root` matches the cached item.
|
||||
pub fn contains_block(&self, block_root: Hash256) -> bool {
|
||||
self.item
|
||||
.read()
|
||||
.as_ref()
|
||||
.map_or(false, |item| item.beacon_block_root == block_root)
|
||||
}
|
||||
|
||||
/// Returns the block, if `block_root` matches the cached item.
|
||||
pub fn get_block(&self, block_root: Hash256) -> Option<SignedBeaconBlock<E>> {
|
||||
self.item
|
||||
.read()
|
||||
.as_ref()
|
||||
.filter(|item| item.beacon_block_root == block_root)
|
||||
.map(|item| item.block.clone())
|
||||
}
|
||||
|
||||
/// Returns the proto-array block, if `block_root` matches the cached item.
|
||||
pub fn get_proto_block(&self, block_root: Hash256) -> Option<ProtoBlock> {
|
||||
self.item
|
||||
.read()
|
||||
.as_ref()
|
||||
.filter(|item| item.beacon_block_root == block_root)
|
||||
.map(|item| item.proto_block.clone())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user