Files
lighthouse/beacon_node/store/src/historic_state_cache.rs
Lion - dapplion dd98534158 Hierarchical state diffs in hot DB (#6750)
This PR implements https://github.com/sigp/lighthouse/pull/5978 (tree-states) but on the hot DB. It allows Lighthouse to massively reduce its disk footprint during non-finality and overall I/O in all cases.

Closes https://github.com/sigp/lighthouse/issues/6580

Conga into https://github.com/sigp/lighthouse/pull/6744

### TODOs

- [x] Fix OOM in CI https://github.com/sigp/lighthouse/pull/7176
- [x] optimise store_hot_state to avoid storing a duplicate state if the summary already exists (should be safe from races now that pruning is cleaner)
- [x] mispelled: get_ancenstor_state_root
- [x] get_ancestor_state_root should use state summaries
- [x] Prevent split from changing during ancestor calc
- [x] Use same hierarchy for hot and cold

### TODO Good optimization for future PRs

- [ ] On the migration, if the latest hot snapshot is aligned with the cold snapshot migrate the diffs instead of the full states.
```
align slot  time
10485760    Nov-26-2024
12582912    Sep-14-2025
14680064    Jul-02-2026
```

### TODO Maybe things good to have

- [ ] Rename anchor_slot https://github.com/sigp/lighthouse/compare/tree-states-hot-rebase-oom...dapplion:lighthouse:tree-states-hot-anchor-slot-rename?expand=1
- [ ] Make anchor fields not public such that they must be mutated through a method. To prevent un-wanted changes of the anchor_slot

### NOTTODO

- [ ] Use fork-choice and a new method [`descendants_of_checkpoint`](ca2388e196 (diff-046fbdb517ca16b80e4464c2c824cf001a74a0a94ac0065e635768ac391062a8)) to filter only the state summaries that descend of finalized checkpoint]
2025-06-19 02:43:25 +00:00

99 lines
3.2 KiB
Rust

use crate::hdiff::{Error, HDiffBuffer};
use crate::metrics;
use lru::LruCache;
use std::num::NonZeroUsize;
use types::{BeaconState, ChainSpec, EthSpec, Slot};
/// Holds a combination of finalized states in two formats:
/// - `hdiff_buffers`: Format close to an SSZ serialized state for rapid application of diffs on top
/// of it
/// - `states`: Deserialized states for direct use or for rapid application of blocks (replay)
///
/// An example use: when requesting state data for consecutive slots, this cache allows the node to
/// apply diffs once on the first request, and latter just apply blocks one at a time.
#[derive(Debug)]
pub struct HistoricStateCache<E: EthSpec> {
hdiff_buffers: LruCache<Slot, HDiffBuffer>,
states: LruCache<Slot, BeaconState<E>>,
}
#[derive(Debug, Default)]
pub struct Metrics {
pub num_hdiff: usize,
pub num_state: usize,
pub hdiff_byte_size: usize,
}
impl<E: EthSpec> HistoricStateCache<E> {
pub fn new(hdiff_buffer_cache_size: NonZeroUsize, state_cache_size: NonZeroUsize) -> Self {
Self {
hdiff_buffers: LruCache::new(hdiff_buffer_cache_size),
states: LruCache::new(state_cache_size),
}
}
pub fn get_hdiff_buffer(&mut self, slot: Slot) -> Option<HDiffBuffer> {
if let Some(buffer_ref) = self.hdiff_buffers.get(&slot) {
let _timer = metrics::start_timer_vec(
&metrics::BEACON_HDIFF_BUFFER_CLONE_TIME,
metrics::COLD_METRIC,
);
Some(buffer_ref.clone())
} else if let Some(state) = self.states.get(&slot) {
let buffer = HDiffBuffer::from_state(state.clone());
let _timer = metrics::start_timer_vec(
&metrics::BEACON_HDIFF_BUFFER_CLONE_TIME,
metrics::COLD_METRIC,
);
let cloned = buffer.clone();
drop(_timer);
self.hdiff_buffers.put(slot, cloned);
Some(buffer)
} else {
None
}
}
pub fn get_state(
&mut self,
slot: Slot,
spec: &ChainSpec,
) -> Result<Option<BeaconState<E>>, Error> {
if let Some(state) = self.states.get(&slot) {
Ok(Some(state.clone()))
} else if let Some(buffer) = self.hdiff_buffers.get(&slot) {
let state = buffer.as_state(spec)?;
self.states.put(slot, state.clone());
Ok(Some(state))
} else {
Ok(None)
}
}
pub fn put_state(&mut self, slot: Slot, state: BeaconState<E>) {
self.states.put(slot, state);
}
pub fn put_hdiff_buffer(&mut self, slot: Slot, buffer: HDiffBuffer) {
self.hdiff_buffers.put(slot, buffer);
}
pub fn put_both(&mut self, slot: Slot, state: BeaconState<E>, buffer: HDiffBuffer) {
self.put_state(slot, state);
self.put_hdiff_buffer(slot, buffer);
}
pub fn metrics(&self) -> Metrics {
let hdiff_byte_size = self
.hdiff_buffers
.iter()
.map(|(_, buffer)| buffer.size())
.sum::<usize>();
Metrics {
num_hdiff: self.hdiff_buffers.len(),
num_state: self.states.len(),
hdiff_byte_size,
}
}
}