State cache tweaks (#7095)

Backport of:

- https://github.com/sigp/lighthouse/pull/7067

For:

- https://github.com/sigp/lighthouse/issues/7039


  - Prevent writing to state cache when migrating the database
- Add `state-cache-headroom` flag to control pruning
- Prune old epoch boundary states ahead of mid-epoch states
- Never prune head block's state
- Avoid caching ancestor states unless they are on an epoch boundary
- Log when states enter/exit the cache

Co-authored-by: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
Michael Sproul
2025-03-18 13:10:21 +11:00
committed by GitHub
parent 8ce9edc584
commit 4de062626b
29 changed files with 358 additions and 114 deletions

View File

@@ -815,8 +815,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let block = self
.get_blinded_block(&block_root)?
.ok_or(Error::MissingBeaconBlock(block_root))?;
// This method is only used in tests, so we may as well cache states to make CI go brr.
// TODO(release-v7) move this method out of beacon chain and into `store_tests`` or something equivalent.
let state = self
.get_state(&block.state_root(), Some(block.slot()))?
.get_state(&block.state_root(), Some(block.slot()), true)?
.ok_or_else(|| Error::MissingBeaconState(block.state_root()))?;
let iter = BlockRootsIterator::owned(&self.store, state);
Ok(std::iter::once(Ok((block_root, block.slot())))
@@ -1343,8 +1345,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&self,
state_root: &Hash256,
slot: Option<Slot>,
update_cache: bool,
) -> Result<Option<BeaconState<T::EthSpec>>, Error> {
Ok(self.store.get_state(state_root, slot)?)
Ok(self.store.get_state(state_root, slot, update_cache)?)
}
/// Return the sync committee at `slot + 1` from the canonical chain.
@@ -1519,8 +1522,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
})?
.ok_or(Error::NoStateForSlot(slot))?;
// This branch is mostly reached from the HTTP API when doing analysis, or in niche
// situations when producing a block. In the HTTP API case we assume the user wants
// to cache states so that future calls are faster, and that if the cache is
// struggling due to non-finality that they will dial down inessential calls. In the
// block proposal case we want to cache the state so that we can process the block
// quickly after it has been signed.
Ok(self
.get_state(&state_root, Some(slot))?
.get_state(&state_root, Some(slot), true)?
.ok_or(Error::NoStateForSlot(slot))?)
}
}
@@ -6916,9 +6925,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
})?;
let beacon_state_root = beacon_block.state_root();
// This branch is reached from the HTTP API. We assume the user wants
// to cache states so that future calls are faster.
let mut beacon_state = self
.store
.get_state(&beacon_state_root, Some(beacon_block.slot()))?
.get_state(&beacon_state_root, Some(beacon_block.slot()), true)?
.ok_or_else(|| {
Error::DBInconsistent(format!("Missing state {:?}", beacon_state_root))
})?;
@@ -7070,8 +7081,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
if signed_beacon_block.slot() % T::EthSpec::slots_per_epoch() == 0 {
let block = self.get_blinded_block(&block_hash).unwrap().unwrap();
// This branch is reached from the HTTP API. We assume the user wants
// to cache states so that future calls are faster.
let state = self
.get_state(&block.state_root(), Some(block.slot()))
.get_state(&block.state_root(), Some(block.slot()), true)
.unwrap()
.unwrap();
finalized_blocks.insert(state.finalized_checkpoint().root);