02e2fd2fb8
## 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.
162 lines
4.9 KiB
Rust
162 lines
4.9 KiB
Rust
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())
|
|
}
|
|
}
|