diff --git a/Cargo.lock b/Cargo.lock index a5009e06c..fcc1d24b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,11 +346,13 @@ dependencies = [ "lighthouse_metrics", "log 0.4.11", "lru", + "maplit", "merkle_proof", "operation_pool", "parking_lot 0.11.0", "proto_array", "rand 0.7.3", + "rand_core 0.5.1", "rayon", "safe_arith", "serde", diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index cc6bcb115..04e22f426 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -11,6 +11,7 @@ participation_metrics = [] # Exposes validator participation metrics to Prometh [dev-dependencies] int_to_bytes = { path = "../../consensus/int_to_bytes" } +maplit = "1.0.2" [dependencies] eth2_config = { path = "../../common/eth2_config" } @@ -45,6 +46,7 @@ futures = "0.3.5" genesis = { path = "../genesis" } integer-sqrt = "0.1.3" rand = "0.7.3" +rand_core = "0.5.1" proto_array = { path = "../../consensus/proto_array" } lru = "0.5.1" tempfile = "3.1.0" diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 0f7ed21b4..b6320ad52 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1978,7 +1978,7 @@ impl BeaconChain { self.head_tracker.clone(), old_finalized_checkpoint, new_finalized_checkpoint, - ); + )?; let _ = self.event_handler.register(EventKind::BeaconFinalization { epoch: new_finalized_checkpoint.epoch, @@ -2070,10 +2070,11 @@ impl BeaconChain { .beacon_block_root; let mut visited: HashSet = HashSet::new(); let mut finalized_blocks: HashSet = HashSet::new(); + let mut justified_blocks: HashSet = HashSet::new(); let genesis_block_hash = Hash256::zero(); writeln!(output, "digraph beacon {{").unwrap(); - writeln!(output, "\t_{:?}[label=\"genesis\"];", genesis_block_hash).unwrap(); + writeln!(output, "\t_{:?}[label=\"zero\"];", genesis_block_hash).unwrap(); // Canonical head needs to be processed first as otherwise finalized blocks aren't detected // properly. @@ -2104,6 +2105,8 @@ impl BeaconChain { .unwrap() .unwrap(); finalized_blocks.insert(state.finalized_checkpoint.root); + justified_blocks.insert(state.current_justified_checkpoint.root); + justified_blocks.insert(state.previous_justified_checkpoint.root); } if block_hash == canonical_head_hash { @@ -2124,6 +2127,15 @@ impl BeaconChain { signed_beacon_block.slot() ) .unwrap(); + } else if justified_blocks.contains(&block_hash) { + writeln!( + output, + "\t_{:?}[label=\"{} ({})\" shape=cds];", + block_hash, + block_hash, + signed_beacon_block.slot() + ) + .unwrap(); } else { writeln!( output, @@ -2153,6 +2165,11 @@ impl BeaconChain { let mut file = std::fs::File::create(file_name).unwrap(); self.dump_as_dot(&mut file); } + + // Should be used in tests only + pub fn set_graffiti(&mut self, graffiti: Graffiti) { + self.graffiti = graffiti; + } } impl Drop for BeaconChain { diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 941627c1f..793179da8 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -2,6 +2,10 @@ #[macro_use] extern crate lazy_static; +#[macro_use] +extern crate slog; +extern crate slog_term; + pub mod attestation_verification; mod beacon_chain; mod beacon_fork_choice_store; diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 47c5ef97e..9730d49f0 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -1,13 +1,13 @@ use crate::errors::BeaconChainError; use crate::head_tracker::HeadTracker; use parking_lot::Mutex; -use slog::{debug, error, warn, Logger}; +use slog::{debug, warn, Logger}; use std::collections::{HashMap, HashSet}; use std::mem; use std::sync::mpsc; use std::sync::Arc; use std::thread; -use store::hot_cold_store::{process_finalization, HotColdDBError}; +use store::hot_cold_store::{migrate_database, HotColdDBError}; use store::iter::RootsIterator; use store::{Error, ItemStore, StoreOp}; pub use store::{HotColdDB, MemoryStore}; @@ -43,7 +43,8 @@ pub trait Migrate, Cold: ItemStore>: _head_tracker: Arc, _old_finalized_checkpoint: Checkpoint, _new_finalized_checkpoint: Checkpoint, - ) { + ) -> Result<(), BeaconChainError> { + Ok(()) } /// Traverses live heads and prunes blocks and states of chains that we know can't be built @@ -237,6 +238,7 @@ pub trait Migrate, Cold: ItemStore>: .map(|(slot, state_hash)| StoreOp::DeleteState(state_hash, slot)), ) .collect(); + store.do_atomically(batch)?; for head_hash in abandoned_heads.into_iter() { head_tracker.remove_head(head_hash); @@ -252,6 +254,17 @@ pub trait Migrate, Cold: ItemStore>: pub struct NullMigrator; impl, Cold: ItemStore> Migrate for NullMigrator { + fn process_finalization( + &self, + _finalized_state_root: BeaconStateHash, + _new_finalized_state: BeaconState, + _head_tracker: Arc, + _old_finalized_checkpoint: Checkpoint, + _new_finalized_checkpoint: Checkpoint, + ) -> Result<(), BeaconChainError> { + Ok(()) + } + fn new(_: Arc>, _: Logger) -> Self { NullMigrator } @@ -279,8 +292,8 @@ impl, Cold: ItemStore> Migrate head_tracker: Arc, old_finalized_checkpoint: Checkpoint, new_finalized_checkpoint: Checkpoint, - ) { - if let Err(e) = Self::prune_abandoned_forks( + ) -> Result<(), BeaconChainError> { + Self::prune_abandoned_forks( self.db.clone(), head_tracker, finalized_state_root, @@ -288,16 +301,23 @@ impl, Cold: ItemStore> Migrate old_finalized_checkpoint, new_finalized_checkpoint, &self.log, - ) { - error!(&self.log, "Pruning error"; "error" => format!("{:?}", e)); - } + )?; - if let Err(e) = process_finalization( + match migrate_database( self.db.clone(), finalized_state_root.into(), &new_finalized_state, ) { - error!(&self.log, "Migration error"; "error" => format!("{:?}", e)); + Ok(()) => Ok(()), + Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => { + debug!( + self.log, + "Database migration postponed, unaligned finalized block"; + "slot" => slot.as_u64() + ); + Ok(()) + } + Err(e) => Err(e.into()), } } } @@ -332,7 +352,7 @@ impl, Cold: ItemStore> Migrate head_tracker: Arc, old_finalized_checkpoint: Checkpoint, new_finalized_checkpoint: Checkpoint, - ) { + ) -> Result<(), BeaconChainError> { let (ref mut tx, ref mut thread) = *self.tx_thread.lock(); if let Err(tx_err) = tx.send(( @@ -360,6 +380,8 @@ impl, Cold: ItemStore> Migrate // Retry at most once, we could recurse but that would risk overflowing the stack. let _ = tx.send(tx_err.0); } + + Ok(()) } } @@ -394,7 +416,7 @@ impl, Cold: ItemStore> BackgroundMigrator warn!(log, "Block pruning failed: {:?}", e), } - match process_finalization(db.clone(), state_root.into(), &state) { + match migrate_database(db.clone(), state_root.into(), &state) { Ok(()) => {} Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => { debug!( diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 7262d7d33..8690c2e8d 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -3,6 +3,7 @@ pub use crate::beacon_chain::{ }; use crate::migrate::{BlockingMigrator, Migrate, NullMigrator}; pub use crate::persisted_beacon_chain::PersistedBeaconChain; +use crate::slog::Drain; use crate::{ builder::{BeaconChainBuilder, Witness}, eth1_chain::CachingEth1Backend, @@ -10,20 +11,23 @@ use crate::{ BeaconChain, BeaconChainTypes, StateSkipConfig, }; use genesis::interop_genesis_state; +use rand::rngs::StdRng; +use rand::Rng; +use rand_core::SeedableRng; use rayon::prelude::*; use sloggers::{null::NullLoggerBuilder, Build}; use slot_clock::TestingSlotClock; use state_processing::per_slot_processing; use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use std::time::Duration; -use store::{config::StoreConfig, HotColdDB, ItemStore, LevelDB, MemoryStore}; +use store::{config::StoreConfig, BlockReplay, HotColdDB, ItemStore, LevelDB, MemoryStore}; use tempfile::{tempdir, TempDir}; use tree_hash::TreeHash; use types::{ - AggregateSignature, Attestation, BeaconState, BeaconStateHash, ChainSpec, Domain, EthSpec, - Hash256, Keypair, SecretKey, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, + AggregateSignature, Attestation, BeaconState, BeaconStateHash, ChainSpec, Domain, Epoch, + EthSpec, Hash256, Keypair, SelectionProof, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHash, SignedRoot, Slot, SubnetId, }; @@ -44,11 +48,25 @@ pub type BaseHarnessType = Witn TColdStore, >; -pub type HarnessType = BaseHarnessType, MemoryStore>; -pub type DiskHarnessType = +pub type NullMigratorEphemeralHarnessType = + BaseHarnessType, MemoryStore>; +pub type BlockingMigratorDiskHarnessType = BaseHarnessType, LevelDB>, E, LevelDB, LevelDB>; +pub type BlockingMigratorEphemeralHarnessType = BaseHarnessType< + BlockingMigrator, MemoryStore>, + E, + MemoryStore, + MemoryStore, +>; -/// Indicates how the `BeaconChainHarness` should produce blocks. +pub type AddBlocksResult = ( + HashMap, + HashMap, + SignedBeaconBlockHash, + BeaconState, +); + +/// Deprecated: Indicates how the `BeaconChainHarness` should produce blocks. #[derive(Clone, Copy, Debug)] pub enum BlockStrategy { /// Produce blocks upon the canonical head (normal case). @@ -64,7 +82,7 @@ pub enum BlockStrategy { }, } -/// Indicates how the `BeaconChainHarness` should produce attestations. +/// Deprecated: Indicates how the `BeaconChainHarness` should produce attestations. #[derive(Clone, Debug)] pub enum AttestationStrategy { /// All validators attest to whichever block the `BeaconChainHarness` has produced. @@ -73,32 +91,98 @@ pub enum AttestationStrategy { SomeValidators(Vec), } +fn make_rng() -> StdRng { + // Nondeterminism in tests is a highly undesirable thing. Seed the RNG to some arbitrary + // but fixed value for reproducibility. + StdRng::seed_from_u64(0x0DDB1A5E5BAD5EEDu64) +} + /// A testing harness which can instantiate a `BeaconChain` and populate it with blocks and /// attestations. /// /// Used for testing. pub struct BeaconChainHarness { + pub validators_keypairs: Vec, + pub chain: BeaconChain, - pub keypairs: Vec, pub spec: ChainSpec, pub data_dir: TempDir, + + pub rng: StdRng, } -impl BeaconChainHarness> { - /// Instantiate a new harness with `validator_count` initial validators. - pub fn new(eth_spec_instance: E, keypairs: Vec, config: StoreConfig) -> Self { +type HarnessAttestations = Vec<( + Vec<(Attestation, SubnetId)>, + Option>, +)>; + +impl BeaconChainHarness> { + pub fn new(eth_spec_instance: E, validators_keypairs: Vec) -> Self { + let data_dir = tempdir().unwrap(); + let mut spec = E::default_spec(); + // Setting the target aggregators to really high means that _all_ validators in the // committee are required to produce an aggregate. This is overkill, however with small // validator counts it's the only way to be certain there is _at least one_ aggregator per // committee. - Self::new_with_target_aggregators(eth_spec_instance, keypairs, 1 << 32, config) + spec.target_aggregators_per_committee = 1 << 32; + + let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter); + let drain = slog_term::FullFormat::new(decorator).build(); + let debug_level = slog::LevelFilter::new(drain, slog::Level::Debug); + let log = slog::Logger::root(std::sync::Mutex::new(debug_level).fuse(), o!()); + + let config = StoreConfig::default(); + let store = Arc::new(HotColdDB::open_ephemeral(config, spec.clone(), log.clone()).unwrap()); + + let chain = BeaconChainBuilder::new(eth_spec_instance) + .logger(log.clone()) + .custom_spec(spec.clone()) + .store(store.clone()) + .store_migrator(BlockingMigrator::new(store, log.clone())) + .data_dir(data_dir.path().to_path_buf()) + .genesis_state( + interop_genesis_state::(&validators_keypairs, HARNESS_GENESIS_TIME, &spec) + .unwrap(), + ) + .unwrap() + .dummy_eth1_backend() + .unwrap() + .null_event_handler() + .testing_slot_clock(HARNESS_SLOT_TIME) + .unwrap() + .build() + .unwrap(); + + Self { + spec: chain.spec.clone(), + chain, + validators_keypairs, + data_dir, + rng: make_rng(), + } + } +} + +impl BeaconChainHarness> { + /// Instantiate a new harness with `validator_count` initial validators. + pub fn new_with_store_config( + eth_spec_instance: E, + validators_keypairs: Vec, + config: StoreConfig, + ) -> Self { + // Setting the target aggregators to really high means that _all_ validators in the + // committee are required to produce an aggregate. This is overkill, however with small + // validator counts it's the only way to be certain there is _at least one_ aggregator per + // committee. + Self::new_with_target_aggregators(eth_spec_instance, validators_keypairs, 1 << 32, config) } /// Instantiate a new harness with `validator_count` initial validators and a custom /// `target_aggregators_per_committee` spec value pub fn new_with_target_aggregators( eth_spec_instance: E, - keypairs: Vec, + validators_keypairs: Vec, target_aggregators_per_committee: u64, config: StoreConfig, ) -> Self { @@ -107,7 +191,11 @@ impl BeaconChainHarness> { spec.target_aggregators_per_committee = target_aggregators_per_committee; - let log = NullLoggerBuilder.build().expect("logger should build"); + let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter); + let drain = slog_term::FullFormat::new(decorator).build(); + let debug_level = slog::LevelFilter::new(drain, slog::Level::Debug); + let log = slog::Logger::root(std::sync::Mutex::new(debug_level).fuse(), o!()); + let store = HotColdDB::open_ephemeral(config, spec.clone(), log.clone()).unwrap(); let chain = BeaconChainBuilder::new(eth_spec_instance) .logger(log) @@ -116,7 +204,7 @@ impl BeaconChainHarness> { .store_migrator(NullMigrator) .data_dir(data_dir.path().to_path_buf()) .genesis_state( - interop_genesis_state::(&keypairs, HARNESS_GENESIS_TIME, &spec) + interop_genesis_state::(&validators_keypairs, HARNESS_GENESIS_TIME, &spec) .expect("should generate interop state"), ) .expect("should build state using recent genesis") @@ -131,23 +219,27 @@ impl BeaconChainHarness> { Self { spec: chain.spec.clone(), chain, - keypairs, + validators_keypairs, data_dir, + rng: make_rng(), } } } -impl BeaconChainHarness> { +impl BeaconChainHarness> { /// Instantiate a new harness with `validator_count` initial validators. pub fn new_with_disk_store( eth_spec_instance: E, store: Arc, LevelDB>>, - keypairs: Vec, + validators_keypairs: Vec, ) -> Self { let data_dir = tempdir().expect("should create temporary data_dir"); let spec = E::default_spec(); - let log = NullLoggerBuilder.build().expect("logger should build"); + let decorator = slog_term::PlainDecorator::new(slog_term::TestStdoutWriter); + let drain = slog_term::FullFormat::new(decorator).build(); + let debug_level = slog::LevelFilter::new(drain, slog::Level::Debug); + let log = slog::Logger::root(std::sync::Mutex::new(debug_level).fuse(), o!()); let chain = BeaconChainBuilder::new(eth_spec_instance) .logger(log.clone()) @@ -157,7 +249,7 @@ impl BeaconChainHarness> { .store_migrator(BlockingMigrator::new(store, log.clone())) .data_dir(data_dir.path().to_path_buf()) .genesis_state( - interop_genesis_state::(&keypairs, HARNESS_GENESIS_TIME, &spec) + interop_genesis_state::(&validators_keypairs, HARNESS_GENESIS_TIME, &spec) .expect("should generate interop state"), ) .expect("should build state using recent genesis") @@ -172,16 +264,19 @@ impl BeaconChainHarness> { Self { spec: chain.spec.clone(), chain, - keypairs, + validators_keypairs, data_dir, + rng: make_rng(), } } +} +impl BeaconChainHarness> { /// Instantiate a new harness with `validator_count` initial validators. pub fn resume_from_disk_store( eth_spec_instance: E, store: Arc, LevelDB>>, - keypairs: Vec, + validators_keypairs: Vec, data_dir: TempDir, ) -> Self { let spec = E::default_spec(); @@ -211,8 +306,9 @@ impl BeaconChainHarness> { Self { spec: chain.spec.clone(), chain, - keypairs, + validators_keypairs, data_dir, + rng: make_rng(), } } } @@ -224,246 +320,68 @@ where Hot: ItemStore, Cold: ItemStore, { - /// Advance the slot of the `BeaconChain`. - /// - /// Does not produce blocks or attestations. - pub fn advance_slot(&self) { - self.chain.slot_clock.advance_slot(); + pub fn get_all_validators(&self) -> Vec { + (0..self.validators_keypairs.len()).collect() } - /// Extend the `BeaconChain` with some blocks and attestations. Returns the root of the - /// last-produced block (the head of the chain). - /// - /// Chain will be extended by `num_blocks` blocks. - /// - /// The `block_strategy` dictates where the new blocks will be placed. - /// - /// The `attestation_strategy` dictates which validators will attest to the newly created - /// blocks. - pub fn extend_chain( - &self, - num_blocks: usize, - block_strategy: BlockStrategy, - attestation_strategy: AttestationStrategy, - ) -> Hash256 { - let mut i = 0; - self.extend_chain_while( - |_, _| { - i += 1; - i <= num_blocks - }, - block_strategy, - attestation_strategy, - ) + pub fn slots_per_epoch(&self) -> u64 { + E::slots_per_epoch() } - /// Extend the `BeaconChain` with some blocks and attestations. Returns the root of the - /// last-produced block (the head of the chain). - /// - /// Chain will be extended while `predidcate` returns `true`. - /// - /// The `block_strategy` dictates where the new blocks will be placed. - /// - /// The `attestation_strategy` dictates which validators will attest to the newly created - /// blocks. - pub fn extend_chain_while( - &self, - mut predicate: F, - block_strategy: BlockStrategy, - attestation_strategy: AttestationStrategy, - ) -> Hash256 - where - F: FnMut(&SignedBeaconBlock, &BeaconState) -> bool, - { - let mut state = { - // Determine the slot for the first block (or skipped block). - let state_slot = match block_strategy { - BlockStrategy::OnCanonicalHead => { - self.chain.slot().expect("should have a slot") - 1 - } - BlockStrategy::ForkCanonicalChainAt { previous_slot, .. } => previous_slot, - }; - - self.chain - .state_at_slot(state_slot, StateSkipConfig::WithStateRoots) - .expect("should find state for slot") - }; - - // Determine the first slot where a block should be built. - let mut slot = match block_strategy { - BlockStrategy::OnCanonicalHead => self.chain.slot().expect("should have a slot"), - BlockStrategy::ForkCanonicalChainAt { first_slot, .. } => first_slot, - }; - - let mut head_block_root = None; - - loop { - let (block, new_state) = self.build_block(state.clone(), slot); - - if !predicate(&block, &new_state) { - break; - } - - while self.chain.slot().expect("should have a slot") < slot { - self.advance_slot(); - } - - let block_root = self - .chain - .process_block(block) - .expect("should not error during block processing"); - - self.chain.fork_choice().expect("should find head"); - head_block_root = Some(block_root); - - self.add_attestations_for_slot(&attestation_strategy, &new_state, block_root, slot); - - state = new_state; - slot += 1; - } - - head_block_root.expect("did not produce any blocks") + pub fn epoch_start_slot(&self, epoch: u64) -> u64 { + let epoch = Epoch::new(epoch); + epoch.start_slot(E::slots_per_epoch()).into() } - /// A simple method to produce a block at the current slot without applying it to the chain. - /// - /// Always uses `BlockStrategy::OnCanonicalHead`. - pub fn get_block(&self) -> (SignedBeaconBlock, BeaconState) { - let state = self - .chain - .state_at_slot( - self.chain.slot().unwrap() - 1, - StateSkipConfig::WithStateRoots, - ) - .unwrap(); - - let slot = self.chain.slot().unwrap(); - - self.build_block(state, slot) - } - - /// A simple method to produce and process all attestation at the current slot. Always uses - /// `AttestationStrategy::AllValidators`. - pub fn generate_all_attestations(&self) { - let slot = self.chain.slot().unwrap(); - let (state, block_root) = { - let head = self.chain.head().unwrap(); - (head.beacon_state.clone(), head.beacon_block_root) - }; - self.add_attestations_for_slot( - &AttestationStrategy::AllValidators, - &state, - block_root, - slot, - ); - } - - /// Returns current canonical head slot - pub fn get_chain_slot(&self) -> Slot { - self.chain.slot().unwrap() - } - - /// Returns current canonical head state - pub fn get_head_state(&self) -> BeaconState { + pub fn get_current_state(&self) -> BeaconState { self.chain.head().unwrap().beacon_state } - pub fn add_block( - &self, - state: &BeaconState, - slot: Slot, - validators: &[usize], - ) -> (SignedBeaconBlockHash, BeaconState) { - while self.chain.slot().expect("should have a slot") < slot { - self.advance_slot(); - } - - let (block, new_state) = self.build_block(state.clone(), slot); - - let block_root = self - .chain - .process_block(block) - .expect("should not error during block processing"); - - self.chain.fork_choice().expect("should find head"); - - let attestation_strategy = AttestationStrategy::SomeValidators(validators.to_vec()); - self.add_attestations_for_slot(&attestation_strategy, &new_state, block_root, slot); - (block_root.into(), new_state) + pub fn get_current_slot(&self) -> Slot { + self.chain.slot().unwrap() } - /// `add_block()` repeated `num_blocks` times. - #[allow(clippy::type_complexity)] - pub fn add_blocks( - &self, - mut state: BeaconState, - mut slot: Slot, - num_blocks: usize, - attesting_validators: &[usize], - ) -> ( - HashMap, - HashMap, - Slot, - SignedBeaconBlockHash, - BeaconState, - ) { - let mut blocks: HashMap = HashMap::with_capacity(num_blocks); - let mut states: HashMap = HashMap::with_capacity(num_blocks); - for _ in 0..num_blocks { - let (new_root_hash, new_state) = self.add_block(&state, slot, attesting_validators); - blocks.insert(slot, new_root_hash); - states.insert(slot, new_state.tree_hash_root().into()); - state = new_state; - slot += 1; - } - let head_hash = blocks[&(slot - 1)]; - (blocks, states, slot, head_hash, state) + pub fn get_block(&self, block_hash: SignedBeaconBlockHash) -> Option> { + self.chain.get_block(&block_hash.into()).unwrap() } - #[allow(clippy::type_complexity)] - pub fn add_canonical_chain_blocks( - &self, - state: BeaconState, - slot: Slot, - num_blocks: usize, - attesting_validators: &[usize], - ) -> ( - HashMap, - HashMap, - Slot, - SignedBeaconBlockHash, - BeaconState, - ) { - self.add_blocks(state, slot, num_blocks, attesting_validators) + pub fn block_exists(&self, block_hash: SignedBeaconBlockHash) -> bool { + self.get_block(block_hash).is_some() } - #[allow(clippy::type_complexity)] - pub fn add_stray_blocks( - &self, - state: BeaconState, - slot: Slot, - num_blocks: usize, - attesting_validators: &[usize], - ) -> ( - HashMap, - HashMap, - Slot, - SignedBeaconBlockHash, - BeaconState, - ) { - self.add_blocks(state, slot + 2, num_blocks, attesting_validators) + pub fn get_hot_state(&self, state_hash: BeaconStateHash) -> Option> { + self.chain + .store + .load_hot_state(&state_hash.into(), BlockReplay::Accurate) + .unwrap() } - /// Returns a newly created block, signed by the proposer for the given slot. - fn build_block( - &self, + pub fn get_cold_state(&self, state_hash: BeaconStateHash) -> Option> { + self.chain + .store + .load_cold_state(&state_hash.into()) + .unwrap() + } + + pub fn hot_state_exists(&self, state_hash: BeaconStateHash) -> bool { + self.get_hot_state(state_hash).is_some() + } + + pub fn cold_state_exists(&self, state_hash: BeaconStateHash) -> bool { + self.get_cold_state(state_hash).is_some() + } + + pub fn is_skipped_slot(&self, state: &BeaconState, slot: Slot) -> bool { + state.get_block_root(slot).unwrap() == state.get_block_root(slot - 1).unwrap() + } + + pub fn make_block( + &mut self, mut state: BeaconState, slot: Slot, ) -> (SignedBeaconBlock, BeaconState) { - assert_ne!(slot, 0); - if slot < state.slot { - panic!("produce slot cannot be prior to the state slot"); - } + assert_ne!(slot, 0, "can't produce a block at slot 0"); + assert!(slot >= state.slot); while state.slot < slot { per_slot_processing(&mut state, None, &self.spec) @@ -474,28 +392,37 @@ where .build_all_caches(&self.spec) .expect("should build caches"); - let proposer_index = state - .get_beacon_proposer_index(slot, &self.spec) - .expect("should get block proposer from state"); + let proposer_index = state.get_beacon_proposer_index(slot, &self.spec).unwrap(); - let sk = &self.keypairs[proposer_index].sk; - let fork = &state.fork; + // If we produce two blocks for the same slot, they hash up to the same value and + // BeaconChain errors out with `BlockIsAlreadyKnown`. Vary the graffiti so that we produce + // different blocks each time. + self.chain.set_graffiti(self.rng.gen::<[u8; 32]>()); let randao_reveal = { let epoch = slot.epoch(E::slots_per_epoch()); - let domain = - self.spec - .get_domain(epoch, Domain::Randao, fork, state.genesis_validators_root); + let domain = self.spec.get_domain( + epoch, + Domain::Randao, + &state.fork, + state.genesis_validators_root, + ); let message = epoch.signing_root(domain); + let sk = &self.validators_keypairs[proposer_index].sk; sk.sign(message) }; let (block, state) = self .chain .produce_block_on_state(state, slot, randao_reveal, None) - .expect("should produce block"); + .unwrap(); - let signed_block = block.sign(sk, &state.fork, state.genesis_validators_root, &self.spec); + let signed_block = block.sign( + &self.validators_keypairs[proposer_index].sk, + &state.fork, + state.genesis_validators_root, + &self.spec, + ); (signed_block, state) } @@ -505,25 +432,18 @@ where /// The first layer of the Vec is organised per committee. For example, if the return value is /// called `all_attestations`, then all attestations in `all_attestations[0]` will be for /// committee 0, whilst all in `all_attestations[1]` will be for committee 1. - pub fn get_unaggregated_attestations( + pub fn make_unaggregated_attestations( &self, - attestation_strategy: &AttestationStrategy, + attesting_validators: &[usize], state: &BeaconState, - head_block_root: Hash256, + head_block_root: SignedBeaconBlockHash, attestation_slot: Slot, ) -> Vec, SubnetId)>> { - let spec = &self.spec; - let fork = &state.fork; - - let attesting_validators = self.get_attesting_validators(attestation_strategy); - - let committee_count = state - .get_committee_count_at_slot(state.slot) - .expect("should get committee count"); + let committee_count = state.get_committee_count_at_slot(state.slot).unwrap(); state .get_beacon_committees_at_slot(state.slot) - .expect("should get committees") + .unwrap() .iter() .map(|bc| { bc.committee @@ -538,21 +458,18 @@ where .produce_unaggregated_attestation_for_block( attestation_slot, bc.index, - head_block_root, + head_block_root.into(), Cow::Borrowed(state), ) - .expect("should produce attestation"); + .unwrap(); - attestation - .aggregation_bits - .set(i, true) - .expect("should be able to set aggregation bits"); + attestation.aggregation_bits.set(i, true).unwrap(); attestation.signature = { - let domain = spec.get_domain( + let domain = self.spec.get_domain( attestation.data.target.epoch, Domain::BeaconAttester, - fork, + &state.fork, state.genesis_validators_root, ); @@ -560,7 +477,9 @@ where let mut agg_sig = AggregateSignature::infinity(); - agg_sig.add_assign(&self.get_sk(*validator_index).sign(message)); + agg_sig.add_assign( + &self.validators_keypairs[*validator_index].sk.sign(message), + ); agg_sig }; @@ -570,7 +489,7 @@ where committee_count, &self.chain.spec, ) - .expect("should get subnet_id"); + .unwrap(); Some((attestation, subnet_id)) }) @@ -579,56 +498,49 @@ where .collect() } - fn get_attesting_validators(&self, attestation_strategy: &AttestationStrategy) -> Vec { - match attestation_strategy { - AttestationStrategy::AllValidators => (0..self.keypairs.len()).collect(), - AttestationStrategy::SomeValidators(vec) => vec.clone(), - } - } - - /// Generates a `Vec` for some attestation strategy and head_block. - pub fn add_attestations_for_slot( + /// Deprecated: Use make_unaggregated_attestations() instead. + /// + /// A list of attestations for each committee for the given slot. + /// + /// The first layer of the Vec is organised per committee. For example, if the return value is + /// called `all_attestations`, then all attestations in `all_attestations[0]` will be for + /// committee 0, whilst all in `all_attestations[1]` will be for committee 1. + pub fn get_unaggregated_attestations( &self, attestation_strategy: &AttestationStrategy, state: &BeaconState, head_block_root: Hash256, - head_block_slot: Slot, - ) { - // These attestations will not be accepted by the chain so no need to generate them. - if state.slot + E::slots_per_epoch() < self.chain.slot().expect("should get slot") { - return; - } - - let spec = &self.spec; - let fork = &state.fork; - - let attesting_validators = self.get_attesting_validators(attestation_strategy); - - let unaggregated_attestations = self.get_unaggregated_attestations( - attestation_strategy, + attestation_slot: Slot, + ) -> Vec, SubnetId)>> { + let validators: Vec = match attestation_strategy { + AttestationStrategy::AllValidators => self.get_all_validators(), + AttestationStrategy::SomeValidators(vals) => vals.clone(), + }; + self.make_unaggregated_attestations( + &validators, state, - head_block_root, - head_block_slot, - ); + head_block_root.into(), + attestation_slot, + ) + } - // Loop through all unaggregated attestations, submit them to the chain and also submit a - // single aggregate. - unaggregated_attestations - .into_iter() - .for_each(|committee_attestations| { - // Submit each unaggregated attestation to the chain. - for (attestation, subnet_id) in &committee_attestations { - self.chain - .verify_unaggregated_attestation_for_gossip(attestation.clone(), *subnet_id) - .expect("should not error during attestation processing") - .add_to_pool(&self.chain) - .expect("should add attestation to naive pool"); - } + pub fn make_attestations( + &self, + attesting_validators: &[usize], + state: &BeaconState, + block_hash: SignedBeaconBlockHash, + slot: Slot, + ) -> HarnessAttestations { + let unaggregated_attestations = + self.make_unaggregated_attestations(&attesting_validators, &state, block_hash, slot); + let aggregated_attestations: Vec>> = unaggregated_attestations + .iter() + .map(|committee_attestations| { // If there are any attestations in this committee, create an aggregate. if let Some((attestation, _)) = committee_attestations.first() { let bc = state.get_beacon_committee(attestation.data.slot, attestation.data.index) - .expect("should get committee"); + .unwrap(); let aggregator_index = bc.committee .iter() @@ -639,13 +551,13 @@ where let selection_proof = SelectionProof::new::( state.slot, - self.get_sk(*validator_index), - fork, + &self.validators_keypairs[*validator_index].sk, + &state.fork, state.genesis_validators_root, - spec, + &self.spec, ); - selection_proof.is_aggregator(bc.committee.len(), spec).unwrap_or(false) + selection_proof.is_aggregator(bc.committee.len(), &self.spec).unwrap_or(false) }) .copied() .unwrap_or_else(|| panic!( @@ -658,7 +570,7 @@ where let aggregate = self .chain .get_aggregated_attestation(&attestation.data) - .expect("should not error whilst finding aggregate") + .unwrap() .unwrap_or_else(|| { committee_attestations.iter().skip(1).fold(attestation.clone(), |mut agg, (att, _)| { agg.aggregate(att); @@ -670,25 +582,307 @@ where aggregator_index as u64, aggregate, None, - self.get_sk(aggregator_index), - fork, + &self.validators_keypairs[aggregator_index].sk, + &state.fork, state.genesis_validators_root, - spec, + &self.spec, ); - let attn = self.chain - .verify_aggregated_attestation_for_gossip(signed_aggregate) - .expect("should not error during attestation processing"); - - self.chain.apply_attestation_to_fork_choice(&attn) - .expect("should add attestation to fork choice"); - - self.chain.add_to_block_inclusion_pool(attn) - .expect("should add attestation to op pool"); + Some(signed_aggregate) } - }); + else { + None + } + }).collect(); + + unaggregated_attestations + .into_iter() + .zip(aggregated_attestations) + .collect() } + pub fn process_block(&self, slot: Slot, block: SignedBeaconBlock) -> SignedBeaconBlockHash { + assert_eq!(self.chain.slot().unwrap(), slot); + let block_hash: SignedBeaconBlockHash = self.chain.process_block(block).unwrap().into(); + self.chain.fork_choice().unwrap(); + block_hash + } + + pub fn process_attestations(&self, attestations: HarnessAttestations) { + for (unaggregated_attestations, maybe_signed_aggregate) in attestations.into_iter() { + for (attestation, subnet_id) in unaggregated_attestations { + self.chain + .verify_unaggregated_attestation_for_gossip(attestation.clone(), subnet_id) + .unwrap() + .add_to_pool(&self.chain) + .unwrap(); + } + + if let Some(signed_aggregate) = maybe_signed_aggregate { + let attn = self + .chain + .verify_aggregated_attestation_for_gossip(signed_aggregate) + .unwrap(); + + self.chain.apply_attestation_to_fork_choice(&attn).unwrap(); + + self.chain.add_to_block_inclusion_pool(attn).unwrap(); + } + } + } + + fn set_current_slot(&self, slot: Slot) { + let current_slot = self.chain.slot().unwrap(); + let current_epoch = current_slot.epoch(E::slots_per_epoch()); + let epoch = slot.epoch(E::slots_per_epoch()); + assert!( + epoch >= current_epoch, + "Jumping backwards to an earlier epoch isn't well defined. \ + Please generate test blocks epoch-by-epoch instead." + ); + self.chain.slot_clock.set_slot(slot.into()); + } + + pub fn add_block_at_slot( + &mut self, + slot: Slot, + state: BeaconState, + ) -> (SignedBeaconBlockHash, SignedBeaconBlock, BeaconState) { + self.set_current_slot(slot); + let (block, new_state) = self.make_block(state, slot); + let block_hash = self.process_block(slot, block.clone()); + (block_hash, block, new_state) + } + + pub fn attest_block( + &self, + state: &BeaconState, + block_hash: SignedBeaconBlockHash, + block: &SignedBeaconBlock, + validators: &[usize], + ) { + let attestations = + self.make_attestations(validators, &state, block_hash, block.message.slot); + self.process_attestations(attestations); + } + + pub fn add_attested_block_at_slot( + &mut self, + slot: Slot, + state: BeaconState, + validators: &[usize], + ) -> (SignedBeaconBlockHash, BeaconState) { + let (block_hash, block, state) = self.add_block_at_slot(slot, state); + self.attest_block(&state, block_hash, &block, validators); + (block_hash, state) + } + + pub fn add_attested_blocks_at_slots( + &mut self, + state: BeaconState, + slots: &[Slot], + validators: &[usize], + ) -> AddBlocksResult { + assert!(!slots.is_empty()); + self.add_attested_blocks_at_slots_given_lbh(state, slots, validators, None) + } + + fn add_attested_blocks_at_slots_given_lbh( + &mut self, + mut state: BeaconState, + slots: &[Slot], + validators: &[usize], + mut latest_block_hash: Option, + ) -> AddBlocksResult { + assert!( + slots.windows(2).all(|w| w[0] <= w[1]), + "Slots have to be sorted" + ); // slice.is_sorted() isn't stabilized at the moment of writing this + let mut block_hash_from_slot: HashMap = HashMap::new(); + let mut state_hash_from_slot: HashMap = HashMap::new(); + for slot in slots { + let (block_hash, new_state) = self.add_attested_block_at_slot(*slot, state, validators); + state = new_state; + block_hash_from_slot.insert(*slot, block_hash); + state_hash_from_slot.insert(*slot, state.tree_hash_root().into()); + latest_block_hash = Some(block_hash); + } + ( + block_hash_from_slot, + state_hash_from_slot, + latest_block_hash.unwrap(), + state, + ) + } + + /// A monstrosity of great usefulness. + /// + /// Calls `add_attested_blocks_at_slots` for each of the chains in `chains`, + /// taking care to batch blocks by epoch so that the slot clock gets advanced one + /// epoch at a time. + /// + /// Chains is a vec of `(state, slots, validators)` tuples. + pub fn add_blocks_on_multiple_chains( + &mut self, + chains: Vec<(BeaconState, Vec, Vec)>, + ) -> Vec> { + let slots_per_epoch = E::slots_per_epoch(); + + let min_epoch = chains + .iter() + .map(|(_, slots, _)| slots.iter().min().unwrap()) + .min() + .unwrap() + .epoch(slots_per_epoch); + let max_epoch = chains + .iter() + .map(|(_, slots, _)| slots.iter().max().unwrap()) + .max() + .unwrap() + .epoch(slots_per_epoch); + + let mut chains = chains + .into_iter() + .map(|(state, slots, validators)| { + ( + state, + slots, + validators, + HashMap::new(), + HashMap::new(), + SignedBeaconBlockHash::from(Hash256::zero()), + ) + }) + .collect::>(); + + for epoch in min_epoch.as_u64()..=max_epoch.as_u64() { + let mut new_chains = vec![]; + + for (head_state, slots, validators, mut block_hashes, mut state_hashes, head_block) in + chains + { + let epoch_slots = slots + .iter() + .filter(|s| s.epoch(slots_per_epoch).as_u64() == epoch) + .copied() + .collect::>(); + + let (new_block_hashes, new_state_hashes, new_head_block, new_head_state) = self + .add_attested_blocks_at_slots_given_lbh( + head_state, + &epoch_slots, + &validators, + Some(head_block), + ); + + block_hashes.extend(new_block_hashes); + state_hashes.extend(new_state_hashes); + + new_chains.push(( + new_head_state, + slots, + validators, + block_hashes, + state_hashes, + new_head_block, + )); + } + + chains = new_chains; + } + + chains + .into_iter() + .map(|(state, _, _, block_hashes, state_hashes, head_block)| { + (block_hashes, state_hashes, head_block, state) + }) + .collect() + } + + pub fn get_finalized_checkpoints(&self) -> HashSet { + let chain_dump = self.chain.chain_dump().unwrap(); + chain_dump + .iter() + .cloned() + .map(|checkpoint| checkpoint.beacon_state.finalized_checkpoint.root.into()) + .filter(|block_hash| *block_hash != Hash256::zero().into()) + .collect() + } + + /// Deprecated: Do not modify the slot clock manually; rely on add_attested_blocks_at_slots() + /// instead + /// + /// Advance the slot of the `BeaconChain`. + /// + /// Does not produce blocks or attestations. + pub fn advance_slot(&self) { + self.chain.slot_clock.advance_slot(); + } + + /// Deprecated: Use make_block() instead + /// + /// Returns a newly created block, signed by the proposer for the given slot. + pub fn build_block( + &mut self, + state: BeaconState, + slot: Slot, + _block_strategy: BlockStrategy, + ) -> (SignedBeaconBlock, BeaconState) { + self.make_block(state, slot) + } + + /// Deprecated: Use add_attested_blocks_at_slots() instead + /// + /// Extend the `BeaconChain` with some blocks and attestations. Returns the root of the + /// last-produced block (the head of the chain). + /// + /// Chain will be extended by `num_blocks` blocks. + /// + /// The `block_strategy` dictates where the new blocks will be placed. + /// + /// The `attestation_strategy` dictates which validators will attest to the newly created + /// blocks. + pub fn extend_chain( + &mut self, + num_blocks: usize, + block_strategy: BlockStrategy, + attestation_strategy: AttestationStrategy, + ) -> Hash256 { + let (state, slots) = match block_strategy { + BlockStrategy::OnCanonicalHead => { + let current_slot: u64 = self.get_current_slot().into(); + let slots: Vec = (current_slot..(current_slot + (num_blocks as u64))) + .map(Slot::new) + .collect(); + let state = self.get_current_state(); + (state, slots) + } + BlockStrategy::ForkCanonicalChainAt { + previous_slot, + first_slot, + } => { + let first_slot_: u64 = first_slot.into(); + let slots: Vec = (first_slot_..(first_slot_ + (num_blocks as u64))) + .map(Slot::new) + .collect(); + let state = self + .chain + .state_at_slot(previous_slot, StateSkipConfig::WithStateRoots) + .unwrap(); + (state, slots) + } + }; + let validators = match attestation_strategy { + AttestationStrategy::AllValidators => self.get_all_validators(), + AttestationStrategy::SomeValidators(vals) => vals, + }; + let (_, _, last_produced_block_hash, _) = + self.add_attested_blocks_at_slots(state, &slots, &validators); + last_produced_block_hash.into() + } + + /// Deprecated: Use add_attested_blocks_at_slots() instead + /// /// Creates two forks: /// /// - The "honest" fork: created by the `honest_validators` who have built `honest_fork_blocks` @@ -698,7 +892,7 @@ where /// /// Returns `(honest_head, faulty_head)`, the roots of the blocks at the top of each chain. pub fn generate_two_forks_by_skipping_a_block( - &self, + &mut self, honest_validators: &[usize], faulty_validators: &[usize], honest_fork_blocks: usize, @@ -737,9 +931,4 @@ where (honest_head, faulty_head) } - - /// Returns the secret key for the given validator index. - fn get_sk(&self, validator_index: usize) -> &SecretKey { - &self.keypairs[validator_index].sk - } } diff --git a/beacon_node/beacon_chain/tests/attestation_production.rs b/beacon_node/beacon_chain/tests/attestation_production.rs index 407bf2ee7..a01286d64 100644 --- a/beacon_node/beacon_chain/tests/attestation_production.rs +++ b/beacon_node/beacon_chain/tests/attestation_production.rs @@ -26,7 +26,7 @@ lazy_static! { fn produces_attestations() { let num_blocks_produced = MainnetEthSpec::slots_per_epoch() * 4; - let harness = BeaconChainHarness::new( + let mut harness = BeaconChainHarness::new_with_store_config( MainnetEthSpec, KEYPAIRS[..].to_vec(), StoreConfig::default(), diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index 27db261cb..ec2200b1d 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -5,7 +5,9 @@ extern crate lazy_static; use beacon_chain::{ attestation_verification::Error as AttnError, - test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType}, + test_utils::{ + AttestationStrategy, BeaconChainHarness, BlockStrategy, NullMigratorEphemeralHarnessType, + }, BeaconChain, BeaconChainTypes, }; use int_to_bytes::int_to_bytes32; @@ -30,7 +32,7 @@ lazy_static! { } /// Returns a beacon chain harness. -fn get_harness(validator_count: usize) -> BeaconChainHarness> { +fn get_harness(validator_count: usize) -> BeaconChainHarness> { let harness = BeaconChainHarness::new_with_target_aggregators( MainnetEthSpec, KEYPAIRS[0..validator_count].to_vec(), @@ -184,8 +186,7 @@ fn get_non_aggregator( /// Tests verification of `SignedAggregateAndProof` from the gossip network. #[test] fn aggregated_gossip_verification() { - let harness = get_harness(VALIDATOR_COUNT); - let chain = &harness.chain; + let mut harness = get_harness(VALIDATOR_COUNT); // Extend the chain out a few epochs so we have some chain depth to play with. harness.extend_chain( @@ -197,7 +198,7 @@ fn aggregated_gossip_verification() { // Advance into a slot where there have not been blocks or attestations produced. harness.advance_slot(); - let current_slot = chain.slot().expect("should get slot"); + let current_slot = harness.chain.slot().expect("should get slot"); assert_eq!( current_slot % E::slots_per_epoch(), @@ -532,8 +533,7 @@ fn aggregated_gossip_verification() { /// Tests the verification conditions for an unaggregated attestation on the gossip network. #[test] fn unaggregated_gossip_verification() { - let harness = get_harness(VALIDATOR_COUNT); - let chain = &harness.chain; + let mut harness = get_harness(VALIDATOR_COUNT); // Extend the chain out a few epochs so we have some chain depth to play with. harness.extend_chain( @@ -545,8 +545,8 @@ fn unaggregated_gossip_verification() { // Advance into a slot where there have not been blocks or attestations produced. harness.advance_slot(); - let current_slot = chain.slot().expect("should get slot"); - let current_epoch = chain.epoch().expect("should get epoch"); + let current_slot = harness.chain.slot().expect("should get slot"); + let current_epoch = harness.chain.epoch().expect("should get epoch"); assert_eq!( current_slot % E::slots_per_epoch(), @@ -772,8 +772,7 @@ fn unaggregated_gossip_verification() { /// This also checks that we can do a state lookup if we don't get a hit from the shuffling cache. #[test] fn attestation_that_skips_epochs() { - let harness = get_harness(VALIDATOR_COUNT); - let chain = &harness.chain; + let mut harness = get_harness(VALIDATOR_COUNT); // Extend the chain out a few epochs so we have some chain depth to play with. harness.extend_chain( @@ -782,16 +781,18 @@ fn attestation_that_skips_epochs() { AttestationStrategy::SomeValidators(vec![]), ); - let current_slot = chain.slot().expect("should get slot"); - let current_epoch = chain.epoch().expect("should get epoch"); + let current_slot = harness.chain.slot().expect("should get slot"); + let current_epoch = harness.chain.epoch().expect("should get epoch"); let earlier_slot = (current_epoch - 2).start_slot(MainnetEthSpec::slots_per_epoch()); - let earlier_block = chain + let earlier_block = harness + .chain .block_at_slot(earlier_slot) .expect("should not error getting block at slot") .expect("should find block at slot"); - let mut state = chain + let mut state = harness + .chain .get_state(&earlier_block.state_root(), Some(earlier_slot)) .expect("should not error getting state") .expect("should find state"); diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index fbee03268..409511761 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -4,7 +4,9 @@ extern crate lazy_static; use beacon_chain::{ - test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType}, + test_utils::{ + AttestationStrategy, BeaconChainHarness, BlockStrategy, NullMigratorEphemeralHarnessType, + }, BeaconSnapshot, BlockError, }; use store::config::StoreConfig; @@ -31,7 +33,7 @@ lazy_static! { } fn get_chain_segment() -> Vec> { - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); harness.extend_chain( CHAIN_SEGMENT_LENGTH, @@ -48,8 +50,8 @@ fn get_chain_segment() -> Vec> { .collect() } -fn get_harness(validator_count: usize) -> BeaconChainHarness> { - let harness = BeaconChainHarness::new( +fn get_harness(validator_count: usize) -> BeaconChainHarness> { + let harness = BeaconChainHarness::new_with_store_config( MainnetEthSpec, KEYPAIRS[0..validator_count].to_vec(), StoreConfig::default(), @@ -81,7 +83,7 @@ fn junk_aggregate_signature() -> AggregateSignature { fn update_proposal_signatures( snapshots: &mut [BeaconSnapshot], - harness: &BeaconChainHarness>, + harness: &BeaconChainHarness>, ) { for snapshot in snapshots { let spec = &harness.chain.spec; @@ -91,7 +93,7 @@ fn update_proposal_signatures( .get_beacon_proposer_index(slot, spec) .expect("should find proposer index"); let keypair = harness - .keypairs + .validators_keypairs .get(proposer_index) .expect("proposer keypair should be available"); @@ -274,7 +276,7 @@ fn chain_segment_non_linear_slots() { } fn assert_invalid_signature( - harness: &BeaconChainHarness>, + harness: &BeaconChainHarness>, block_index: usize, snapshots: &[BeaconSnapshot], item: &str, @@ -325,7 +327,7 @@ fn assert_invalid_signature( // slot) tuple. } -fn get_invalid_sigs_harness() -> BeaconChainHarness> { +fn get_invalid_sigs_harness() -> BeaconChainHarness> { let harness = get_harness(VALIDATOR_COUNT); harness .chain diff --git a/beacon_node/beacon_chain/tests/op_verification.rs b/beacon_node/beacon_chain/tests/op_verification.rs index 4bb2f0748..6ca90565b 100644 --- a/beacon_node/beacon_chain/tests/op_verification.rs +++ b/beacon_node/beacon_chain/tests/op_verification.rs @@ -7,7 +7,7 @@ extern crate lazy_static; use beacon_chain::observed_operations::ObservationOutcome; use beacon_chain::test_utils::{ - AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType, + AttestationStrategy, BeaconChainHarness, BlockStrategy, BlockingMigratorDiskHarnessType, }; use sloggers::{null::NullLoggerBuilder, Build}; use std::sync::Arc; @@ -28,7 +28,7 @@ lazy_static! { } type E = MinimalEthSpec; -type TestHarness = BeaconChainHarness>; +type TestHarness = BeaconChainHarness>; type HotColdDB = store::HotColdDB, LevelDB>; fn get_store(db_path: &TempDir) -> Arc { @@ -57,8 +57,8 @@ fn get_harness(store: Arc, validator_count: usize) -> TestHarness { fn voluntary_exit() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), VALIDATOR_COUNT); - let spec = &harness.chain.spec; + let mut harness = get_harness(store.clone(), VALIDATOR_COUNT); + let spec = &harness.chain.spec.clone(); harness.extend_chain( (E::slots_per_epoch() * (spec.shard_committee_period + 1)) as usize, diff --git a/beacon_node/beacon_chain/tests/persistence_tests.rs b/beacon_node/beacon_chain/tests/persistence_tests.rs index 23f1a94f4..b5bc3cae1 100644 --- a/beacon_node/beacon_chain/tests/persistence_tests.rs +++ b/beacon_node/beacon_chain/tests/persistence_tests.rs @@ -44,7 +44,7 @@ fn finalizes_after_resuming_from_db() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = BeaconChainHarness::new_with_disk_store( + let mut harness = BeaconChainHarness::new_with_disk_store( MinimalEthSpec, store.clone(), KEYPAIRS[0..validator_count].to_vec(), @@ -88,7 +88,7 @@ fn finalizes_after_resuming_from_db() { let data_dir = harness.data_dir; let original_chain = harness.chain; - let resumed_harness = BeaconChainHarness::resume_from_disk_store( + let mut resumed_harness = BeaconChainHarness::resume_from_disk_store( MinimalEthSpec, store, KEYPAIRS[0..validator_count].to_vec(), diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 8d322f83e..caa2f9d6c 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -10,13 +10,14 @@ extern crate slog_term; use crate::slog::Drain; use beacon_chain::attestation_verification::Error as AttnError; use beacon_chain::test_utils::{ - AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType, + AttestationStrategy, BeaconChainHarness, BlockStrategy, BlockingMigratorDiskHarnessType, }; use beacon_chain::BeaconSnapshot; -use beacon_chain::StateSkipConfig; +use maplit::hashset; use rand::Rng; use std::collections::HashMap; use std::collections::HashSet; +use std::convert::TryInto; use std::sync::Arc; use store::{ iter::{BlockRootsIterator, StateRootsIterator}, @@ -37,7 +38,7 @@ lazy_static! { } type E = MinimalEthSpec; -type TestHarness = BeaconChainHarness>; +type TestHarness = BeaconChainHarness>; fn get_store(db_path: &TempDir) -> Arc, LevelDB>> { let spec = MinimalEthSpec::default_spec(); @@ -73,7 +74,7 @@ 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(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); harness.extend_chain( num_blocks_produced as usize, @@ -93,7 +94,7 @@ fn randomised_skips() { let mut num_blocks_produced = 0; let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); let rng = &mut XorShiftRng::from_seed([42; 16]); let mut head_slot = 0; @@ -129,7 +130,7 @@ fn randomised_skips() { fn long_skip() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_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 @@ -180,7 +181,7 @@ 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 mut harness = get_harness(store.clone(), validator_count); let num_slots = E::slots_per_epoch() * (E::epochs_per_historical_vector() - 1) as u64; @@ -241,7 +242,7 @@ fn split_slot_restore() { let split_slot = { let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); let num_blocks = 4 * E::slots_per_epoch(); @@ -269,7 +270,7 @@ fn epoch_boundary_state_attestation_processing() { 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(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); let late_validators = vec![0, 1]; let timely_validators = (2..LOW_VALIDATOR_COUNT).collect::>(); @@ -355,16 +356,18 @@ fn epoch_boundary_state_attestation_processing() { fn delete_blocks_and_states() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let validators_keypairs = + types::test_utils::generate_deterministic_keypairs(LOW_VALIDATOR_COUNT); + let mut harness = + BeaconChainHarness::new_with_disk_store(MinimalEthSpec, store.clone(), validators_keypairs); - let unforked_blocks = 4 * E::slots_per_epoch(); + let unforked_blocks: u64 = 4 * E::slots_per_epoch(); // Finalize an initial portion of the chain. - harness.extend_chain( - unforked_blocks as usize, - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, - ); + let initial_slots: Vec = (1..=unforked_blocks).map(Into::into).collect(); + let state = harness.get_current_state(); + let all_validators = harness.get_all_validators(); + harness.add_attested_blocks_at_slots(state, &initial_slots, &all_validators); // Create a fork post-finalization. let two_thirds = (LOW_VALIDATOR_COUNT / 3) * 2; @@ -373,24 +376,37 @@ fn delete_blocks_and_states() { let fork_blocks = 2 * E::slots_per_epoch(); - let (honest_head, faulty_head) = harness.generate_two_forks_by_skipping_a_block( - &honest_validators, - &faulty_validators, - fork_blocks as usize, - fork_blocks as usize, - ); + let slot_u64: u64 = harness.get_current_slot().as_u64() + 1; + + let fork1_slots: Vec = (slot_u64..(slot_u64 + fork_blocks)) + .map(Into::into) + .collect(); + let fork2_slots: Vec = (slot_u64 + 1..(slot_u64 + 1 + fork_blocks)) + .map(Into::into) + .collect(); + + let fork1_state = harness.get_current_state(); + let fork2_state = fork1_state.clone(); + let results = harness.add_blocks_on_multiple_chains(vec![ + (fork1_state, fork1_slots, honest_validators), + (fork2_state, fork2_slots, faulty_validators), + ]); + + let honest_head = results[0].2; + let faulty_head = results[1].2; assert_ne!(honest_head, faulty_head, "forks should be distinct"); let head_info = harness.chain.head_info().expect("should get head"); assert_eq!(head_info.slot, unforked_blocks + fork_blocks); assert_eq!( - head_info.block_root, honest_head, + head_info.block_root, + honest_head.into(), "the honest chain should be the canonical chain", ); let faulty_head_block = store - .get_block(&faulty_head) + .get_block(&faulty_head.into()) .expect("no errors") .expect("faulty head block exists"); @@ -402,31 +418,35 @@ fn delete_blocks_and_states() { .expect("no db error") .expect("faulty head state exists"); - let states_to_delete = StateRootsIterator::new(store.clone(), &faulty_head_state) - .map(Result::unwrap) - .take_while(|(_, slot)| *slot > unforked_blocks) - .collect::>(); - // Delete faulty fork // Attempting to load those states should find them unavailable - for (state_root, slot) in &states_to_delete { - store.delete_state(state_root, *slot).unwrap(); - assert_eq!(store.get_state(state_root, Some(*slot)).unwrap(), None); + for (state_root, slot) in + StateRootsIterator::new(store.clone(), &faulty_head_state).map(Result::unwrap) + { + if slot <= unforked_blocks { + break; + } + store.delete_state(&state_root, slot).unwrap(); + assert_eq!(store.get_state(&state_root, Some(slot)).unwrap(), None); } // Double-deleting should also be OK (deleting non-existent things is fine) - for (state_root, slot) in &states_to_delete { - store.delete_state(state_root, *slot).unwrap(); + for (state_root, slot) in + StateRootsIterator::new(store.clone(), &faulty_head_state).map(Result::unwrap) + { + if slot <= unforked_blocks { + break; + } + store.delete_state(&state_root, slot).unwrap(); } // Deleting the blocks from the fork should remove them completely - let blocks_to_delete = BlockRootsIterator::new(store.clone(), &faulty_head_state) - .map(Result::unwrap) - // Extra +1 here accounts for the skipped slot that started this fork - .take_while(|(_, slot)| *slot > unforked_blocks + 1) - .collect::>(); - - for (block_root, _) in blocks_to_delete { + for (block_root, slot) in + BlockRootsIterator::new(store.clone(), &faulty_head_state).map(Result::unwrap) + { + if slot <= unforked_blocks + 1 { + break; + } store.delete_block(&block_root).unwrap(); assert_eq!(store.get_block(&block_root).unwrap(), None); } @@ -437,11 +457,12 @@ fn delete_blocks_and_states() { .chain .rev_iter_state_roots() .expect("rev iter ok") - .map(Result::unwrap) - .filter(|(_, slot)| *slot < split_slot); + .map(Result::unwrap); for (state_root, slot) in finalized_states { - store.delete_state(&state_root, slot).unwrap(); + if slot < split_slot { + store.delete_state(&state_root, slot).unwrap(); + } } // After all that, the chain dump should still be OK @@ -452,35 +473,52 @@ fn delete_blocks_and_states() { // See https://github.com/sigp/lighthouse/issues/845 fn multi_epoch_fork_valid_blocks_test( initial_blocks: usize, - num_fork1_blocks: usize, - num_fork2_blocks: usize, + num_fork1_blocks_: usize, + num_fork2_blocks_: usize, num_fork1_validators: usize, ) -> (TempDir, TestHarness, Hash256, Hash256) { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let validators_keypairs = + types::test_utils::generate_deterministic_keypairs(LOW_VALIDATOR_COUNT); + let mut harness = + BeaconChainHarness::new_with_disk_store(MinimalEthSpec, store, validators_keypairs); + + let num_fork1_blocks: u64 = num_fork1_blocks_.try_into().unwrap(); + let num_fork2_blocks: u64 = num_fork2_blocks_.try_into().unwrap(); // Create the initial portion of the chain if initial_blocks > 0 { - harness.extend_chain( - initial_blocks, - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, - ); + let initial_slots: Vec = (1..=initial_blocks).map(Into::into).collect(); + let state = harness.get_current_state(); + let all_validators = harness.get_all_validators(); + harness.add_attested_blocks_at_slots(state, &initial_slots, &all_validators); } assert!(num_fork1_validators <= LOW_VALIDATOR_COUNT); let fork1_validators: Vec = (0..num_fork1_validators).collect(); let fork2_validators: Vec = (num_fork1_validators..LOW_VALIDATOR_COUNT).collect(); - let (head1, head2) = harness.generate_two_forks_by_skipping_a_block( - &fork1_validators, - &fork2_validators, - num_fork1_blocks, - num_fork2_blocks, - ); + let fork1_state = harness.get_current_state(); + let fork2_state = fork1_state.clone(); - (db_path, harness, head1, head2) + let slot_u64: u64 = harness.get_current_slot().as_u64() + 1; + let fork1_slots: Vec = (slot_u64..(slot_u64 + num_fork1_blocks)) + .map(Into::into) + .collect(); + let fork2_slots: Vec = (slot_u64 + 1..(slot_u64 + 1 + num_fork2_blocks)) + .map(Into::into) + .collect(); + + let results = harness.add_blocks_on_multiple_chains(vec![ + (fork1_state, fork1_slots, fork1_validators), + (fork2_state, fork2_slots, fork2_validators), + ]); + + let head1 = results[0].2; + let head2 = results[1].2; + + (db_path, harness, head1.into(), head2.into()) } // This is the minimal test of block production with different shufflings. @@ -512,8 +550,7 @@ fn block_production_different_shuffling_long() { fn multiple_attestations_per_block() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store, HIGH_VALIDATOR_COUNT); - let chain = &harness.chain; + let mut harness = get_harness(store, HIGH_VALIDATOR_COUNT); harness.extend_chain( MainnetEthSpec::slots_per_epoch() as usize * 3, @@ -521,14 +558,14 @@ fn multiple_attestations_per_block() { AttestationStrategy::AllValidators, ); - let head = chain.head().unwrap(); + let head = harness.chain.head().unwrap(); let committees_per_slot = head .beacon_state .get_committee_count_at_slot(head.beacon_state.slot) .unwrap(); assert!(committees_per_slot > 1); - for snapshot in chain.chain_dump().unwrap() { + for snapshot in harness.chain.chain_dump().unwrap() { assert_eq!( snapshot.beacon_block.message.body.attestations.len() as u64, if snapshot.beacon_block.slot() <= 1 { @@ -544,7 +581,7 @@ fn multiple_attestations_per_block() { fn shuffling_compatible_linear_chain() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); // Skip the block at the end of the first epoch. let head_block_root = harness.extend_chain( @@ -568,7 +605,7 @@ fn shuffling_compatible_linear_chain() { fn shuffling_compatible_missing_pivot_block() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); // Skip the block at the end of the first epoch. harness.extend_chain( @@ -726,150 +763,160 @@ fn check_shuffling_compatible( // Ensure blocks from abandoned forks are pruned from the Hot DB #[test] fn prunes_abandoned_fork_between_two_finalized_checkpoints() { - const VALIDATOR_COUNT: usize = 24; - const VALIDATOR_SUPERMAJORITY: usize = (VALIDATOR_COUNT / 3) * 2; - let db_path = tempdir().unwrap(); - let store = get_store(&db_path); - let harness = get_harness(Arc::clone(&store), VALIDATOR_COUNT); - const HONEST_VALIDATOR_COUNT: usize = VALIDATOR_SUPERMAJORITY; + const HONEST_VALIDATOR_COUNT: usize = 16 + 0; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0; + const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; + let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); - let faulty_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let slots_per_epoch: usize = MinimalEthSpec::slots_per_epoch() as usize; + let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); + let mut rig = BeaconChainHarness::new(MinimalEthSpec, validators_keypairs); + let slots_per_epoch = rig.slots_per_epoch(); + let mut state = rig.get_current_state(); - let slot = harness.get_chain_slot(); - let state = harness.get_head_state(); - let (canonical_blocks_pre_finalization, _, slot, _, state) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch, &honest_validators); - let (stray_blocks, stray_states, _, stray_head, _) = harness.add_stray_blocks( - harness.get_head_state(), - slot, - slots_per_epoch - 3, - &faulty_validators, + let canonical_chain_slots: Vec = (1..=rig.epoch_start_slot(1)).map(Slot::new).collect(); + let (canonical_chain_blocks_pre_finalization, _, _, new_state) = + rig.add_attested_blocks_at_slots(state, &canonical_chain_slots, &honest_validators); + state = new_state; + let canonical_chain_slot: u64 = rig.get_current_slot().into(); + + let stray_slots: Vec = (canonical_chain_slot + 1..rig.epoch_start_slot(2)) + .map(Slot::new) + .collect(); + let (stray_blocks, stray_states, stray_head, _) = rig.add_attested_blocks_at_slots( + rig.get_current_state(), + &stray_slots, + &adversarial_validators, ); // Precondition: Ensure all stray_blocks blocks are still known for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_some(), + rig.block_exists(block_hash), "stray block {} should be still present", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness - .chain - .get_state(&state_hash.into(), Some(slot)) - .unwrap(); assert!( - state.is_some(), + rig.hot_state_exists(state_hash), "stray state {} at slot {} should be still present", state_hash, slot ); } - // Precondition: Only genesis is finalized - let chain_dump = harness.chain.chain_dump().unwrap(); - assert_eq!( - get_finalized_epoch_boundary_blocks(&chain_dump), - vec![Hash256::zero().into()].into_iter().collect(), - ); + assert_eq!(rig.get_finalized_checkpoints(), hashset! {},); - assert!(harness.chain.knows_head(&stray_head)); + assert!(rig.chain.knows_head(&stray_head)); // Trigger finalization - let (canonical_blocks_post_finalization, _, _, _, _) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch * 5, &honest_validators); + let finalization_slots: Vec = ((canonical_chain_slot + 1) + ..=(canonical_chain_slot + slots_per_epoch * 5)) + .map(Slot::new) + .collect(); + let (canonical_chain_blocks_post_finalization, _, _, _) = + rig.add_attested_blocks_at_slots(state, &finalization_slots, &honest_validators); // Postcondition: New blocks got finalized - let chain_dump = harness.chain.chain_dump().unwrap(); - let finalized_blocks = get_finalized_epoch_boundary_blocks(&chain_dump); assert_eq!( - finalized_blocks, - vec![ - Hash256::zero().into(), - canonical_blocks_pre_finalization[&Slot::new(slots_per_epoch as u64)], - canonical_blocks_post_finalization[&Slot::new((slots_per_epoch * 2) as u64)], - ] - .into_iter() - .collect() + rig.get_finalized_checkpoints(), + hashset! { + canonical_chain_blocks_pre_finalization[&rig.epoch_start_slot(1).into()], + canonical_chain_blocks_post_finalization[&rig.epoch_start_slot(2).into()], + }, ); // Postcondition: Ensure all stray_blocks blocks have been pruned for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_none(), + !rig.block_exists(block_hash), "abandoned block {} should have been pruned", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_none(), - "stray state {} at slot {} should have been deleted", + !rig.hot_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", + state_hash, + slot + ); + assert!( + !rig.cold_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", state_hash, slot ); } - assert!(!harness.chain.knows_head(&stray_head)); + assert!(!rig.chain.knows_head(&stray_head)); } #[test] fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() { - const VALIDATOR_COUNT: usize = 24; - const VALIDATOR_SUPERMAJORITY: usize = (VALIDATOR_COUNT / 3) * 2; - let db_path = tempdir().unwrap(); - let store = get_store(&db_path); - let harness = get_harness(Arc::clone(&store), VALIDATOR_COUNT); - const HONEST_VALIDATOR_COUNT: usize = VALIDATOR_SUPERMAJORITY; + const HONEST_VALIDATOR_COUNT: usize = 16 + 0; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0; + const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; + let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); - let faulty_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let all_validators: Vec = (0..VALIDATOR_COUNT).collect(); - let slots_per_epoch: usize = MinimalEthSpec::slots_per_epoch() as usize; + let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); + let mut rig = BeaconChainHarness::new(MinimalEthSpec, validators_keypairs); + let slots_per_epoch = rig.slots_per_epoch(); + let state = rig.get_current_state(); // Fill up 0th epoch - let slot = harness.get_chain_slot(); - let state = harness.get_head_state(); - let (canonical_blocks_zeroth_epoch, _, slot, _, state) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch, &honest_validators); + let canonical_chain_slots_zeroth_epoch: Vec = + (1..rig.epoch_start_slot(1)).map(Slot::new).collect(); + let (_, _, _, state) = rig.add_attested_blocks_at_slots( + state, + &canonical_chain_slots_zeroth_epoch, + &honest_validators, + ); // Fill up 1st epoch - let (_, _, canonical_slot, shared_head, canonical_state) = - harness.add_canonical_chain_blocks(state, slot, 1, &all_validators); - let (stray_blocks, stray_states, _, stray_head, _) = harness.add_stray_blocks( - canonical_state.clone(), - canonical_slot, - 1, - &faulty_validators, + let canonical_chain_slots_first_epoch: Vec = (rig.epoch_start_slot(1) + ..=rig.epoch_start_slot(1) + 1) + .map(Slot::new) + .collect(); + let (canonical_chain_blocks_first_epoch, _, shared_head, state) = rig + .add_attested_blocks_at_slots( + state.clone(), + &canonical_chain_slots_first_epoch, + &honest_validators, + ); + let canonical_chain_slot: u64 = rig.get_current_slot().into(); + + let stray_chain_slots_first_epoch: Vec = (rig.epoch_start_slot(1) + 2 + ..=rig.epoch_start_slot(1) + 2) + .map(Slot::new) + .collect(); + let (stray_blocks, stray_states, stray_head, _) = rig.add_attested_blocks_at_slots( + state.clone(), + &stray_chain_slots_first_epoch, + &adversarial_validators, ); // Preconditions for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_some(), + rig.block_exists(block_hash), "stray block {} should be still present", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_some(), + rig.hot_state_exists(state_hash), "stray state {} at slot {} should be still present", state_hash, slot ); } - let chain_dump = harness.chain.chain_dump().unwrap(); + let chain_dump = rig.chain.chain_dump().unwrap(); assert_eq!( get_finalized_epoch_boundary_blocks(&chain_dump), vec![Hash256::zero().into()].into_iter().collect(), @@ -878,172 +925,167 @@ fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() { assert!(get_blocks(&chain_dump).contains(&shared_head)); // Trigger finalization - let (canonical_blocks, _, _, _, _) = harness.add_canonical_chain_blocks( - canonical_state, - canonical_slot, - slots_per_epoch * 5, - &honest_validators, - ); + let finalization_slots: Vec = ((canonical_chain_slot + 1) + ..=(canonical_chain_slot + slots_per_epoch * 5)) + .map(Slot::new) + .collect(); + let (canonical_chain_blocks, _, _, _) = + rig.add_attested_blocks_at_slots(state, &finalization_slots, &honest_validators); // Postconditions - let chain_dump = harness.chain.chain_dump().unwrap(); - let finalized_blocks = get_finalized_epoch_boundary_blocks(&chain_dump); assert_eq!( - finalized_blocks, - vec![ - Hash256::zero().into(), - canonical_blocks_zeroth_epoch[&Slot::new(slots_per_epoch as u64)], - canonical_blocks[&Slot::new((slots_per_epoch * 2) as u64)], - ] - .into_iter() - .collect() + rig.get_finalized_checkpoints(), + hashset! { + canonical_chain_blocks_first_epoch[&rig.epoch_start_slot(1).into()], + canonical_chain_blocks[&rig.epoch_start_slot(2).into()], + }, ); for &block_hash in stray_blocks.values() { assert!( - harness - .chain - .get_block(&block_hash.into()) - .unwrap() - .is_none(), + !rig.block_exists(block_hash), "stray block {} should have been pruned", block_hash, ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_none(), - "stray state {} at slot {} should have been deleted", + !rig.hot_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", + state_hash, + slot + ); + assert!( + !rig.cold_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", state_hash, slot ); } - assert!(!harness.chain.knows_head(&stray_head)); + assert!(!rig.chain.knows_head(&stray_head)); + let chain_dump = rig.chain.chain_dump().unwrap(); assert!(get_blocks(&chain_dump).contains(&shared_head)); } #[test] fn pruning_does_not_touch_blocks_prior_to_finalization() { - const VALIDATOR_COUNT: usize = 24; - const VALIDATOR_SUPERMAJORITY: usize = (VALIDATOR_COUNT / 3) * 2; - let db_path = tempdir().unwrap(); - let store = get_store(&db_path); - let harness = get_harness(Arc::clone(&store), VALIDATOR_COUNT); - const HONEST_VALIDATOR_COUNT: usize = VALIDATOR_SUPERMAJORITY; + const HONEST_VALIDATOR_COUNT: usize = 16; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 8; + const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; + let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); - let faulty_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let slots_per_epoch: usize = MinimalEthSpec::slots_per_epoch() as usize; + let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); + let mut rig = BeaconChainHarness::new(MinimalEthSpec, validators_keypairs); + let slots_per_epoch = rig.slots_per_epoch(); + let mut state = rig.get_current_state(); // Fill up 0th epoch with canonical chain blocks - let slot = harness.get_chain_slot(); - let state = harness.get_head_state(); - let (canonical_blocks_zeroth_epoch, _, slot, _, state) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch, &honest_validators); + let zeroth_epoch_slots: Vec = (1..=rig.epoch_start_slot(1)).map(Slot::new).collect(); + let (canonical_chain_blocks, _, _, new_state) = + rig.add_attested_blocks_at_slots(state, &zeroth_epoch_slots, &honest_validators); + state = new_state; + let canonical_chain_slot: u64 = rig.get_current_slot().into(); // Fill up 1st epoch. Contains a fork. - let (stray_blocks, stray_states, _, stray_head, _) = - harness.add_stray_blocks(state.clone(), slot, slots_per_epoch - 1, &faulty_validators); + let first_epoch_slots: Vec = ((rig.epoch_start_slot(1) + 1)..(rig.epoch_start_slot(2))) + .map(Slot::new) + .collect(); + let (stray_blocks, stray_states, stray_head, _) = rig.add_attested_blocks_at_slots( + state.clone(), + &first_epoch_slots, + &adversarial_validators, + ); // Preconditions for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_some(), + rig.block_exists(block_hash), "stray block {} should be still present", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_some(), + rig.hot_state_exists(state_hash), "stray state {} at slot {} should be still present", state_hash, slot ); } - let chain_dump = harness.chain.chain_dump().unwrap(); - assert_eq!( - get_finalized_epoch_boundary_blocks(&chain_dump), - vec![Hash256::zero().into()].into_iter().collect(), - ); + assert_eq!(rig.get_finalized_checkpoints(), hashset! {}); // Trigger finalization - let (_, _, _, _, _) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch * 4, &honest_validators); + let slots: Vec = ((canonical_chain_slot + 1) + ..=(canonical_chain_slot + slots_per_epoch * 4)) + .map(Slot::new) + .collect(); + let (_, _, _, _) = rig.add_attested_blocks_at_slots(state, &slots, &honest_validators); // Postconditions - let chain_dump = harness.chain.chain_dump().unwrap(); - let finalized_blocks = get_finalized_epoch_boundary_blocks(&chain_dump); assert_eq!( - finalized_blocks, - vec![ - Hash256::zero().into(), - canonical_blocks_zeroth_epoch[&Slot::new(slots_per_epoch as u64)], - ] - .into_iter() - .collect() + rig.get_finalized_checkpoints(), + hashset! {canonical_chain_blocks[&rig.epoch_start_slot(1).into()]}, ); for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_some(), + rig.block_exists(block_hash), "stray block {} should be still present", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_some(), + rig.hot_state_exists(state_hash), "stray state {} at slot {} should be still present", state_hash, slot ); } - assert!(harness.chain.knows_head(&stray_head)); + assert!(rig.chain.knows_head(&stray_head)); } #[test] -fn prunes_fork_running_past_finalized_checkpoint() { - const VALIDATOR_COUNT: usize = 24; - const VALIDATOR_SUPERMAJORITY: usize = (VALIDATOR_COUNT / 3) * 2; - let db_path = tempdir().unwrap(); - let store = get_store(&db_path); - let harness = get_harness(Arc::clone(&store), VALIDATOR_COUNT); - const HONEST_VALIDATOR_COUNT: usize = VALIDATOR_SUPERMAJORITY; +fn prunes_fork_growing_past_youngest_finalized_checkpoint() { + const HONEST_VALIDATOR_COUNT: usize = 16 + 0; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0; + const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; + let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); - let faulty_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let slots_per_epoch: usize = MinimalEthSpec::slots_per_epoch() as usize; + let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); + let mut rig = BeaconChainHarness::new(MinimalEthSpec, validators_keypairs); + let state = rig.get_current_state(); // Fill up 0th epoch with canonical chain blocks - let slot = harness.get_chain_slot(); - let state = harness.get_head_state(); - let (canonical_blocks_zeroth_epoch, _, slot, _, state) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch, &honest_validators); + let zeroth_epoch_slots: Vec = (1..=rig.epoch_start_slot(1)).map(Slot::new).collect(); + let (canonical_blocks_zeroth_epoch, _, _, state) = + rig.add_attested_blocks_at_slots(state, &zeroth_epoch_slots, &honest_validators); // Fill up 1st epoch. Contains a fork. - let (stray_blocks_first_epoch, stray_states_first_epoch, stray_slot, _, stray_state) = - harness.add_stray_blocks(state.clone(), slot, slots_per_epoch, &faulty_validators); - - let (canonical_blocks_first_epoch, _, canonical_slot, _, canonical_state) = - harness.add_canonical_chain_blocks(state, slot, slots_per_epoch, &honest_validators); + let slots_first_epoch: Vec = (rig.epoch_start_slot(1) + 1..rig.epoch_start_slot(2)) + .map(Into::into) + .collect(); + let (stray_blocks_first_epoch, stray_states_first_epoch, _, stray_state) = rig + .add_attested_blocks_at_slots(state.clone(), &slots_first_epoch, &adversarial_validators); + let (canonical_blocks_first_epoch, _, _, canonical_state) = + rig.add_attested_blocks_at_slots(state, &slots_first_epoch, &honest_validators); // Fill up 2nd epoch. Extends both the canonical chain and the fork. - let (stray_blocks_second_epoch, stray_states_second_epoch, _, stray_head, _) = harness - .add_stray_blocks( + let stray_slots_second_epoch: Vec = (rig.epoch_start_slot(2) + ..=rig.epoch_start_slot(2) + 1) + .map(Into::into) + .collect(); + let (stray_blocks_second_epoch, stray_states_second_epoch, stray_head, _) = rig + .add_attested_blocks_at_slots( stray_state, - stray_slot, - slots_per_epoch - 1, - &faulty_validators, + &stray_slots_second_epoch, + &adversarial_validators, ); // Precondition: Ensure all stray_blocks blocks are still known @@ -1058,235 +1100,292 @@ fn prunes_fork_running_past_finalized_checkpoint() { .collect(); for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_some(), + rig.block_exists(block_hash), "stray block {} should be still present", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_some(), + rig.hot_state_exists(state_hash), "stray state {} at slot {} should be still present", state_hash, slot ); } - // Precondition: Only genesis is finalized - let chain_dump = harness.chain.chain_dump().unwrap(); - assert_eq!( - get_finalized_epoch_boundary_blocks(&chain_dump), - vec![Hash256::zero().into()].into_iter().collect(), - ); + // Precondition: Nothing is finalized yet + assert_eq!(rig.get_finalized_checkpoints(), hashset! {},); - assert!(harness.chain.knows_head(&stray_head)); + assert!(rig.chain.knows_head(&stray_head)); // Trigger finalization - let (canonical_blocks_second_epoch, _, _, _, _) = harness.add_canonical_chain_blocks( - canonical_state, - canonical_slot, - slots_per_epoch * 6, - &honest_validators, - ); - assert_ne!( - harness - .chain - .head() - .unwrap() - .beacon_state - .finalized_checkpoint - .epoch, - 0, - "chain should have finalized" - ); + let canonical_slots: Vec = (rig.epoch_start_slot(2)..=rig.epoch_start_slot(6)) + .map(Into::into) + .collect(); + let (canonical_blocks, _, _, _) = + rig.add_attested_blocks_at_slots(canonical_state, &canonical_slots, &honest_validators); // Postconditions let canonical_blocks: HashMap = canonical_blocks_zeroth_epoch .into_iter() .chain(canonical_blocks_first_epoch.into_iter()) - .chain(canonical_blocks_second_epoch.into_iter()) + .chain(canonical_blocks.into_iter()) .collect(); // Postcondition: New blocks got finalized - let chain_dump = harness.chain.chain_dump().unwrap(); - let finalized_blocks = get_finalized_epoch_boundary_blocks(&chain_dump); assert_eq!( - finalized_blocks, - vec![ - Hash256::zero().into(), - canonical_blocks[&Slot::new(slots_per_epoch as u64 * 3)], - canonical_blocks[&Slot::new(slots_per_epoch as u64 * 4)], - ] - .into_iter() - .collect() + rig.get_finalized_checkpoints(), + hashset! { + canonical_blocks[&rig.epoch_start_slot(1).into()], + canonical_blocks[&rig.epoch_start_slot(2).into()], + }, ); // Postcondition: Ensure all stray_blocks blocks have been pruned for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_none(), + !rig.block_exists(block_hash), "abandoned block {} should have been pruned", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_none(), - "stray state {} at slot {} should have been deleted", + !rig.hot_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", + state_hash, + slot + ); + assert!( + !rig.cold_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", state_hash, slot ); } - assert!(!harness.chain.knows_head(&stray_head)); + assert!(!rig.chain.knows_head(&stray_head)); } // This is to check if state outside of normal block processing are pruned correctly. #[test] fn prunes_skipped_slots_states() { - const VALIDATOR_COUNT: usize = 24; - const VALIDATOR_SUPERMAJORITY: usize = (VALIDATOR_COUNT / 3) * 2; - let db_path = tempdir().unwrap(); - let store = get_store(&db_path); - let harness = get_harness(Arc::clone(&store), VALIDATOR_COUNT); - const HONEST_VALIDATOR_COUNT: usize = VALIDATOR_SUPERMAJORITY; + const HONEST_VALIDATOR_COUNT: usize = 16 + 0; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0; + const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; + let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); - let faulty_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let slots_per_epoch: usize = MinimalEthSpec::slots_per_epoch() as usize; + let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); + let mut rig = BeaconChainHarness::new(MinimalEthSpec, validators_keypairs); + let state = rig.get_current_state(); - // Arrange skipped slots so as to cross the epoch boundary. That way, we excercise the code - // responsible for storing state outside of normal block processing. + let canonical_slots_zeroth_epoch: Vec = + (1..=rig.epoch_start_slot(1)).map(Into::into).collect(); + let (canonical_blocks_zeroth_epoch, _, _, canonical_state) = rig.add_attested_blocks_at_slots( + state.clone(), + &canonical_slots_zeroth_epoch, + &honest_validators, + ); - let canonical_slot = harness.get_chain_slot(); - let canonical_state = harness.get_head_state(); - let (canonical_blocks_zeroth_epoch, _, canonical_slot, _, canonical_state) = harness - .add_canonical_chain_blocks( - canonical_state, - canonical_slot, - slots_per_epoch - 1, - &honest_validators, - ); + let skipped_slot: Slot = (rig.epoch_start_slot(1) + 1).into(); - let (stray_blocks, stray_states, stray_slot, _, _) = harness.add_stray_blocks( + let stray_slots: Vec = ((skipped_slot + 1).into()..rig.epoch_start_slot(2)) + .map(Into::into) + .collect(); + let (stray_blocks, stray_states, _, stray_state) = rig.add_attested_blocks_at_slots( canonical_state.clone(), - canonical_slot, - slots_per_epoch, - &faulty_validators, + &stray_slots, + &adversarial_validators, ); // Preconditions for &block_hash in stray_blocks.values() { - let block = harness.chain.get_block(&block_hash.into()).unwrap(); assert!( - block.is_some(), + rig.block_exists(block_hash), "stray block {} should be still present", block_hash ); } for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_some(), + rig.hot_state_exists(state_hash), "stray state {} at slot {} should be still present", state_hash, slot ); } - let chain_dump = harness.chain.chain_dump().unwrap(); - assert_eq!( - get_finalized_epoch_boundary_blocks(&chain_dump), - vec![Hash256::zero().into()].into_iter().collect(), - ); + assert_eq!(rig.get_finalized_checkpoints(), hashset! {},); // Make sure slots were skipped - let stray_state = harness - .chain - .state_at_slot(stray_slot, StateSkipConfig::WithoutStateRoots) - .unwrap(); - let block_root = stray_state.get_block_root(canonical_slot - 1); - assert_eq!(stray_state.get_block_root(canonical_slot), block_root); - assert_eq!(stray_state.get_block_root(canonical_slot + 1), block_root); - - let skipped_slots = vec![canonical_slot, canonical_slot + 1]; - for &slot in &skipped_slots { - assert_eq!(stray_state.get_block_root(slot), block_root); - let state_hash = stray_state.get_state_root(slot).unwrap(); + assert!(rig.is_skipped_slot(&stray_state, skipped_slot)); + { + let state_hash = (*stray_state.get_state_root(skipped_slot).unwrap()).into(); assert!( - harness - .chain - .get_state(&state_hash, Some(slot)) - .unwrap() - .is_some(), - "skipped slots state should be still present" + rig.hot_state_exists(state_hash), + "skipped slot state {} should be still present", + state_hash ); } // Trigger finalization - let (canonical_blocks_post_finalization, _, _, _, _) = harness.add_canonical_chain_blocks( - canonical_state, - canonical_slot, - slots_per_epoch * 6, - &honest_validators, - ); - assert_eq!( - harness - .chain - .head() - .unwrap() - .beacon_state - .finalized_checkpoint - .epoch, - 2, - "chain should have finalized" - ); + let canonical_slots: Vec = ((skipped_slot + 1).into()..rig.epoch_start_slot(7)) + .map(Into::into) + .collect(); + let (canonical_blocks_post_finalization, _, _, _) = + rig.add_attested_blocks_at_slots(canonical_state, &canonical_slots, &honest_validators); // Postconditions - let chain_dump = harness.chain.chain_dump().unwrap(); - let finalized_blocks = get_finalized_epoch_boundary_blocks(&chain_dump); let canonical_blocks: HashMap = canonical_blocks_zeroth_epoch .into_iter() .chain(canonical_blocks_post_finalization.into_iter()) .collect(); assert_eq!( - finalized_blocks, - vec![ - Hash256::zero().into(), - canonical_blocks[&Slot::new(slots_per_epoch as u64 * 2)], - ] - .into_iter() - .collect() + rig.get_finalized_checkpoints(), + hashset! { + canonical_blocks[&rig.epoch_start_slot(1).into()], + canonical_blocks[&rig.epoch_start_slot(2).into()], + }, ); for (&slot, &state_hash) in &stray_states { - let state = harness.chain.get_state(&state_hash.into(), None).unwrap(); assert!( - state.is_none(), - "stray state {} at slot {} should have been deleted", + !rig.hot_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", + state_hash, + slot + ); + assert!( + !rig.cold_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", state_hash, slot ); } - for &slot in &skipped_slots { - assert_eq!(stray_state.get_block_root(slot), block_root); - let state_hash = stray_state.get_state_root(slot).unwrap(); + assert!(rig.is_skipped_slot(&stray_state, skipped_slot)); + { + let state_hash: BeaconStateHash = + (*stray_state.get_state_root(skipped_slot).unwrap()).into(); assert!( - harness - .chain - .get_state(&state_hash, None) - .unwrap() - .is_none(), + !rig.hot_state_exists(state_hash), "skipped slot {} state {} should have been pruned", - slot, + skipped_slot, + state_hash + ); + } +} + +// This is to check if state outside of normal block processing are pruned correctly. +#[test] +fn finalizes_non_epoch_start_slot() { + const HONEST_VALIDATOR_COUNT: usize = 16 + 0; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0; + const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; + let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); + let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); + let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); + let mut rig = BeaconChainHarness::new(MinimalEthSpec, validators_keypairs); + let state = rig.get_current_state(); + + let canonical_slots_zeroth_epoch: Vec = + (1..rig.epoch_start_slot(1)).map(Into::into).collect(); + let (canonical_blocks_zeroth_epoch, _, _, canonical_state) = rig.add_attested_blocks_at_slots( + state.clone(), + &canonical_slots_zeroth_epoch, + &honest_validators, + ); + + let skipped_slot: Slot = rig.epoch_start_slot(1).into(); + + let stray_slots: Vec = ((skipped_slot + 1).into()..rig.epoch_start_slot(2)) + .map(Into::into) + .collect(); + let (stray_blocks, stray_states, _, stray_state) = rig.add_attested_blocks_at_slots( + canonical_state.clone(), + &stray_slots, + &adversarial_validators, + ); + + // Preconditions + for &block_hash in stray_blocks.values() { + assert!( + rig.block_exists(block_hash), + "stray block {} should be still present", + block_hash + ); + } + + for (&slot, &state_hash) in &stray_states { + assert!( + rig.hot_state_exists(state_hash), + "stray state {} at slot {} should be still present", + state_hash, + slot + ); + } + + assert_eq!(rig.get_finalized_checkpoints(), hashset! {}); + + // Make sure slots were skipped + assert!(rig.is_skipped_slot(&stray_state, skipped_slot)); + { + let state_hash = (*stray_state.get_state_root(skipped_slot).unwrap()).into(); + assert!( + rig.hot_state_exists(state_hash), + "skipped slot state {} should be still present", + state_hash + ); + } + + // Trigger finalization + let canonical_slots: Vec = ((skipped_slot + 1).into()..rig.epoch_start_slot(7)) + .map(Into::into) + .collect(); + let (canonical_blocks_post_finalization, _, _, _) = + rig.add_attested_blocks_at_slots(canonical_state, &canonical_slots, &honest_validators); + + // Postconditions + let canonical_blocks: HashMap = canonical_blocks_zeroth_epoch + .into_iter() + .chain(canonical_blocks_post_finalization.into_iter()) + .collect(); + assert_eq!( + rig.get_finalized_checkpoints(), + hashset! { + canonical_blocks[&(rig.epoch_start_slot(1)-1).into()], + canonical_blocks[&rig.epoch_start_slot(2).into()], + }, + ); + + for (&slot, &state_hash) in &stray_states { + assert!( + !rig.hot_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", + state_hash, + slot + ); + assert!( + !rig.cold_state_exists(state_hash), + "stray state {} at slot {} should have been pruned", + state_hash, + slot + ); + } + + assert!(rig.is_skipped_slot(&stray_state, skipped_slot)); + { + let state_hash: BeaconStateHash = + (*stray_state.get_state_root(skipped_slot).unwrap()).into(); + assert!( + !rig.hot_state_exists(state_hash), + "skipped slot {} state {} should have been pruned", + skipped_slot, state_hash ); } @@ -1355,17 +1454,17 @@ fn check_no_blocks_exist<'a>( #[test] fn prune_single_block_fork() { - let slots_per_epoch = E::slots_per_epoch() as usize; + let slots_per_epoch = E::slots_per_epoch(); pruning_test(3 * slots_per_epoch, 1, slots_per_epoch, 0, 1); } #[test] fn prune_single_block_long_skip() { - let slots_per_epoch = E::slots_per_epoch() as usize; + let slots_per_epoch = E::slots_per_epoch(); pruning_test( 2 * slots_per_epoch, 1, - slots_per_epoch, + 2 * slots_per_epoch, 2 * slots_per_epoch as u64, 1, ); @@ -1373,7 +1472,7 @@ fn prune_single_block_long_skip() { #[test] fn prune_shared_skip_states_mid_epoch() { - let slots_per_epoch = E::slots_per_epoch() as usize; + let slots_per_epoch = E::slots_per_epoch(); pruning_test( slots_per_epoch + slots_per_epoch / 2, 1, @@ -1385,7 +1484,7 @@ fn prune_shared_skip_states_mid_epoch() { #[test] fn prune_shared_skip_states_epoch_boundaries() { - let slots_per_epoch = E::slots_per_epoch() as usize; + let slots_per_epoch = E::slots_per_epoch(); pruning_test(slots_per_epoch - 1, 1, slots_per_epoch, 2, slots_per_epoch); pruning_test(slots_per_epoch - 1, 2, slots_per_epoch, 1, slots_per_epoch); pruning_test( @@ -1414,16 +1513,16 @@ fn prune_shared_skip_states_epoch_boundaries() { /// Generic harness for pruning tests. fn pruning_test( // Number of blocks to start the chain with before forking. - num_initial_blocks: usize, + num_initial_blocks: u64, // Number of skip slots on the main chain after the initial blocks. num_canonical_skips: u64, // Number of blocks on the main chain after the skip, but before the finalisation-triggering // blocks. - num_canonical_middle_blocks: usize, + num_canonical_middle_blocks: u64, // Number of skip slots on the fork chain after the initial blocks. num_fork_skips: u64, // Number of blocks on the fork chain after the skips. - num_fork_blocks: usize, + num_fork_blocks: u64, ) { const VALIDATOR_COUNT: usize = 24; const VALIDATOR_SUPERMAJORITY: usize = (VALIDATOR_COUNT / 3) * 2; @@ -1431,33 +1530,46 @@ fn pruning_test( let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let harness = get_harness(store.clone(), VALIDATOR_COUNT); + let mut harness = get_harness(store.clone(), VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let faulty_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize; - let (_, _, divergence_slot, _, divergence_state) = harness.add_blocks( - harness.get_head_state(), - harness.get_chain_slot(), - num_initial_blocks, + let slots = |start: Slot, num_blocks: u64| -> Vec { + (start.as_u64()..start.as_u64() + num_blocks) + .map(Slot::new) + .collect() + }; + + let start_slot = Slot::new(1); + let divergence_slot = start_slot + num_initial_blocks; + let (_, _, _, divergence_state) = harness.add_attested_blocks_at_slots( + harness.get_current_state(), + &slots(start_slot, num_initial_blocks)[..], &honest_validators, ); - let (_, _, canonical_slot, _, canonical_state) = harness.add_blocks( - divergence_state.clone(), - divergence_slot + num_canonical_skips, - num_canonical_middle_blocks, - &honest_validators, - ); + let mut chains = harness.add_blocks_on_multiple_chains(vec![ + // Canonical chain + ( + divergence_state.clone(), + slots( + divergence_slot + num_canonical_skips, + num_canonical_middle_blocks, + ), + honest_validators.clone(), + ), + // Fork chain + ( + divergence_state.clone(), + slots(divergence_slot + num_fork_skips, num_fork_blocks), + faulty_validators, + ), + ]); + let (_, _, _, canonical_state) = chains.remove(0); + let (stray_blocks, stray_states, _, stray_head_state) = chains.remove(0); - let (stray_blocks, stray_states, stray_slot, _, stray_head_state) = harness.add_blocks( - divergence_state.clone(), - divergence_slot + num_fork_skips, - num_fork_blocks, - &faulty_validators, - ); - - let stray_head_state_root = stray_states[&(stray_slot - 1)]; + let stray_head_slot = divergence_slot + num_fork_skips + num_fork_blocks - 1; + let stray_head_state_root = stray_states[&stray_head_slot]; let stray_states = harness .chain .rev_iter_state_roots_from(stray_head_state_root.into(), &stray_head_state) @@ -1475,11 +1587,11 @@ fn pruning_test( ); // Trigger finalization - let num_finalization_blocks = 4 * slots_per_epoch; - let (_, _, _, _, _) = harness.add_blocks( + let num_finalization_blocks = 4 * E::slots_per_epoch(); + let canonical_slot = divergence_slot + num_canonical_skips + num_canonical_middle_blocks; + harness.add_attested_blocks_at_slots( canonical_state, - canonical_slot, - num_finalization_blocks, + &slots(canonical_slot, num_finalization_blocks), &honest_validators, ); diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs index d07b20d2f..12f1c4364 100644 --- a/beacon_node/beacon_chain/tests/tests.rs +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -6,7 +6,8 @@ extern crate lazy_static; use beacon_chain::{ attestation_verification::Error as AttnError, test_utils::{ - AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType, OP_POOL_DB_KEY, + AttestationStrategy, BeaconChainHarness, BlockStrategy, NullMigratorEphemeralHarnessType, + OP_POOL_DB_KEY, }, }; use operation_pool::PersistedOperationPool; @@ -24,8 +25,10 @@ lazy_static! { static ref KEYPAIRS: Vec = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); } -fn get_harness(validator_count: usize) -> BeaconChainHarness> { - let harness = BeaconChainHarness::new( +fn get_harness( + validator_count: usize, +) -> BeaconChainHarness> { + let harness = BeaconChainHarness::new_with_store_config( MinimalEthSpec, KEYPAIRS[0..validator_count].to_vec(), StoreConfig::default(), @@ -64,7 +67,7 @@ fn massive_skips() { fn iterators() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 2 - 1; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); harness.extend_chain( num_blocks_produced as usize, @@ -139,7 +142,7 @@ fn iterators() { #[test] fn chooses_fork() { - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); let two_thirds = (VALIDATOR_COUNT / 3) * 2; let delay = MinimalEthSpec::default_spec().min_attestation_inclusion_delay as usize; @@ -190,7 +193,7 @@ fn chooses_fork() { fn finalizes_with_full_participation() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); harness.extend_chain( num_blocks_produced as usize, @@ -225,7 +228,7 @@ fn finalizes_with_full_participation() { fn finalizes_with_two_thirds_participation() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); let two_thirds = (VALIDATOR_COUNT / 3) * 2; let attesters = (0..two_thirds).collect(); @@ -268,7 +271,7 @@ fn finalizes_with_two_thirds_participation() { fn does_not_finalize_with_less_than_two_thirds_participation() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); let two_thirds = (VALIDATOR_COUNT / 3) * 2; let less_than_two_thirds = two_thirds - 1; @@ -305,7 +308,7 @@ fn does_not_finalize_with_less_than_two_thirds_participation() { fn does_not_finalize_without_attestation() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); harness.extend_chain( num_blocks_produced as usize, @@ -338,7 +341,7 @@ fn does_not_finalize_without_attestation() { fn roundtrip_operation_pool() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); // Add some attestations harness.extend_chain( @@ -370,7 +373,7 @@ fn roundtrip_operation_pool() { fn unaggregated_attestations_added_to_fork_choice_some_none() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() / 2; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); harness.extend_chain( num_blocks_produced as usize, @@ -424,7 +427,7 @@ fn unaggregated_attestations_added_to_fork_choice_some_none() { fn attestations_with_increasing_slots() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 5; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); let mut attestations = vec![]; @@ -486,7 +489,7 @@ fn attestations_with_increasing_slots() { fn unaggregated_attestations_added_to_fork_choice_all_updated() { let num_blocks_produced = MinimalEthSpec::slots_per_epoch() * 2 - 1; - let harness = get_harness(VALIDATOR_COUNT); + let mut harness = get_harness(VALIDATOR_COUNT); harness.extend_chain( num_blocks_produced as usize, @@ -541,7 +544,7 @@ fn unaggregated_attestations_added_to_fork_choice_all_updated() { fn run_skip_slot_test(skip_slots: u64) { let num_validators = 8; - let harness_a = get_harness(num_validators); + let mut harness_a = get_harness(num_validators); let harness_b = get_harness(num_validators); for _ in 0..skip_slots { diff --git a/beacon_node/network/src/service/tests.rs b/beacon_node/network/src/service/tests.rs index 8658a6324..2efcf889e 100644 --- a/beacon_node/network/src/service/tests.rs +++ b/beacon_node/network/src/service/tests.rs @@ -23,7 +23,7 @@ mod tests { let log = get_logger(); let beacon_chain = Arc::new( - BeaconChainHarness::new( + BeaconChainHarness::new_with_store_config( MinimalEthSpec, generate_deterministic_keypairs(8), StoreConfig::default(), diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 3378d43da..08e810866 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -825,7 +825,7 @@ impl, Cold: ItemStore> HotColdDB } /// Advance the split point of the store, moving new finalized states to the freezer. -pub fn process_finalization, Cold: ItemStore>( +pub fn migrate_database, Cold: ItemStore>( store: Arc>, frozen_head_root: Hash256, frozen_head: &BeaconState, diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 02e314dc6..271870226 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -27,7 +27,7 @@ pub mod iter; use std::borrow::Cow; pub use self::config::StoreConfig; -pub use self::hot_cold_store::{HotColdDB, HotStateSummary, Split}; +pub use self::hot_cold_store::{BlockReplay, HotColdDB, HotStateSummary, Split}; pub use self::leveldb_store::LevelDB; pub use self::memory_store::MemoryStore; pub use self::partial_beacon_state::PartialBeaconState; diff --git a/consensus/fork_choice/tests/tests.rs b/consensus/fork_choice/tests/tests.rs index 7897c01a4..d8b71243b 100644 --- a/consensus/fork_choice/tests/tests.rs +++ b/consensus/fork_choice/tests/tests.rs @@ -1,8 +1,10 @@ #![cfg(not(debug_assertions))] use beacon_chain::{ - test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, HarnessType}, - BeaconChain, BeaconChainError, BeaconForkChoiceStore, ForkChoiceError, + test_utils::{ + AttestationStrategy, BeaconChainHarness, BlockStrategy, NullMigratorEphemeralHarnessType, + }, + BeaconChain, BeaconChainError, BeaconForkChoiceStore, ForkChoiceError, StateSkipConfig, }; use fork_choice::{ ForkChoiceStore, InvalidAttestation, InvalidBlock, QueuedAttestation, @@ -18,7 +20,7 @@ use types::{BeaconBlock, BeaconState, Hash256, SignedBeaconBlock}; pub type E = MainnetEthSpec; -pub const VALIDATOR_COUNT: usize = 16; +pub const VALIDATOR_COUNT: usize = 32; /// Defines some delay between when an attestation is created and when it is mutated. pub enum MutationDelay { @@ -30,7 +32,7 @@ pub enum MutationDelay { /// A helper struct to make testing fork choice more ergonomic and less repetitive. struct ForkChoiceTest { - harness: BeaconChainHarness>, + harness: BeaconChainHarness>, } impl ForkChoiceTest { @@ -115,22 +117,31 @@ impl ForkChoiceTest { } /// Build the chain whilst `predicate` returns `true`. - pub fn apply_blocks_while(self, mut predicate: F) -> Self + pub fn apply_blocks_while(mut self, mut predicate: F) -> Self where F: FnMut(&BeaconBlock, &BeaconState) -> bool, { self.harness.advance_slot(); - self.harness.extend_chain_while( - |block, state| predicate(&block.message, state), - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, - ); + let mut state = self.harness.get_current_state(); + let validators = self.harness.get_all_validators(); + loop { + let slot = self.harness.get_current_slot(); + let (block, state_) = self.harness.make_block(state, slot); + state = state_; + if !predicate(&block.message, &state) { + break; + } + let block_hash = self.harness.process_block(slot, block.clone()); + self.harness + .attest_block(&state, block_hash, &block, &validators); + self.harness.advance_slot(); + } self } /// Apply `count` blocks to the chain (with attestations). - pub fn apply_blocks(self, count: usize) -> Self { + pub fn apply_blocks(mut self, count: usize) -> Self { self.harness.advance_slot(); self.harness.extend_chain( count, @@ -142,7 +153,7 @@ impl ForkChoiceTest { } /// Apply `count` blocks to the chain (without attestations). - pub fn apply_blocks_without_new_attestations(self, count: usize) -> Self { + pub fn apply_blocks_without_new_attestations(mut self, count: usize) -> Self { self.harness.advance_slot(); self.harness.extend_chain( count, @@ -181,13 +192,22 @@ impl ForkChoiceTest { /// Applies a block directly to fork choice, bypassing the beacon chain. /// /// Asserts the block was applied successfully. - pub fn apply_block_directly_to_fork_choice(self, mut func: F) -> Self + pub fn apply_block_directly_to_fork_choice(mut self, mut func: F) -> Self where F: FnMut(&mut BeaconBlock, &mut BeaconState), { - let (mut block, mut state) = self.harness.get_block(); + let state = self + .harness + .chain + .state_at_slot( + self.harness.get_current_slot() - 1, + StateSkipConfig::WithStateRoots, + ) + .unwrap(); + let slot = self.harness.get_current_slot(); + let (mut block, mut state) = self.harness.make_block(state, slot); func(&mut block.message, &mut state); - let current_slot = self.harness.chain.slot().unwrap(); + let current_slot = self.harness.get_current_slot(); self.harness .chain .fork_choice @@ -201,7 +221,7 @@ impl ForkChoiceTest { /// /// Asserts that an error occurred and allows inspecting it via `comparison_func`. pub fn apply_invalid_block_directly_to_fork_choice( - self, + mut self, mut mutation_func: F, mut comparison_func: G, ) -> Self @@ -209,9 +229,18 @@ impl ForkChoiceTest { F: FnMut(&mut BeaconBlock, &mut BeaconState), G: FnMut(ForkChoiceError), { - let (mut block, mut state) = self.harness.get_block(); + let state = self + .harness + .chain + .state_at_slot( + self.harness.get_current_slot() - 1, + StateSkipConfig::WithStateRoots, + ) + .unwrap(); + let slot = self.harness.get_current_slot(); + let (mut block, mut state) = self.harness.make_block(state, slot); mutation_func(&mut block.message, &mut state); - let current_slot = self.harness.chain.slot().unwrap(); + let current_slot = self.harness.get_current_slot(); let err = self .harness .chain @@ -267,20 +296,21 @@ impl ForkChoiceTest { /// /// Also returns some info about who created it. fn apply_attestation_to_chain( - self, + mut self, delay: MutationDelay, mut mutation_func: F, mut comparison_func: G, ) -> Self where - F: FnMut(&mut IndexedAttestation, &BeaconChain>), + F: FnMut(&mut IndexedAttestation, &BeaconChain>), G: FnMut(Result<(), BeaconChainError>), { - let chain = &self.harness.chain; - let head = chain.head().expect("should get head"); - let current_slot = chain.slot().expect("should get slot"); + let head = self.harness.chain.head().expect("should get head"); + let current_slot = self.harness.chain.slot().expect("should get slot"); - let mut attestation = chain + let mut attestation = self + .harness + .chain .produce_unaggregated_attestation(current_slot, 0) .expect("should not error while producing attestation"); @@ -298,9 +328,13 @@ impl ForkChoiceTest { .get_committee_count_at_slot(current_slot) .expect("should not error while getting committee count"); - let subnet_id = - SubnetId::compute_subnet::(current_slot, 0, committee_count, &chain.spec) - .expect("should compute subnet id"); + let subnet_id = SubnetId::compute_subnet::( + current_slot, + 0, + committee_count, + &self.harness.chain.spec, + ) + .expect("should compute subnet id"); let validator_sk = generate_deterministic_keypair(validator_index).sk; @@ -309,12 +343,14 @@ impl ForkChoiceTest { &validator_sk, validator_committee_index, &head.beacon_state.fork, - chain.genesis_validators_root, - &chain.spec, + self.harness.chain.genesis_validators_root, + &self.harness.chain.spec, ) .expect("should sign attestation"); - let mut verified_attestation = chain + let mut verified_attestation = self + .harness + .chain .verify_unaggregated_attestation_for_gossip(attestation, subnet_id) .expect("precondition: should gossip verify attestation"); @@ -327,9 +363,15 @@ impl ForkChoiceTest { ); } - mutation_func(verified_attestation.__indexed_attestation_mut(), chain); + mutation_func( + verified_attestation.__indexed_attestation_mut(), + &self.harness.chain, + ); - let result = chain.apply_attestation_to_fork_choice(&verified_attestation); + let result = self + .harness + .chain + .apply_attestation_to_fork_choice(&verified_attestation); comparison_func(result);