mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Implement freezer database (#508)
* Implement freezer database for state vectors * Improve BeaconState safe accessors And fix a bug in the compact committees accessor. * Banish dodgy type bounds back to gRPC * Clean up * Switch to exclusive end points in chunked vec * Cleaning up and start of tests * Randao fix, more tests * Fix unsightly hack * Resolve test FIXMEs * Config file support * More clean-ups, migrator beginnings * Finish migrator, integrate into BeaconChain * Fixups * Fix store tests * Fix BeaconChain tests * Fix LMD GHOST tests * Address review comments, delete 'static bounds * Cargo format * Address review comments * Fix LMD ghost tests * Update to spec v0.9.0 * Update to v0.9.1 * Bump spec tags for v0.9.1 * Formatting, fix CI failures * Resolve accidental KeyPair merge conflict * Document new BeaconState functions * Fix incorrect cache drops in `advance_caches` * Update fork choice for v0.9.1 * Clean up some FIXMEs * Fix a few docs/logs * Update for new builder paradigm, spec changes * Freezer DB integration into BeaconNode * Cleaning up * This works, clean it up * Cleanups * Fix and improve store tests * Refine store test * Delete unused beacon_chain_builder.rs * Fix CLI * Store state at split slot in hot database * Make fork choice lookup fast again * Store freezer DB split slot in the database * Handle potential div by 0 in chunked_vector * Exclude committee caches from freezer DB * Remove FIXME about long-running test
This commit is contained in:
committed by
Paul Hauner
parent
a514968155
commit
bf2eeae3f2
290
beacon_node/beacon_chain/tests/store_tests.rs
Normal file
290
beacon_node/beacon_chain/tests/store_tests.rs
Normal file
@@ -0,0 +1,290 @@
|
||||
#![cfg(not(debug_assertions))]
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use beacon_chain::test_utils::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType,
|
||||
};
|
||||
use rand::Rng;
|
||||
use sloggers::{null::NullLoggerBuilder, Build};
|
||||
use std::sync::Arc;
|
||||
use store::DiskStore;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
use tree_hash::TreeHash;
|
||||
use types::test_utils::{SeedableRng, XorShiftRng};
|
||||
use types::*;
|
||||
|
||||
// Should ideally be divisible by 3.
|
||||
pub const VALIDATOR_COUNT: usize = 24;
|
||||
|
||||
lazy_static! {
|
||||
/// A cached set of keys.
|
||||
static ref KEYPAIRS: Vec<Keypair> = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
type E = MinimalEthSpec;
|
||||
type TestHarness = BeaconChainHarness<DiskHarnessType<E>>;
|
||||
|
||||
fn get_store(db_path: &TempDir) -> Arc<DiskStore> {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
let hot_path = db_path.path().join("hot_db");
|
||||
let cold_path = db_path.path().join("cold_db");
|
||||
let log = NullLoggerBuilder.build().expect("logger should build");
|
||||
Arc::new(
|
||||
DiskStore::open(&hot_path, &cold_path, spec, log).expect("disk store should initialize"),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_harness(store: Arc<DiskStore>, validator_count: usize) -> TestHarness {
|
||||
let harness = BeaconChainHarness::with_disk_store(
|
||||
MinimalEthSpec,
|
||||
store,
|
||||
KEYPAIRS[0..validator_count].to_vec(),
|
||||
);
|
||||
harness.advance_slot();
|
||||
harness
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_participation_no_skips() {
|
||||
let num_blocks_produced = E::slots_per_epoch() * 5;
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
|
||||
|
||||
harness.extend_chain(
|
||||
num_blocks_produced as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
check_finalization(&harness, num_blocks_produced);
|
||||
check_split_slot(&harness, store);
|
||||
check_chain_dump(&harness, num_blocks_produced + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn randomised_skips() {
|
||||
let num_slots = E::slots_per_epoch() * 5;
|
||||
let mut num_blocks_produced = 0;
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let mut head_slot = 0;
|
||||
|
||||
for slot in 1..=num_slots {
|
||||
if rng.gen_bool(0.8) {
|
||||
harness.extend_chain(
|
||||
1,
|
||||
BlockStrategy::ForkCanonicalChainAt {
|
||||
previous_slot: Slot::new(head_slot),
|
||||
first_slot: Slot::new(slot),
|
||||
},
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
harness.advance_slot();
|
||||
num_blocks_produced += 1;
|
||||
head_slot = slot;
|
||||
} else {
|
||||
harness.advance_slot();
|
||||
}
|
||||
}
|
||||
|
||||
let state = &harness.chain.head().beacon_state;
|
||||
|
||||
assert_eq!(state.slot, num_slots, "head should be at the current slot");
|
||||
|
||||
check_split_slot(&harness, store);
|
||||
check_chain_dump(&harness, num_blocks_produced + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_skip() {
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
|
||||
|
||||
// Number of blocks to create in the first run, intentionally not falling on an epoch
|
||||
// boundary in order to check that the DB hot -> cold migration is capable of reaching
|
||||
// back across the skip distance, and correctly migrating those extra non-finalized states.
|
||||
let initial_blocks = E::slots_per_epoch() * 5 + E::slots_per_epoch() / 2;
|
||||
let skip_slots = E::slots_per_historical_root() as u64 * 8;
|
||||
let final_blocks = E::slots_per_epoch() * 4;
|
||||
|
||||
harness.extend_chain(
|
||||
initial_blocks as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
check_finalization(&harness, initial_blocks);
|
||||
|
||||
// 2. Skip slots
|
||||
for _ in 0..skip_slots {
|
||||
harness.advance_slot();
|
||||
}
|
||||
|
||||
// 3. Produce more blocks, establish a new finalized epoch
|
||||
harness.extend_chain(
|
||||
final_blocks as usize,
|
||||
BlockStrategy::ForkCanonicalChainAt {
|
||||
previous_slot: Slot::new(initial_blocks),
|
||||
first_slot: Slot::new(initial_blocks + skip_slots as u64 + 1),
|
||||
},
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
check_finalization(&harness, initial_blocks + skip_slots + final_blocks);
|
||||
check_split_slot(&harness, store);
|
||||
check_chain_dump(&harness, initial_blocks + final_blocks + 1);
|
||||
}
|
||||
|
||||
/// Go forward to the point where the genesis randao value is no longer part of the vector.
|
||||
///
|
||||
/// This implicitly checks that:
|
||||
/// 1. The chunked vector scheme doesn't attempt to store an incorrect genesis value
|
||||
/// 2. We correctly load the genesis value for all required slots
|
||||
/// NOTE: this test takes about a minute to run
|
||||
#[test]
|
||||
fn randao_genesis_storage() {
|
||||
let validator_count = 8;
|
||||
let db_path = tempdir().unwrap();
|
||||
let store = get_store(&db_path);
|
||||
let harness = get_harness(store.clone(), validator_count);
|
||||
|
||||
let num_slots = E::slots_per_epoch() * (E::epochs_per_historical_vector() - 1) as u64;
|
||||
|
||||
// Check we have a non-trivial genesis value
|
||||
let genesis_value = *harness
|
||||
.chain
|
||||
.head()
|
||||
.beacon_state
|
||||
.get_randao_mix(Epoch::new(0))
|
||||
.expect("randao mix ok");
|
||||
assert!(!genesis_value.is_zero());
|
||||
|
||||
harness.extend_chain(
|
||||
num_slots as usize - 1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
// Check that genesis value is still present
|
||||
assert!(harness
|
||||
.chain
|
||||
.head()
|
||||
.beacon_state
|
||||
.randao_mixes
|
||||
.iter()
|
||||
.find(|x| **x == genesis_value)
|
||||
.is_some());
|
||||
|
||||
// Then upon adding one more block, it isn't
|
||||
harness.advance_slot();
|
||||
harness.extend_chain(
|
||||
1,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
assert!(harness
|
||||
.chain
|
||||
.head()
|
||||
.beacon_state
|
||||
.randao_mixes
|
||||
.iter()
|
||||
.find(|x| **x == genesis_value)
|
||||
.is_none());
|
||||
|
||||
check_finalization(&harness, num_slots);
|
||||
check_split_slot(&harness, store);
|
||||
check_chain_dump(&harness, num_slots + 1);
|
||||
}
|
||||
|
||||
// Check that closing and reopening a freezer DB restores the split slot to its correct value.
|
||||
#[test]
|
||||
fn split_slot_restore() {
|
||||
let db_path = tempdir().unwrap();
|
||||
|
||||
let split_slot = {
|
||||
let store = get_store(&db_path);
|
||||
let harness = get_harness(store.clone(), VALIDATOR_COUNT);
|
||||
|
||||
let num_blocks = 4 * E::slots_per_epoch();
|
||||
|
||||
harness.extend_chain(
|
||||
num_blocks as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
store.get_split_slot()
|
||||
};
|
||||
assert_ne!(split_slot, Slot::new(0));
|
||||
|
||||
// Re-open the store
|
||||
let store = get_store(&db_path);
|
||||
|
||||
assert_eq!(store.get_split_slot(), split_slot);
|
||||
}
|
||||
|
||||
/// Check that the head state's slot matches `expected_slot`.
|
||||
fn check_slot(harness: &TestHarness, expected_slot: u64) {
|
||||
let state = &harness.chain.head().beacon_state;
|
||||
|
||||
assert_eq!(
|
||||
state.slot, expected_slot,
|
||||
"head should be at the current slot"
|
||||
);
|
||||
}
|
||||
|
||||
/// Check that the chain has finalized under best-case assumptions, and check the head slot.
|
||||
fn check_finalization(harness: &TestHarness, expected_slot: u64) {
|
||||
let state = &harness.chain.head().beacon_state;
|
||||
|
||||
check_slot(harness, expected_slot);
|
||||
|
||||
assert_eq!(
|
||||
state.current_justified_checkpoint.epoch,
|
||||
state.current_epoch() - 1,
|
||||
"the head should be justified one behind the current epoch"
|
||||
);
|
||||
assert_eq!(
|
||||
state.finalized_checkpoint.epoch,
|
||||
state.current_epoch() - 2,
|
||||
"the head should be finalized two behind the current epoch"
|
||||
);
|
||||
}
|
||||
|
||||
/// Check that the DiskStore's split_slot is equal to the start slot of the last finalized epoch.
|
||||
fn check_split_slot(harness: &TestHarness, store: Arc<DiskStore>) {
|
||||
let split_slot = store.get_split_slot();
|
||||
assert_eq!(
|
||||
harness
|
||||
.chain
|
||||
.head()
|
||||
.beacon_state
|
||||
.finalized_checkpoint
|
||||
.epoch
|
||||
.start_slot(E::slots_per_epoch()),
|
||||
split_slot
|
||||
);
|
||||
assert_ne!(split_slot, 0);
|
||||
}
|
||||
|
||||
/// Check that all the states in a chain dump have the correct tree hash.
|
||||
fn check_chain_dump(harness: &TestHarness, expected_len: u64) {
|
||||
let chain_dump = harness.chain.chain_dump().unwrap();
|
||||
|
||||
assert_eq!(chain_dump.len() as u64, expected_len);
|
||||
|
||||
for checkpoint in chain_dump {
|
||||
assert_eq!(
|
||||
checkpoint.beacon_state_root,
|
||||
Hash256::from_slice(&checkpoint.beacon_state.tree_hash_root()),
|
||||
"tree hash of stored state is incorrect"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user