Forwards block root iterators (#672)

* Implement forwards block root iterators

* Clean up errors and docs
This commit is contained in:
Michael Sproul
2019-12-06 18:52:11 +11:00
committed by Paul Hauner
parent 779873680b
commit bd1b61a5b1
23 changed files with 573 additions and 187 deletions

View File

@@ -1,7 +1,7 @@
use super::*;
use ssz::{Decode, DecodeError};
fn get_block_bytes<T: Store, E: EthSpec>(
fn get_block_bytes<T: Store<E>, E: EthSpec>(
store: &T,
root: Hash256,
) -> Result<Option<Vec<u8>>, Error> {
@@ -23,7 +23,7 @@ fn read_parent_root_from_block_bytes(bytes: &[u8]) -> Result<Hash256, DecodeErro
Hash256::from_ssz_bytes(slice)
}
pub fn get_block_at_preceeding_slot<T: Store, E: EthSpec>(
pub fn get_block_at_preceeding_slot<T: Store<E>, E: EthSpec>(
store: &T,
slot: Slot,
start_root: Hash256,
@@ -36,7 +36,7 @@ pub fn get_block_at_preceeding_slot<T: Store, E: EthSpec>(
)
}
fn get_at_preceeding_slot<T: Store, E: EthSpec>(
fn get_at_preceeding_slot<T: Store<E>, E: EthSpec>(
store: &T,
slot: Slot,
mut root: Hash256,
@@ -107,7 +107,7 @@ mod tests {
}
fn build_chain(
store: &impl Store,
store: &impl Store<MinimalEthSpec>,
slots: &[usize],
spec: &ChainSpec,
) -> Vec<(Hash256, BeaconBlock)> {
@@ -157,7 +157,7 @@ mod tests {
#[test]
fn chain_with_skips() {
let store = MemoryStore::open();
let store = MemoryStore::<MinimalEthSpec>::open();
let spec = MinimalEthSpec::default_spec();
let slots = vec![0, 1, 2, 5];
@@ -181,14 +181,14 @@ mod tests {
// Slot that doesn't exist
let (source_root, _source_block) = &blocks_and_roots[3];
assert!(store
.get_block_at_preceeding_slot::<MinimalEthSpec>(*source_root, Slot::new(3))
.get_block_at_preceeding_slot(*source_root, Slot::new(3))
.unwrap()
.is_none());
// Slot too high
let (source_root, _source_block) = &blocks_and_roots[3];
assert!(store
.get_block_at_preceeding_slot::<MinimalEthSpec>(*source_root, Slot::new(3))
.get_block_at_preceeding_slot(*source_root, Slot::new(3))
.unwrap()
.is_none());
}

View File

@@ -0,0 +1,118 @@
use crate::chunked_vector::{chunk_key, Chunk, Field};
use crate::DiskStore;
use slog::error;
use std::sync::Arc;
use types::{ChainSpec, EthSpec, Slot};
/// Iterator over the values of a `BeaconState` vector field (like `block_roots`).
///
/// Uses the freezer DB's separate table to load the values.
pub struct ChunkedVectorIter<F, E>
where
F: Field<E>,
E: EthSpec,
{
pub(crate) store: Arc<DiskStore<E>>,
current_vindex: usize,
pub(crate) end_vindex: usize,
next_cindex: usize,
current_chunk: Chunk<F::Value>,
}
impl<F, E> ChunkedVectorIter<F, E>
where
F: Field<E>,
E: EthSpec,
{
/// Create a new iterator which can yield elements from `start_vindex` up to the last
/// index stored by the restore point at `last_restore_point_slot`.
///
/// The `last_restore_point` slot should be the slot of a recent restore point as obtained from
/// `DiskStore::get_latest_restore_point_slot`. We pass it as a parameter so that the caller can
/// maintain a stable view of the database (see `HybridForwardsBlockRootsIterator`).
pub fn new(
store: Arc<DiskStore<E>>,
start_vindex: usize,
last_restore_point_slot: Slot,
spec: &ChainSpec,
) -> Self {
let (_, end_vindex) = F::start_and_end_vindex(last_restore_point_slot, spec);
// Set the next chunk to the one containing `start_vindex`.
let next_cindex = start_vindex / F::chunk_size();
// Set the current chunk to the empty chunk, it will never be read.
let current_chunk = Chunk::default();
Self {
store,
current_vindex: start_vindex,
end_vindex,
next_cindex,
current_chunk,
}
}
}
impl<F, E> Iterator for ChunkedVectorIter<F, E>
where
F: Field<E>,
E: EthSpec,
{
type Item = (usize, F::Value);
fn next(&mut self) -> Option<Self::Item> {
let chunk_size = F::chunk_size();
// Range exhausted, return `None` forever.
if self.current_vindex >= self.end_vindex {
None
}
// Value lies in the current chunk, return it.
else if self.current_vindex < self.next_cindex * chunk_size {
let vindex = self.current_vindex;
let val = self
.current_chunk
.values
.get(vindex % chunk_size)
.cloned()
.or_else(|| {
error!(
self.store.log,
"Missing chunk value in forwards iterator";
"vector index" => vindex
);
None
})?;
self.current_vindex += 1;
Some((vindex, val))
}
// Need to load the next chunk, load it and recurse back into the in-range case.
else {
self.current_chunk = Chunk::load(
&self.store.cold_db,
F::column(),
&chunk_key(self.next_cindex as u64),
)
.map_err(|e| {
error!(
self.store.log,
"Database error in forwards iterator";
"chunk index" => self.next_cindex,
"error" => format!("{:?}", e)
);
e
})
.ok()?
.or_else(|| {
error!(
self.store.log,
"Missing chunk in forwards iterator";
"chunk index" => self.next_cindex
);
None
})?;
self.next_cindex += 1;
self.next()
}
}
}

View File

@@ -34,7 +34,7 @@ pub enum UpdatePattern {
/// Map a chunk index to bytes that can be used to key the NoSQL database.
///
/// We shift chunks up by 1 to make room for a genesis chunk that is handled separately.
fn chunk_key(cindex: u64) -> [u8; 8] {
pub fn chunk_key(cindex: u64) -> [u8; 8] {
(cindex + 1).to_be_bytes()
}
@@ -177,7 +177,7 @@ pub trait Field<E: EthSpec>: Copy {
/// Load the genesis value for a fixed length field from the store.
///
/// This genesis value should be used to fill the initial state of the vector.
fn load_genesis_value<S: Store>(store: &S) -> Result<Self::Value, Error> {
fn load_genesis_value<S: Store<E>>(store: &S) -> Result<Self::Value, Error> {
let key = &genesis_value_key()[..];
let chunk =
Chunk::load(store, Self::column(), key)?.ok_or(ChunkError::MissingGenesisValue)?;
@@ -192,7 +192,10 @@ pub trait Field<E: EthSpec>: Copy {
///
/// Check the existing value (if any) for consistency with the value we intend to store, and
/// return an error if they are inconsistent.
fn check_and_store_genesis_value<S: Store>(store: &S, value: Self::Value) -> Result<(), Error> {
fn check_and_store_genesis_value<S: Store<E>>(
store: &S,
value: Self::Value,
) -> Result<(), Error> {
let key = &genesis_value_key()[..];
if let Some(existing_chunk) = Chunk::<Self::Value>::load(store, Self::column(), key)? {
@@ -324,7 +327,7 @@ field!(
|state: &BeaconState<_>, index, _| safe_modulo_index(&state.randao_mixes, index)
);
pub fn store_updated_vector<F: Field<E>, E: EthSpec, S: Store>(
pub fn store_updated_vector<F: Field<E>, E: EthSpec, S: Store<E>>(
field: F,
store: &S,
state: &BeaconState<E>,
@@ -384,7 +387,7 @@ fn store_range<F, E, S, I>(
where
F: Field<E>,
E: EthSpec,
S: Store,
S: Store<E>,
I: Iterator<Item = usize>,
{
for chunk_index in range {
@@ -414,7 +417,7 @@ where
// Chunks at the end index are included.
// TODO: could be more efficient with a real range query (perhaps RocksDB)
fn range_query<S: Store, T: Decode + Encode>(
fn range_query<S: Store<E>, E: EthSpec, T: Decode + Encode>(
store: &S,
column: DBColumn,
start_index: usize,
@@ -479,7 +482,7 @@ fn stitch<T: Default + Clone>(
Ok(result)
}
pub fn load_vector_from_db<F: FixedLengthField<E>, E: EthSpec, S: Store>(
pub fn load_vector_from_db<F: FixedLengthField<E>, E: EthSpec, S: Store<E>>(
store: &S,
slot: Slot,
spec: &ChainSpec,
@@ -511,7 +514,7 @@ pub fn load_vector_from_db<F: FixedLengthField<E>, E: EthSpec, S: Store>(
}
/// The historical roots are stored in vector chunks, despite not actually being a vector.
pub fn load_variable_list_from_db<F: VariableLengthField<E>, E: EthSpec, S: Store>(
pub fn load_variable_list_from_db<F: VariableLengthField<E>, E: EthSpec, S: Store<E>>(
store: &S,
slot: Slot,
spec: &ChainSpec,
@@ -571,14 +574,23 @@ where
Chunk { values }
}
pub fn load<S: Store>(store: &S, column: DBColumn, key: &[u8]) -> Result<Option<Self>, Error> {
pub fn load<S: Store<E>, E: EthSpec>(
store: &S,
column: DBColumn,
key: &[u8],
) -> Result<Option<Self>, Error> {
store
.get_bytes(column.into(), key)?
.map(|bytes| Self::decode(&bytes))
.transpose()
}
pub fn store<S: Store>(&self, store: &S, column: DBColumn, key: &[u8]) -> Result<(), Error> {
pub fn store<S: Store<E>, E: EthSpec>(
&self,
store: &S,
column: DBColumn,
key: &[u8],
) -> Result<(), Error> {
store.put_bytes(column.into(), key, &self.encode()?)?;
Ok(())
}

View File

@@ -0,0 +1,164 @@
use crate::chunked_iter::ChunkedVectorIter;
use crate::chunked_vector::BlockRoots;
use crate::iter::{BlockRootsIterator, ReverseBlockRootIterator};
use crate::{DiskStore, Store};
use slog::error;
use std::sync::Arc;
use types::{BeaconState, ChainSpec, EthSpec, Hash256, Slot};
/// Forwards block roots iterator that makes use of the `block_roots` table in the freezer DB.
pub struct FrozenForwardsBlockRootsIterator<E: EthSpec> {
inner: ChunkedVectorIter<BlockRoots, E>,
}
/// Forwards block roots iterator that reverses a backwards iterator (only good for short ranges).
pub struct SimpleForwardsBlockRootsIterator {
// Values from the backwards iterator (in slot descending order)
values: Vec<(Hash256, Slot)>,
}
/// Fusion of the above two approaches to forwards iteration. Fast and efficient.
pub enum HybridForwardsBlockRootsIterator<E: EthSpec> {
PreFinalization {
iter: FrozenForwardsBlockRootsIterator<E>,
/// Data required by the `PostFinalization` iterator when we get to it.
continuation_data: Option<(BeaconState<E>, Hash256)>,
},
PostFinalization {
iter: SimpleForwardsBlockRootsIterator,
},
}
impl<E: EthSpec> FrozenForwardsBlockRootsIterator<E> {
pub fn new(
store: Arc<DiskStore<E>>,
start_slot: Slot,
last_restore_point_slot: Slot,
spec: &ChainSpec,
) -> Self {
Self {
inner: ChunkedVectorIter::new(
store,
start_slot.as_usize(),
last_restore_point_slot,
spec,
),
}
}
}
impl<E: EthSpec> Iterator for FrozenForwardsBlockRootsIterator<E> {
type Item = (Hash256, Slot);
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|(slot, block_hash)| (block_hash, Slot::from(slot)))
}
}
impl SimpleForwardsBlockRootsIterator {
pub fn new<S: Store<E>, E: EthSpec>(
store: Arc<S>,
start_slot: Slot,
end_state: BeaconState<E>,
end_block_root: Hash256,
) -> Self {
// Iterate backwards from the end state, stopping at the start slot.
Self {
values: ReverseBlockRootIterator::new(
(end_block_root, end_state.slot),
BlockRootsIterator::owned(store, end_state),
)
.take_while(|(_, slot)| *slot >= start_slot)
.collect(),
}
}
}
impl Iterator for SimpleForwardsBlockRootsIterator {
type Item = (Hash256, Slot);
fn next(&mut self) -> Option<Self::Item> {
// Pop from the end of the vector to get the block roots in slot-ascending order.
self.values.pop()
}
}
impl<E: EthSpec> HybridForwardsBlockRootsIterator<E> {
pub fn new(
store: Arc<DiskStore<E>>,
start_slot: Slot,
end_state: BeaconState<E>,
end_block_root: Hash256,
spec: &ChainSpec,
) -> Self {
use HybridForwardsBlockRootsIterator::*;
let latest_restore_point_slot = store.get_latest_restore_point_slot();
if start_slot < latest_restore_point_slot {
PreFinalization {
iter: FrozenForwardsBlockRootsIterator::new(
store,
start_slot,
latest_restore_point_slot,
spec,
),
continuation_data: Some((end_state, end_block_root)),
}
} else {
PostFinalization {
iter: SimpleForwardsBlockRootsIterator::new(
store,
start_slot,
end_state,
end_block_root,
),
}
}
}
}
impl<E: EthSpec> Iterator for HybridForwardsBlockRootsIterator<E> {
type Item = (Hash256, Slot);
fn next(&mut self) -> Option<Self::Item> {
use HybridForwardsBlockRootsIterator::*;
match self {
PreFinalization {
iter,
continuation_data,
} => {
match iter.next() {
Some(x) => Some(x),
// Once the pre-finalization iterator is consumed, transition
// to a post-finalization iterator beginning from the last slot
// of the pre iterator.
None => {
let (end_state, end_block_root) =
continuation_data.take().or_else(|| {
error!(
iter.inner.store.log,
"HybridForwardsBlockRootsIterator: logic error"
);
None
})?;
*self = PostFinalization {
iter: SimpleForwardsBlockRootsIterator::new(
iter.inner.store.clone(),
Slot::from(iter.inner.end_vindex),
end_state,
end_block_root,
),
};
self.next()
}
}
}
PostFinalization { iter } => iter.next(),
}
}
}

View File

@@ -1,6 +1,7 @@
use crate::chunked_vector::{
store_updated_vector, BlockRoots, HistoricalRoots, RandaoMixes, StateRoots,
};
use crate::forwards_iter::HybridForwardsBlockRootsIterator;
use crate::iter::{ParentRootBlockIterator, StateRootsIterator};
use crate::{
leveldb_store::LevelDB, DBColumn, Error, PartialBeaconState, SimpleStoreItem, Store, StoreItem,
@@ -14,6 +15,7 @@ use state_processing::{
SlotProcessingError,
};
use std::convert::TryInto;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use types::*;
@@ -25,7 +27,7 @@ pub const SPLIT_DB_KEY: &str = "FREEZERDBSPLITFREEZERDBSPLITFREE";
///
/// Stores vector fields like the `block_roots` and `state_roots` separately, and only stores
/// intermittent "restore point" states pre-finalization.
pub struct HotColdDB {
pub struct HotColdDB<E: EthSpec> {
/// The slot and state root at the point where the database is split between hot and cold.
///
/// States with slots less than `split.slot` are in the cold DB, while states with slots
@@ -34,15 +36,17 @@ pub struct HotColdDB {
/// Number of slots per restore point state in the freezer database.
slots_per_restore_point: u64,
/// Cold database containing compact historical data.
cold_db: LevelDB,
pub(crate) cold_db: LevelDB<E>,
/// Hot database containing duplicated but quick-to-access recent data.
///
/// The hot database also contains all blocks.
hot_db: LevelDB,
pub(crate) hot_db: LevelDB<E>,
/// Chain spec.
spec: ChainSpec,
/// Logger.
pub(crate) log: Logger,
/// Mere vessel for E.
_phantom: PhantomData<E>,
}
#[derive(Debug, PartialEq)]
@@ -71,7 +75,9 @@ pub enum HotColdDbError {
RestorePointBlockHashError(BeaconStateError),
}
impl Store for HotColdDB {
impl<E: EthSpec> Store<E> for HotColdDB<E> {
type ForwardsBlockRootsIterator = HybridForwardsBlockRootsIterator<E>;
// Defer to the hot database for basic operations (including blocks for now)
fn get_bytes(&self, column: &str, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
self.hot_db.get_bytes(column, key)
@@ -90,11 +96,7 @@ impl Store for HotColdDB {
}
/// Store a state in the store.
fn put_state<E: EthSpec>(
&self,
state_root: &Hash256,
state: &BeaconState<E>,
) -> Result<(), Error> {
fn put_state(&self, state_root: &Hash256, state: &BeaconState<E>) -> Result<(), Error> {
if state.slot < self.get_split_slot() {
self.store_archive_state(state_root, state)
} else {
@@ -103,7 +105,7 @@ impl Store for HotColdDB {
}
/// Fetch a state from the store.
fn get_state<E: EthSpec>(
fn get_state(
&self,
state_root: &Hash256,
slot: Option<Slot>,
@@ -129,7 +131,7 @@ impl Store for HotColdDB {
}
/// Advance the split point of the store, moving new finalized states to the freezer.
fn freeze_to_state<E: EthSpec>(
fn freeze_to_state(
store: Arc<Self>,
frozen_head_root: Hash256,
frozen_head: &BeaconState<E>,
@@ -157,7 +159,7 @@ impl Store for HotColdDB {
for (state_root, slot) in
state_root_iter.take_while(|&(_, slot)| slot >= current_split_slot)
{
if slot % store.slots_per_restore_point == 0 {
if slot % dbg!(store.slots_per_restore_point) == 0 {
let state: BeaconState<E> = store
.hot_db
.get_state(&state_root, None)?
@@ -195,20 +197,30 @@ impl Store for HotColdDB {
Ok(())
}
fn forwards_block_roots_iterator(
store: Arc<Self>,
start_slot: Slot,
end_state: BeaconState<E>,
end_block_root: Hash256,
spec: &ChainSpec,
) -> Self::ForwardsBlockRootsIterator {
HybridForwardsBlockRootsIterator::new(store, start_slot, end_state, end_block_root, spec)
}
}
impl HotColdDB {
impl<E: EthSpec> HotColdDB<E> {
/// Open a new or existing database, with the given paths to the hot and cold DBs.
///
/// The `slots_per_restore_point` parameter must be a divisor of `SLOTS_PER_HISTORICAL_ROOT`.
pub fn open<E: EthSpec>(
pub fn open(
hot_path: &Path,
cold_path: &Path,
slots_per_restore_point: u64,
spec: ChainSpec,
log: Logger,
) -> Result<Self, Error> {
Self::verify_slots_per_restore_point::<E>(slots_per_restore_point)?;
Self::verify_slots_per_restore_point(slots_per_restore_point)?;
let db = HotColdDB {
split: RwLock::new(Split::default()),
@@ -217,6 +229,7 @@ impl HotColdDB {
hot_db: LevelDB::open(hot_path)?,
spec,
log,
_phantom: PhantomData,
};
// Load the previous split slot from the database (if any). This ensures we can
@@ -230,7 +243,7 @@ impl HotColdDB {
/// Store a pre-finalization state in the freezer database.
///
/// Will return an error if the state does not lie on a restore point boundary.
pub fn store_archive_state<E: EthSpec>(
pub fn store_archive_state(
&self,
state_root: &Hash256,
state: &BeaconState<E>,
@@ -251,6 +264,7 @@ impl HotColdDB {
"slot" => state.slot,
"state_root" => format!("{:?}", state_root)
);
println!("Creating restore point {}", state.slot);
// 1. Convert to PartialBeaconState and store that in the DB.
let partial_state = PartialBeaconState::from_state_forgetful(state);
@@ -273,7 +287,7 @@ impl HotColdDB {
/// Load a pre-finalization state from the freezer database.
///
/// Will reconstruct the state if it lies between restore points.
pub fn load_archive_state<E: EthSpec>(
pub fn load_archive_state(
&self,
state_root: &Hash256,
slot: Slot,
@@ -286,10 +300,7 @@ impl HotColdDB {
}
/// Load a restore point state by its `state_root`.
fn load_restore_point<E: EthSpec>(
&self,
state_root: &Hash256,
) -> Result<BeaconState<E>, Error> {
fn load_restore_point(&self, state_root: &Hash256) -> Result<BeaconState<E>, Error> {
let mut partial_state = PartialBeaconState::db_get(&self.cold_db, state_root)?
.ok_or_else(|| HotColdDbError::MissingRestorePoint(*state_root))?;
@@ -303,7 +314,7 @@ impl HotColdDB {
}
/// Load a restore point state by its `restore_point_index`.
fn load_restore_point_by_index<E: EthSpec>(
fn load_restore_point_by_index(
&self,
restore_point_index: u64,
) -> Result<BeaconState<E>, Error> {
@@ -312,7 +323,7 @@ impl HotColdDB {
}
/// Load a state that lies between restore points.
fn load_intermediate_state<E: EthSpec>(
fn load_intermediate_state(
&self,
state_root: &Hash256,
slot: Slot,
@@ -330,7 +341,7 @@ impl HotColdDB {
let high_restore_point = if high_restore_point_idx * self.slots_per_restore_point
>= split.slot.as_u64()
{
self.get_state::<E>(&split.state_root, Some(split.slot))?
self.get_state(&split.state_root, Some(split.slot))?
.ok_or_else(|| HotColdDbError::MissingSplitState(split.state_root, split.slot))?
} else {
self.load_restore_point_by_index(high_restore_point_idx)?
@@ -365,7 +376,7 @@ impl HotColdDB {
/// Get a suitable block root for backtracking from `high_restore_point` to the state at `slot`.
///
/// Defaults to the block root for `slot`, which *should* be in range.
fn get_high_restore_point_block_root<E: EthSpec>(
fn get_high_restore_point_block_root(
&self,
high_restore_point: &BeaconState<E>,
slot: Slot,
@@ -381,7 +392,7 @@ impl HotColdDB {
///
/// Blocks are returned in slot-ascending order, suitable for replaying on a state with slot
/// equal to `start_slot`, to reach a state with slot equal to `end_slot`.
fn load_blocks_to_replay<E: EthSpec>(
fn load_blocks_to_replay(
&self,
start_slot: Slot,
end_slot: Slot,
@@ -402,7 +413,7 @@ impl HotColdDB {
/// Replay `blocks` on top of `state` until `target_slot` is reached.
///
/// Will skip slots as necessary.
fn replay_blocks<E: EthSpec>(
fn replay_blocks(
&self,
mut state: BeaconState<E>,
blocks: Vec<BeaconBlock<E>>,
@@ -440,6 +451,11 @@ impl HotColdDB {
self.split.read().slot
}
/// Fetch the slot of the most recently stored restore point.
pub fn get_latest_restore_point_slot(&self) -> Slot {
self.get_split_slot() / self.slots_per_restore_point * self.slots_per_restore_point
}
/// Load the split point from disk.
fn load_split(&self) -> Result<Option<Split>, Error> {
let key = Hash256::from_slice(SPLIT_DB_KEY.as_bytes());
@@ -498,9 +514,7 @@ impl HotColdDB {
/// This ensures that we have at least one restore point within range of our state
/// root history when iterating backwards (and allows for more frequent restore points if
/// desired).
fn verify_slots_per_restore_point<E: EthSpec>(
slots_per_restore_point: u64,
) -> Result<(), HotColdDbError> {
fn verify_slots_per_restore_point(slots_per_restore_point: u64) -> Result<(), HotColdDbError> {
let slots_per_historical_root = E::SlotsPerHistoricalRoot::to_u64();
if slots_per_restore_point > 0 && slots_per_historical_root % slots_per_restore_point == 0 {
Ok(())

View File

@@ -4,7 +4,7 @@ use ssz_derive::{Decode, Encode};
use std::convert::TryInto;
use types::beacon_state::{BeaconTreeHashCache, CommitteeCache, CACHED_EPOCHS};
pub fn store_full_state<S: Store, E: EthSpec>(
pub fn store_full_state<S: Store<E>, E: EthSpec>(
store: &S,
state_root: &Hash256,
state: &BeaconState<E>,
@@ -21,7 +21,7 @@ pub fn store_full_state<S: Store, E: EthSpec>(
result
}
pub fn get_full_state<S: Store, E: EthSpec>(
pub fn get_full_state<S: Store<E>, E: EthSpec>(
store: &S,
state_root: &Hash256,
) -> Result<Option<BeaconState<E>>, Error> {

View File

@@ -12,12 +12,14 @@ use types::{
///
/// It is assumed that all ancestors for this object are stored in the database. If this is not the
/// case, the iterator will start returning `None` prior to genesis.
pub trait AncestorIter<U: Store, I: Iterator> {
pub trait AncestorIter<U: Store<E>, E: EthSpec, I: Iterator> {
/// Returns an iterator over the roots of the ancestors of `self`.
fn try_iter_ancestor_roots(&self, store: Arc<U>) -> Option<I>;
}
impl<'a, U: Store, E: EthSpec> AncestorIter<U, BlockRootsIterator<'a, E, U>> for BeaconBlock<E> {
impl<'a, U: Store<E>, E: EthSpec> AncestorIter<U, E, BlockRootsIterator<'a, E, U>>
for BeaconBlock<E>
{
/// Iterates across all available prior block roots of `self`, starting at the most recent and ending
/// at genesis.
fn try_iter_ancestor_roots(&self, store: Arc<U>) -> Option<BlockRootsIterator<'a, E, U>> {
@@ -27,7 +29,9 @@ impl<'a, U: Store, E: EthSpec> AncestorIter<U, BlockRootsIterator<'a, E, U>> for
}
}
impl<'a, U: Store, E: EthSpec> AncestorIter<U, StateRootsIterator<'a, E, U>> for BeaconState<E> {
impl<'a, U: Store<E>, E: EthSpec> AncestorIter<U, E, StateRootsIterator<'a, E, U>>
for BeaconState<E>
{
/// Iterates across all available prior state roots of `self`, starting at the most recent and ending
/// at genesis.
fn try_iter_ancestor_roots(&self, store: Arc<U>) -> Option<StateRootsIterator<'a, E, U>> {
@@ -52,7 +56,7 @@ impl<'a, T: EthSpec, U> Clone for StateRootsIterator<'a, T, U> {
}
}
impl<'a, T: EthSpec, U: Store> StateRootsIterator<'a, T, U> {
impl<'a, T: EthSpec, U: Store<T>> StateRootsIterator<'a, T, U> {
pub fn new(store: Arc<U>, beacon_state: &'a BeaconState<T>) -> Self {
Self {
store,
@@ -70,7 +74,7 @@ impl<'a, T: EthSpec, U: Store> StateRootsIterator<'a, T, U> {
}
}
impl<'a, T: EthSpec, U: Store> Iterator for StateRootsIterator<'a, T, U> {
impl<'a, T: EthSpec, U: Store<T>> Iterator for StateRootsIterator<'a, T, U> {
type Item = (Hash256, Slot);
fn next(&mut self) -> Option<Self::Item> {
@@ -99,13 +103,13 @@ impl<'a, T: EthSpec, U: Store> Iterator for StateRootsIterator<'a, T, U> {
}
/// Block iterator that uses the `parent_root` of each block to backtrack.
pub struct ParentRootBlockIterator<'a, E: EthSpec, S: Store> {
pub struct ParentRootBlockIterator<'a, E: EthSpec, S: Store<E>> {
store: &'a S,
next_block_root: Hash256,
_phantom: PhantomData<E>,
}
impl<'a, E: EthSpec, S: Store> ParentRootBlockIterator<'a, E, S> {
impl<'a, E: EthSpec, S: Store<E>> ParentRootBlockIterator<'a, E, S> {
pub fn new(store: &'a S, start_block_root: Hash256) -> Self {
Self {
store,
@@ -115,7 +119,7 @@ impl<'a, E: EthSpec, S: Store> ParentRootBlockIterator<'a, E, S> {
}
}
impl<'a, E: EthSpec, S: Store> Iterator for ParentRootBlockIterator<'a, E, S> {
impl<'a, E: EthSpec, S: Store<E>> Iterator for ParentRootBlockIterator<'a, E, S> {
type Item = BeaconBlock<E>;
fn next(&mut self) -> Option<Self::Item> {
@@ -137,7 +141,7 @@ pub struct BlockIterator<'a, T: EthSpec, U> {
roots: BlockRootsIterator<'a, T, U>,
}
impl<'a, T: EthSpec, U: Store> BlockIterator<'a, T, U> {
impl<'a, T: EthSpec, U: Store<T>> BlockIterator<'a, T, U> {
/// Create a new iterator over all blocks in the given `beacon_state` and prior states.
pub fn new(store: Arc<U>, beacon_state: &'a BeaconState<T>) -> Self {
Self {
@@ -153,7 +157,7 @@ impl<'a, T: EthSpec, U: Store> BlockIterator<'a, T, U> {
}
}
impl<'a, T: EthSpec, U: Store> Iterator for BlockIterator<'a, T, U> {
impl<'a, T: EthSpec, U: Store<T>> Iterator for BlockIterator<'a, T, U> {
type Item = BeaconBlock<T>;
fn next(&mut self) -> Option<Self::Item> {
@@ -186,7 +190,7 @@ impl<'a, T: EthSpec, U> Clone for BlockRootsIterator<'a, T, U> {
}
}
impl<'a, T: EthSpec, U: Store> BlockRootsIterator<'a, T, U> {
impl<'a, T: EthSpec, U: Store<T>> BlockRootsIterator<'a, T, U> {
/// Create a new iterator over all block roots in the given `beacon_state` and prior states.
pub fn new(store: Arc<U>, beacon_state: &'a BeaconState<T>) -> Self {
Self {
@@ -206,7 +210,7 @@ impl<'a, T: EthSpec, U: Store> BlockRootsIterator<'a, T, U> {
}
}
impl<'a, T: EthSpec, U: Store> Iterator for BlockRootsIterator<'a, T, U> {
impl<'a, T: EthSpec, U: Store<T>> Iterator for BlockRootsIterator<'a, T, U> {
type Item = (Hash256, Slot);
fn next(&mut self) -> Option<Self::Item> {
@@ -235,7 +239,7 @@ impl<'a, T: EthSpec, U: Store> Iterator for BlockRootsIterator<'a, T, U> {
}
/// Fetch the next state to use whilst backtracking in `*RootsIterator`.
fn next_historical_root_backtrack_state<E: EthSpec, S: Store>(
fn next_historical_root_backtrack_state<E: EthSpec, S: Store<E>>(
store: &S,
current_state: &BeaconState<E>,
) -> Option<BeaconState<E>> {

View File

@@ -1,4 +1,5 @@
use super::*;
use crate::forwards_iter::SimpleForwardsBlockRootsIterator;
use crate::impls::beacon_state::{get_full_state, store_full_state};
use crate::metrics;
use db_key::Key;
@@ -6,14 +7,16 @@ use leveldb::database::kv::KV;
use leveldb::database::Database;
use leveldb::error::Error as LevelDBError;
use leveldb::options::{Options, ReadOptions, WriteOptions};
use std::marker::PhantomData;
use std::path::Path;
/// A wrapped leveldb database.
pub struct LevelDB {
pub struct LevelDB<E: EthSpec> {
db: Database<BytesKey>,
_phantom: PhantomData<E>,
}
impl LevelDB {
impl<E: EthSpec> LevelDB<E> {
/// Open a database at `path`, creating a new database if one does not already exist.
pub fn open(path: &Path) -> Result<Self, Error> {
let mut options = Options::new();
@@ -22,7 +25,10 @@ impl LevelDB {
let db = Database::open(path, options)?;
Ok(Self { db })
Ok(Self {
db,
_phantom: PhantomData,
})
}
fn read_options(&self) -> ReadOptions<BytesKey> {
@@ -55,7 +61,9 @@ impl Key for BytesKey {
}
}
impl Store for LevelDB {
impl<E: EthSpec> Store<E> for LevelDB<E> {
type ForwardsBlockRootsIterator = SimpleForwardsBlockRootsIterator;
/// Retrieve some bytes in `column` with `key`.
fn get_bytes(&self, col: &str, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let column_key = Self::get_key_for_col(col, key);
@@ -110,22 +118,28 @@ impl Store for LevelDB {
}
/// Store a state in the store.
fn put_state<E: EthSpec>(
&self,
state_root: &Hash256,
state: &BeaconState<E>,
) -> Result<(), Error> {
fn put_state(&self, state_root: &Hash256, state: &BeaconState<E>) -> Result<(), Error> {
store_full_state(self, state_root, state)
}
/// Fetch a state from the store.
fn get_state<E: EthSpec>(
fn get_state(
&self,
state_root: &Hash256,
_: Option<Slot>,
) -> Result<Option<BeaconState<E>>, Error> {
get_full_state(self, state_root)
}
fn forwards_block_roots_iterator(
store: Arc<Self>,
start_slot: Slot,
end_state: BeaconState<E>,
end_block_root: Hash256,
_: &ChainSpec,
) -> Self::ForwardsBlockRootsIterator {
SimpleForwardsBlockRootsIterator::new(store, start_slot, end_state, end_block_root)
}
}
impl From<LevelDBError> for Error {

View File

@@ -11,9 +11,11 @@
extern crate lazy_static;
mod block_at_slot;
pub mod chunked_iter;
pub mod chunked_vector;
pub mod config;
mod errors;
mod forwards_iter;
mod hot_cold_store;
mod impls;
mod leveldb_store;
@@ -42,7 +44,9 @@ pub use types::*;
/// A `Store` is fundamentally backed by a key-value database, however it provides support for
/// columns. A simple column implementation might involve prefixing a key with some bytes unique to
/// each column.
pub trait Store: Sync + Send + Sized + 'static {
pub trait Store<E: EthSpec>: Sync + Send + Sized + 'static {
type ForwardsBlockRootsIterator: Iterator<Item = (Hash256, Slot)>;
/// Retrieve some bytes in `column` with `key`.
fn get_bytes(&self, column: &str, key: &[u8]) -> Result<Option<Vec<u8>>, Error>;
@@ -76,14 +80,10 @@ pub trait Store: Sync + Send + Sized + 'static {
}
/// Store a state in the store.
fn put_state<E: EthSpec>(
&self,
state_root: &Hash256,
state: &BeaconState<E>,
) -> Result<(), Error>;
fn put_state(&self, state_root: &Hash256, state: &BeaconState<E>) -> Result<(), Error>;
/// Fetch a state from the store.
fn get_state<E: EthSpec>(
fn get_state(
&self,
state_root: &Hash256,
slot: Option<Slot>,
@@ -94,7 +94,7 @@ pub trait Store: Sync + Send + Sized + 'static {
///
/// Returns `None` if no parent block exists at that slot, or if `slot` is greater than the
/// slot of `start_block_root`.
fn get_block_at_preceeding_slot<E: EthSpec>(
fn get_block_at_preceeding_slot(
&self,
start_block_root: Hash256,
slot: Slot,
@@ -103,13 +103,31 @@ pub trait Store: Sync + Send + Sized + 'static {
}
/// (Optionally) Move all data before the frozen slot to the freezer database.
fn freeze_to_state<E: EthSpec>(
fn freeze_to_state(
_store: Arc<Self>,
_frozen_head_root: Hash256,
_frozen_head: &BeaconState<E>,
) -> Result<(), Error> {
Ok(())
}
/// Get a forwards (slot-ascending) iterator over the beacon block roots since `start_slot`.
///
/// Will be efficient for frozen portions of the database if using `DiskStore`.
///
/// The `end_state` and `end_block_root` are required for backtracking in the post-finalization
/// part of the chain, and should be usually be set to the current head. Importantly, the
/// `end_state` must be a state that has had a block applied to it, and the hash of that
/// block must be `end_block_root`.
// NOTE: could maybe optimise by getting the `BeaconState` and end block root from a closure, as
// it's not always required.
fn forwards_block_roots_iterator(
store: Arc<Self>,
start_slot: Slot,
end_state: BeaconState<E>,
end_block_root: Hash256,
spec: &ChainSpec,
) -> Self::ForwardsBlockRootsIterator;
}
/// A unique column identifier.
@@ -165,16 +183,16 @@ pub trait SimpleStoreItem: Sized {
/// An item that may be stored in a `Store`.
pub trait StoreItem: Sized {
/// Store `self`.
fn db_put<S: Store>(&self, store: &S, key: &Hash256) -> Result<(), Error>;
fn db_put<S: Store<E>, E: EthSpec>(&self, store: &S, key: &Hash256) -> Result<(), Error>;
/// Retrieve an instance of `Self` from `store`.
fn db_get<S: Store>(store: &S, key: &Hash256) -> Result<Option<Self>, Error>;
fn db_get<S: Store<E>, E: EthSpec>(store: &S, key: &Hash256) -> Result<Option<Self>, Error>;
/// Return `true` if an instance of `Self` exists in `store`.
fn db_exists<S: Store>(store: &S, key: &Hash256) -> Result<bool, Error>;
fn db_exists<S: Store<E>, E: EthSpec>(store: &S, key: &Hash256) -> Result<bool, Error>;
/// Delete an instance of `Self` from `store`.
fn db_delete<S: Store>(store: &S, key: &Hash256) -> Result<(), Error>;
fn db_delete<S: Store<E>, E: EthSpec>(store: &S, key: &Hash256) -> Result<(), Error>;
}
impl<T> StoreItem for T
@@ -182,7 +200,7 @@ where
T: SimpleStoreItem,
{
/// Store `self`.
fn db_put<S: Store>(&self, store: &S, key: &Hash256) -> Result<(), Error> {
fn db_put<S: Store<E>, E: EthSpec>(&self, store: &S, key: &Hash256) -> Result<(), Error> {
let column = Self::db_column().into();
let key = key.as_bytes();
@@ -192,7 +210,7 @@ where
}
/// Retrieve an instance of `Self`.
fn db_get<S: Store>(store: &S, key: &Hash256) -> Result<Option<Self>, Error> {
fn db_get<S: Store<E>, E: EthSpec>(store: &S, key: &Hash256) -> Result<Option<Self>, Error> {
let column = Self::db_column().into();
let key = key.as_bytes();
@@ -203,7 +221,7 @@ where
}
/// Return `true` if an instance of `Self` exists in `Store`.
fn db_exists<S: Store>(store: &S, key: &Hash256) -> Result<bool, Error> {
fn db_exists<S: Store<E>, E: EthSpec>(store: &S, key: &Hash256) -> Result<bool, Error> {
let column = Self::db_column().into();
let key = key.as_bytes();
@@ -211,7 +229,7 @@ where
}
/// Delete `self` from the `Store`.
fn db_delete<S: Store>(store: &S, key: &Hash256) -> Result<(), Error> {
fn db_delete<S: Store<E>, E: EthSpec>(store: &S, key: &Hash256) -> Result<(), Error> {
let column = Self::db_column().into();
let key = key.as_bytes();
@@ -246,7 +264,7 @@ mod tests {
}
}
fn test_impl(store: impl Store) {
fn test_impl(store: impl Store<MinimalEthSpec>) {
let key = Hash256::random();
let item = StorableThing { a: 1, b: 42 };
@@ -275,7 +293,7 @@ mod tests {
let slots_per_restore_point = MinimalEthSpec::slots_per_historical_root() as u64;
let spec = MinimalEthSpec::default_spec();
let log = NullLoggerBuilder.build().unwrap();
let store = DiskStore::open::<MinimalEthSpec>(
let store = DiskStore::open(
&hot_dir.path(),
&cold_dir.path(),
slots_per_restore_point,
@@ -305,7 +323,7 @@ mod tests {
#[test]
fn exists() {
let store = MemoryStore::open();
let store = MemoryStore::<MinimalEthSpec>::open();
let key = Hash256::random();
let item = StorableThing { a: 1, b: 42 };

View File

@@ -1,29 +1,35 @@
use super::{Error, Store};
use crate::forwards_iter::SimpleForwardsBlockRootsIterator;
use crate::impls::beacon_state::{get_full_state, store_full_state};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
use types::*;
type DBHashMap = HashMap<Vec<u8>, Vec<u8>>;
/// A thread-safe `HashMap` wrapper.
pub struct MemoryStore {
pub struct MemoryStore<E: EthSpec> {
db: RwLock<DBHashMap>,
_phantom: PhantomData<E>,
}
impl Clone for MemoryStore {
impl<E: EthSpec> Clone for MemoryStore<E> {
fn clone(&self) -> Self {
Self {
db: RwLock::new(self.db.read().clone()),
_phantom: PhantomData,
}
}
}
impl MemoryStore {
impl<E: EthSpec> MemoryStore<E> {
/// Create a new, empty database.
pub fn open() -> Self {
Self {
db: RwLock::new(HashMap::new()),
_phantom: PhantomData,
}
}
@@ -34,10 +40,12 @@ impl MemoryStore {
}
}
impl Store for MemoryStore {
impl<E: EthSpec> Store<E> for MemoryStore<E> {
type ForwardsBlockRootsIterator = SimpleForwardsBlockRootsIterator;
/// Get the value of some key from the database. Returns `None` if the key does not exist.
fn get_bytes(&self, col: &str, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let column_key = MemoryStore::get_key_for_col(col, key);
let column_key = Self::get_key_for_col(col, key);
Ok(self
.db
@@ -48,7 +56,7 @@ impl Store for MemoryStore {
/// Puts a key in the database.
fn put_bytes(&self, col: &str, key: &[u8], val: &[u8]) -> Result<(), Error> {
let column_key = MemoryStore::get_key_for_col(col, key);
let column_key = Self::get_key_for_col(col, key);
self.db.write().insert(column_key, val.to_vec());
@@ -57,14 +65,14 @@ impl Store for MemoryStore {
/// Return true if some key exists in some column.
fn key_exists(&self, col: &str, key: &[u8]) -> Result<bool, Error> {
let column_key = MemoryStore::get_key_for_col(col, key);
let column_key = Self::get_key_for_col(col, key);
Ok(self.db.read().contains_key(&column_key))
}
/// Delete some key from the database.
fn key_delete(&self, col: &str, key: &[u8]) -> Result<(), Error> {
let column_key = MemoryStore::get_key_for_col(col, key);
let column_key = Self::get_key_for_col(col, key);
self.db.write().remove(&column_key);
@@ -72,20 +80,26 @@ impl Store for MemoryStore {
}
/// Store a state in the store.
fn put_state<E: EthSpec>(
&self,
state_root: &Hash256,
state: &BeaconState<E>,
) -> Result<(), Error> {
fn put_state(&self, state_root: &Hash256, state: &BeaconState<E>) -> Result<(), Error> {
store_full_state(self, state_root, state)
}
/// Fetch a state from the store.
fn get_state<E: EthSpec>(
fn get_state(
&self,
state_root: &Hash256,
_: Option<Slot>,
) -> Result<Option<BeaconState<E>>, Error> {
get_full_state(self, state_root)
}
fn forwards_block_roots_iterator(
store: Arc<Self>,
start_slot: Slot,
end_state: BeaconState<E>,
end_block_root: Hash256,
_: &ChainSpec,
) -> Self::ForwardsBlockRootsIterator {
SimpleForwardsBlockRootsIterator::new(store, start_slot, end_state, end_block_root)
}
}

View File

@@ -23,14 +23,14 @@ pub trait Migrate<S, E: EthSpec>: Send + Sync + 'static {
/// Migrator that does nothing, for stores that don't need migration.
pub struct NullMigrator;
impl<E: EthSpec> Migrate<SimpleDiskStore, E> for NullMigrator {
fn new(_: Arc<SimpleDiskStore>) -> Self {
impl<E: EthSpec> Migrate<SimpleDiskStore<E>, E> for NullMigrator {
fn new(_: Arc<SimpleDiskStore<E>>) -> Self {
NullMigrator
}
}
impl<E: EthSpec> Migrate<MemoryStore, E> for NullMigrator {
fn new(_: Arc<MemoryStore>) -> Self {
impl<E: EthSpec> Migrate<MemoryStore<E>, E> for NullMigrator {
fn new(_: Arc<MemoryStore<E>>) -> Self {
NullMigrator
}
}
@@ -40,7 +40,7 @@ impl<E: EthSpec> Migrate<MemoryStore, E> for NullMigrator {
/// Mostly useful for tests.
pub struct BlockingMigrator<S>(Arc<S>);
impl<E: EthSpec, S: Store> Migrate<S, E> for BlockingMigrator<S> {
impl<E: EthSpec, S: Store<E>> Migrate<S, E> for BlockingMigrator<S> {
fn new(db: Arc<S>) -> Self {
BlockingMigrator(db)
}
@@ -60,15 +60,15 @@ impl<E: EthSpec, S: Store> Migrate<S, E> for BlockingMigrator<S> {
/// Migrator that runs a background thread to migrate state from the hot to the cold database.
pub struct BackgroundMigrator<E: EthSpec> {
db: Arc<DiskStore>,
db: Arc<DiskStore<E>>,
tx_thread: Mutex<(
mpsc::Sender<(Hash256, BeaconState<E>)>,
thread::JoinHandle<()>,
)>,
}
impl<E: EthSpec> Migrate<DiskStore, E> for BackgroundMigrator<E> {
fn new(db: Arc<DiskStore>) -> Self {
impl<E: EthSpec> Migrate<DiskStore<E>, E> for BackgroundMigrator<E> {
fn new(db: Arc<DiskStore<E>>) -> Self {
let tx_thread = Mutex::new(Self::spawn_thread(db.clone()));
Self { db, tx_thread }
}
@@ -119,7 +119,7 @@ impl<E: EthSpec> BackgroundMigrator<E> {
///
/// Return a channel handle for sending new finalized states to the thread.
fn spawn_thread(
db: Arc<DiskStore>,
db: Arc<DiskStore<E>>,
) -> (
mpsc::Sender<(Hash256, BeaconState<E>)>,
thread::JoinHandle<()>,

View File

@@ -111,7 +111,11 @@ impl<T: EthSpec> PartialBeaconState<T> {
}
}
pub fn load_block_roots<S: Store>(&mut self, store: &S, spec: &ChainSpec) -> Result<(), Error> {
pub fn load_block_roots<S: Store<T>>(
&mut self,
store: &S,
spec: &ChainSpec,
) -> Result<(), Error> {
if self.block_roots.is_none() {
self.block_roots = Some(load_vector_from_db::<BlockRoots, T, _>(
store, self.slot, spec,
@@ -120,7 +124,11 @@ impl<T: EthSpec> PartialBeaconState<T> {
Ok(())
}
pub fn load_state_roots<S: Store>(&mut self, store: &S, spec: &ChainSpec) -> Result<(), Error> {
pub fn load_state_roots<S: Store<T>>(
&mut self,
store: &S,
spec: &ChainSpec,
) -> Result<(), Error> {
if self.state_roots.is_none() {
self.state_roots = Some(load_vector_from_db::<StateRoots, T, _>(
store, self.slot, spec,
@@ -129,7 +137,7 @@ impl<T: EthSpec> PartialBeaconState<T> {
Ok(())
}
pub fn load_historical_roots<S: Store>(
pub fn load_historical_roots<S: Store<T>>(
&mut self,
store: &S,
spec: &ChainSpec,
@@ -142,7 +150,7 @@ impl<T: EthSpec> PartialBeaconState<T> {
Ok(())
}
pub fn load_randao_mixes<S: Store>(
pub fn load_randao_mixes<S: Store<T>>(
&mut self,
store: &S,
spec: &ChainSpec,