From 6fd3a1a03e0cb272409b1c7fda2e06e2ffdc4772 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 24 Jan 2019 17:05:48 +1100 Subject: [PATCH] Add progress on test rig --- .../beacon_chain/src/attestation_targets.rs | 50 +++++++++ beacon_node/beacon_chain/src/block_graph.rs | 37 +++++++ .../beacon_chain/src/block_processing.rs | 22 ++-- .../beacon_chain/src/block_production.rs | 16 +-- .../beacon_chain/src/canonical_head.rs | 34 ++++++ beacon_node/beacon_chain/src/info.rs | 49 +++++++++ beacon_node/beacon_chain/src/lib.rs | 96 ++++++++++++----- beacon_node/beacon_chain/src/lmd_ghost.rs | 16 +-- beacon_node/beacon_chain/tests/chain.rs | 101 +++--------------- .../tests/utils/direct_beacon_node.rs | 58 ++++++++++ .../beacon_chain/tests/utils/direct_duties.rs | 37 +++++++ beacon_node/beacon_chain/tests/utils/mod.rs | 9 ++ .../beacon_chain/tests/utils/test_rig.rs | 85 +++++++++++++++ .../beacon_chain/tests/utils/validator.rs | 61 +++++++++++ eth2/block_producer/Cargo.toml | 1 + eth2/block_producer/src/lib.rs | 26 ++++- .../src/test_utils/beacon_node.rs | 26 ++++- eth2/block_producer/src/traits.rs | 13 ++- eth2/spec/src/lib.rs | 2 +- 19 files changed, 595 insertions(+), 144 deletions(-) create mode 100644 beacon_node/beacon_chain/src/attestation_targets.rs create mode 100644 beacon_node/beacon_chain/src/block_graph.rs create mode 100644 beacon_node/beacon_chain/src/canonical_head.rs create mode 100644 beacon_node/beacon_chain/src/info.rs create mode 100644 beacon_node/beacon_chain/tests/utils/direct_beacon_node.rs create mode 100644 beacon_node/beacon_chain/tests/utils/direct_duties.rs create mode 100644 beacon_node/beacon_chain/tests/utils/mod.rs create mode 100644 beacon_node/beacon_chain/tests/utils/test_rig.rs create mode 100644 beacon_node/beacon_chain/tests/utils/validator.rs diff --git a/beacon_node/beacon_chain/src/attestation_targets.rs b/beacon_node/beacon_chain/src/attestation_targets.rs new file mode 100644 index 0000000000..6971d5b4eb --- /dev/null +++ b/beacon_node/beacon_chain/src/attestation_targets.rs @@ -0,0 +1,50 @@ +use crate::{BeaconChain, CheckPoint, ClientDB, SlotClock}; +use std::collections::HashMap; +use std::sync::RwLockReadGuard; +use types::{BeaconBlock, BeaconState, Hash256}; + +pub struct AttestationTargets { + map: HashMap, +} + +impl AttestationTargets { + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + pub fn get(&self, validator_index: u64) -> Option<&Hash256> { + self.map.get(&validator_index) + } + + pub fn insert(&mut self, validator_index: u64, block_hash: Hash256) -> Option { + self.map.insert(validator_index, block_hash) + } +} + +impl BeaconChain +where + T: ClientDB, + U: SlotClock, +{ + pub fn insert_latest_attestation_target(&self, validator_index: u64, block_root: Hash256) { + let mut targets = self + .latest_attestation_targets + .write() + .expect("CRITICAL: CanonicalHead poisioned."); + targets.insert(validator_index, block_root); + } + + pub fn get_latest_attestation_target(&self, validator_index: u64) -> Option { + let targets = self + .latest_attestation_targets + .read() + .expect("CRITICAL: CanonicalHead poisioned."); + + match targets.get(validator_index) { + Some(hash) => Some(hash.clone()), + None => None, + } + } +} diff --git a/beacon_node/beacon_chain/src/block_graph.rs b/beacon_node/beacon_chain/src/block_graph.rs new file mode 100644 index 0000000000..8a0f6e61d9 --- /dev/null +++ b/beacon_node/beacon_chain/src/block_graph.rs @@ -0,0 +1,37 @@ +use crate::{BeaconChain, CheckPoint, ClientDB, SlotClock}; +use std::collections::HashSet; +use std::sync::{RwLock, RwLockReadGuard}; +use types::{BeaconBlock, BeaconState, Hash256}; + +pub struct BlockGraph { + pub leaves: RwLock>, +} + +impl BlockGraph { + pub fn new() -> Self { + Self { + leaves: RwLock::new(HashSet::new()), + } + } + /// Add a new leaf to the block hash graph. Returns `true` if the leaf was built upon another + /// leaf. + pub fn add_leaf(&self, parent: &Hash256, leaf: Hash256) -> bool { + let mut leaves = self + .leaves + .write() + .expect("CRITICAL: BlockGraph poisioned."); + + if leaves.contains(parent) { + leaves.remove(parent); + leaves.insert(leaf); + true + } else { + leaves.insert(leaf); + false + } + } + + pub fn leaves(&self) -> RwLockReadGuard> { + self.leaves.read().expect("CRITICAL: BlockGraph poisioned.") + } +} diff --git a/beacon_node/beacon_chain/src/block_processing.rs b/beacon_node/beacon_chain/src/block_processing.rs index 6c534bbce9..f99958dfbd 100644 --- a/beacon_node/beacon_chain/src/block_processing.rs +++ b/beacon_node/beacon_chain/src/block_processing.rs @@ -54,7 +54,7 @@ where U: SlotClock, Error: From<::Error>, { - pub fn process_block(&mut self, block: V) -> Result + pub fn process_block(&self, block: V) -> Result where V: BeaconBlockReader + Encodable + Sized, { @@ -103,14 +103,20 @@ where self.block_store.put(&block_root, &ssz_encode(&block)[..])?; self.state_store.put(&state_root, &ssz_encode(&state)[..])?; - // Update leaf blocks so the implementation can track the chain heads. - if self.leaf_blocks.contains(&block.parent_root()) { - self.leaf_blocks.remove(&block.parent_root()); + self.block_graph + .add_leaf(&parent_block_root, block_root.clone()); + + // If the parent block was the parent_block, automatically update the canonical head. + // + // TODO: this is a first-in-best-dressed scenario that is not ideal -- find a solution. + if self.canonical_head().beacon_block_root == parent_block_root { + self.update_canonical_head( + block.clone(), + block_root.clone(), + state.clone(), + state_root.clone(), + ); } - if self.canonical_leaf_block == block.parent_root() { - self.canonical_leaf_block = block_root; - } - self.leaf_blocks.insert(block_root); // The block was sucessfully processed. Ok(Outcome::Processed) diff --git a/beacon_node/beacon_chain/src/block_production.rs b/beacon_node/beacon_chain/src/block_production.rs index 50524410f7..c0a057ad26 100644 --- a/beacon_node/beacon_chain/src/block_production.rs +++ b/beacon_node/beacon_chain/src/block_production.rs @@ -18,18 +18,22 @@ impl BeaconChain where T: ClientDB, U: SlotClock, - Error: From<::Error>, { pub fn produce_block( - &mut self, + &self, randao_reveal: Signature, - ) -> Result<(BeaconBlock, BeaconState), Error> { + ) -> Result<(BeaconBlock, BeaconState), Error> + where + Error: From<::Error>, + { + // TODO: allow producing a block from a previous (or future?) slot. let present_slot = self .slot_clock - .present_slot()? + .present_slot() + .map_err(|e| e.into())? .ok_or(Error::PresentSlotIsNone)?; - let parent_root = self.canonical_leaf_block; + let parent_root = self.canonical_head().beacon_block_root; let parent_block_reader = self .block_store .get_reader(&parent_root)? @@ -43,7 +47,7 @@ where let mut block = BeaconBlock { slot: present_slot, - parent_root, + parent_root: parent_root.clone(), state_root: Hash256::zero(), // Updated after the state is calculated. randao_reveal: randao_reveal, eth1_data: Eth1Data { diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs new file mode 100644 index 0000000000..acccb5de74 --- /dev/null +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -0,0 +1,34 @@ +use crate::{BeaconChain, CheckPoint, ClientDB, SlotClock}; +use std::sync::RwLockReadGuard; +use types::{BeaconBlock, BeaconState, Hash256}; + +impl BeaconChain +where + T: ClientDB, + U: SlotClock, +{ + pub fn update_canonical_head( + &self, + new_beacon_block: BeaconBlock, + new_beacon_block_root: Hash256, + new_beacon_state: BeaconState, + new_beacon_state_root: Hash256, + ) { + let mut canonical_head = self + .canonical_head + .write() + .expect("CRITICAL: CanonicalHead poisioned."); + canonical_head.update( + new_beacon_block, + new_beacon_block_root, + new_beacon_state, + new_beacon_state_root, + ); + } + + pub fn canonical_head(&self) -> RwLockReadGuard { + self.canonical_head + .read() + .expect("CRITICAL: CanonicalHead poisioned.") + } +} diff --git a/beacon_node/beacon_chain/src/info.rs b/beacon_node/beacon_chain/src/info.rs new file mode 100644 index 0000000000..2025ba27a1 --- /dev/null +++ b/beacon_node/beacon_chain/src/info.rs @@ -0,0 +1,49 @@ +use super::{BeaconChain, ClientDB, SlotClock}; +use types::PublicKey; + +impl BeaconChain +where + T: ClientDB, + U: SlotClock, +{ + pub fn validator_index(&self, pubkey: &PublicKey) -> Option { + for (i, validator) in self + .canonical_head() + .beacon_state + .validator_registry + .iter() + .enumerate() + { + if validator.pubkey == *pubkey { + return Some(i); + } + } + None + } + + pub fn proposer_slots(&self, validator_index: usize) -> Option { + if let Some(validator) = self + .canonical_head() + .beacon_state + .validator_registry + .get(validator_index) + { + Some(validator.proposer_slots) + } else { + None + } + } + + pub fn present_slot(&self) -> Option { + match self.slot_clock.present_slot() { + Ok(some_slot) => some_slot, + _ => None, + } + } + + pub fn block_proposer(&self, slot: u64) -> Option { + //TODO: this is a stub; fix. + let validator_count = self.canonical_head().beacon_state.validator_registry.len(); + Some((slot as usize) % validator_count) + } +} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 6c95fcaa4b..b52dfdd73e 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,8 +1,14 @@ +mod attestation_targets; +mod block_graph; mod block_processing; -mod block_production; +pub mod block_production; +mod canonical_head; +mod info; mod lmd_ghost; mod state_transition; +use self::attestation_targets::AttestationTargets; +use self::block_graph::BlockGraph; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, ClientDB, DBError, @@ -12,18 +18,11 @@ use slot_clock::SlotClock; use spec::ChainSpec; use ssz::ssz_encode; use std::collections::{HashMap, HashSet}; -use std::sync::Arc; -use types::Hash256; +use std::sync::{Arc, RwLock}; +use types::{BeaconBlock, BeaconState, Hash256, PublicKey}; pub use self::block_processing::Outcome as BlockProcessingOutcome; -#[derive(Debug, PartialEq)] -pub struct CheckPoint { - block_root: Hash256, - state_root: Hash256, - slot: u64, -} - #[derive(Debug, PartialEq)] pub enum BeaconChainError { InsufficientValidators, @@ -31,15 +30,51 @@ pub enum BeaconChainError { DBError(String), } +pub struct CheckPoint { + beacon_block: BeaconBlock, + beacon_block_root: Hash256, + beacon_state: BeaconState, + beacon_state_root: Hash256, +} + +impl CheckPoint { + pub fn new( + beacon_block: BeaconBlock, + beacon_block_root: Hash256, + beacon_state: BeaconState, + beacon_state_root: Hash256, + ) -> Self { + Self { + beacon_block, + beacon_block_root, + beacon_state, + beacon_state_root, + } + } + + pub fn update( + &mut self, + beacon_block: BeaconBlock, + beacon_block_root: Hash256, + beacon_state: BeaconState, + beacon_state_root: Hash256, + ) { + self.beacon_block = beacon_block; + self.beacon_block_root = beacon_block_root; + self.beacon_state = beacon_state; + self.beacon_state_root = beacon_state_root; + } +} + pub struct BeaconChain { pub block_store: Arc>, pub state_store: Arc>, pub slot_clock: U, - pub leaf_blocks: HashSet, - pub canonical_leaf_block: Hash256, + pub block_graph: BlockGraph, + canonical_head: RwLock, + finalized_head: RwLock, + pub latest_attestation_targets: RwLock, pub spec: ChainSpec, - latest_attestation_targets: HashMap, - finalized_checkpoint: CheckPoint, } impl BeaconChain @@ -65,24 +100,33 @@ where let block_root = genesis_block.canonical_root(); block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; - let mut leaf_blocks = HashSet::new(); - leaf_blocks.insert(block_root); + let block_graph = BlockGraph::new(); + block_graph.add_leaf(&Hash256::zero(), block_root.clone()); - let finalized_checkpoint = CheckPoint { - block_root, - state_root, - slot: genesis_block.slot, - }; + let finalized_head = RwLock::new(CheckPoint::new( + genesis_block.clone(), + block_root.clone(), + genesis_state.clone(), + state_root.clone(), + )); + let canonical_head = RwLock::new(CheckPoint::new( + genesis_block.clone(), + block_root.clone(), + genesis_state.clone(), + state_root.clone(), + )); + + let latest_attestation_targets = RwLock::new(AttestationTargets::new()); Ok(Self { block_store, state_store, slot_clock, - leaf_blocks, - canonical_leaf_block: block_root, - spec, - latest_attestation_targets: HashMap::new(), - finalized_checkpoint, + block_graph, + finalized_head, + canonical_head, + latest_attestation_targets, + spec: spec, }) } } diff --git a/beacon_node/beacon_chain/src/lmd_ghost.rs b/beacon_node/beacon_chain/src/lmd_ghost.rs index 7f00a9276e..e4aaa34835 100644 --- a/beacon_node/beacon_chain/src/lmd_ghost.rs +++ b/beacon_node/beacon_chain/src/lmd_ghost.rs @@ -4,13 +4,12 @@ use db::{ ClientDB, DBError, }; use slot_clock::TestingSlotClockError; -use ssz::{ssz_encode, Encodable}; use std::collections::HashSet; use std::sync::Arc; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, validator_registry::get_active_validator_indices, - BeaconBlock, Hash256, + Hash256, }; #[derive(Debug, PartialEq)] @@ -53,7 +52,7 @@ where let mut attestation_targets = Vec::with_capacity(active_validator_indices.len()); for i in active_validator_indices { - if let Some(target) = self.latest_attestation_targets.get(&i) { + if let Some(target) = self.get_latest_attestation_target(i as u64) { attestation_targets.push(target); } } @@ -62,8 +61,11 @@ where let mut head_vote_count = 0; loop { - let child_hashes_and_slots = - get_child_hashes_and_slots(&self.block_store, &head_hash, &self.leaf_blocks)?; + let child_hashes_and_slots = get_child_hashes_and_slots( + &self.block_store, + &head_hash, + &self.block_graph.leaves(), + )?; if child_hashes_and_slots.len() == 0 { break; @@ -90,7 +92,7 @@ where fn get_vote_count( block_store: &Arc>, - attestation_targets: &[&Hash256], + attestation_targets: &[Hash256], block_root: &Hash256, slot: u64, ) -> Result { @@ -99,7 +101,7 @@ fn get_vote_count( let (root_at_slot, _) = block_store .block_at_slot(&block_root, slot)? .ok_or(Error::MissingBeaconBlock(*block_root))?; - if root_at_slot == *block_root { + if root_at_slot == *target { count += 1; } } diff --git a/beacon_node/beacon_chain/tests/chain.rs b/beacon_node/beacon_chain/tests/chain.rs index 390d48ea41..3c0a9b8f92 100644 --- a/beacon_node/beacon_chain/tests/chain.rs +++ b/beacon_node/beacon_chain/tests/chain.rs @@ -1,9 +1,7 @@ -use beacon_chain::{BeaconChain, BlockProcessingOutcome}; +use self::utils::TestRig; +use beacon_chain::BeaconChain; #[cfg(test)] -use block_producer::{ - test_utils::{TestEpochMap, TestSigner}, - BeaconNode as BeaconBlockNode, BeaconNodeError as BeaconBlockNodeError, BlockProducer, -}; +use block_producer::{test_utils::TestSigner, BlockProducer}; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, @@ -11,92 +9,19 @@ use db::{ use slot_clock::TestingSlotClock; use spec::ChainSpec; use std::sync::{Arc, RwLock}; -use types::{BeaconBlock, Keypair}; +use types::{Keypair, Validator}; -struct DirectBeaconNode(); - -impl BeaconBlockNode for DirectBeaconNode { - fn produce_beacon_block(&self, slot: u64) -> Result, BeaconBlockNodeError> { - Err(BeaconBlockNodeError::DecodeFailure) - } - - /// Returns the value specified by the `set_next_publish_result`. - fn publish_beacon_block(&self, block: BeaconBlock) -> Result { - Err(BeaconBlockNodeError::DecodeFailure) - } -} - -struct Validator { - block_producer: BlockProducer, - spec: Arc, - epoch_map: Arc, - keypair: Keypair, - beacon_node: Arc, - slot_clock: Arc>, - signer: Arc, -} - -impl Validator { - pub fn new() -> Self { - let spec = Arc::new(ChainSpec::foundation()); - let keypair = Keypair::random(); - let slot_clock = Arc::new(RwLock::new(TestingSlotClock::new(0))); - let signer = Arc::new(TestSigner::new(keypair.clone())); - let beacon_node = Arc::new(DirectBeaconNode()); - let epoch_map = Arc::new(TestEpochMap::new()); - - let block_producer = BlockProducer::new( - spec.clone(), - epoch_map.clone(), - slot_clock.clone(), - beacon_node.clone(), - signer.clone(), - ); - - Self { - block_producer, - spec, - epoch_map, - keypair, - beacon_node, - slot_clock, - signer, - } - } -} - -fn generate_validators(n: usize) -> Vec { - let mut validators = Vec::with_capacity(n); - for _ in 0..n { - validators.push(Validator::new()); - } - validators -} - -fn in_memory_test_stores() -> ( - Arc, - Arc>, - Arc>, -) { - let db = Arc::new(MemoryDB::open()); - let block_store = Arc::new(BeaconBlockStore::new(db.clone())); - let state_store = Arc::new(BeaconStateStore::new(db.clone())); - (db, block_store, state_store) -} - -fn in_memory_test_chain( - spec: ChainSpec, -) -> (Arc, BeaconChain) { - let (db, block_store, state_store) = in_memory_test_stores(); - let slot_clock = TestingSlotClock::new(0); - - let chain = BeaconChain::genesis(state_store, block_store, slot_clock, spec); - (db, chain.unwrap()) -} +mod utils; #[test] -fn it_constructs() { - let (_db, _chain) = in_memory_test_chain(ChainSpec::foundation()); +fn rig_can_generate_validators() { + /* + let (_db, mut chain) = in_memory_test_chain(ChainSpec::foundation()); + let validators = generate_validators(2, &chain); + chain.spec = inject_validators_into_spec(chain.spec.clone(), &validators[..]); + */ + let mut rig = TestRig::new(ChainSpec::foundation()); + rig.generate_validators(2); } /* diff --git a/beacon_node/beacon_chain/tests/utils/direct_beacon_node.rs b/beacon_node/beacon_chain/tests/utils/direct_beacon_node.rs new file mode 100644 index 0000000000..130a4d1360 --- /dev/null +++ b/beacon_node/beacon_chain/tests/utils/direct_beacon_node.rs @@ -0,0 +1,58 @@ +use beacon_chain::{block_production::Error as BlockProductionError, BeaconChain}; +use block_producer::{BeaconNode as BeaconBlockNode, BeaconNodeError as BeaconBlockNodeError}; +use db::ClientDB; +use slot_clock::SlotClock; +use types::{BeaconBlock, PublicKey, Signature}; + +pub struct DirectBeaconNode<'a, T: ClientDB, U: SlotClock> { + beacon_chain: &'a BeaconChain, +} + +impl<'a, T: ClientDB, U: SlotClock> DirectBeaconNode<'a, T, U> { + pub fn new(beacon_chain: &'a BeaconChain) -> Self { + Self { beacon_chain } + } +} + +impl<'a, T: ClientDB, U: SlotClock> BeaconBlockNode for DirectBeaconNode<'a, T, U> +where + BlockProductionError: From<::Error>, +{ + fn proposer_nonce(&self, pubkey: &PublicKey) -> Result { + let validator_index = self + .beacon_chain + .validator_index(pubkey) + .ok_or_else(|| BeaconBlockNodeError::RemoteFailure("pubkey unknown.".to_string()))?; + + self.beacon_chain + .proposer_slots(validator_index) + .ok_or_else(|| { + BeaconBlockNodeError::RemoteFailure("validator_index unknown.".to_string()) + }) + } + + fn produce_beacon_block( + &self, + slot: u64, + randao_reveal: &Signature, + ) -> Result, BeaconBlockNodeError> +where { + let (block, _state) = self + .beacon_chain + .produce_block(randao_reveal.clone()) + .map_err(|e| BeaconBlockNodeError::RemoteFailure(format!("{:?}", e)))?; + + if block.slot == slot { + Ok(Some(block)) + } else { + Err(BeaconBlockNodeError::RemoteFailure( + "Unable to produce at non-current slot.".to_string(), + )) + } + } + + /// Returns the value specified by the `set_next_publish_result`. + fn publish_beacon_block(&self, block: BeaconBlock) -> Result { + Err(BeaconBlockNodeError::DecodeFailure) + } +} diff --git a/beacon_node/beacon_chain/tests/utils/direct_duties.rs b/beacon_node/beacon_chain/tests/utils/direct_duties.rs new file mode 100644 index 0000000000..75177ac149 --- /dev/null +++ b/beacon_node/beacon_chain/tests/utils/direct_duties.rs @@ -0,0 +1,37 @@ +use beacon_chain::{block_production::Error as BlockProductionError, BeaconChain}; +use block_producer::{DutiesReader, DutiesReaderError}; +use db::ClientDB; +use slot_clock::SlotClock; +use types::PublicKey; + +pub struct DirectDuties<'a, T: ClientDB, U: SlotClock> { + beacon_chain: &'a BeaconChain, + pubkey: PublicKey, +} + +impl<'a, T: ClientDB, U: SlotClock> DirectDuties<'a, T, U> { + pub fn new(pubkey: PublicKey, beacon_chain: &'a BeaconChain) -> Self { + Self { + beacon_chain, + pubkey, + } + } +} + +impl<'a, T: ClientDB, U: SlotClock> DutiesReader for DirectDuties<'a, T, U> +where + BlockProductionError: From<::Error>, +{ + fn is_block_production_slot(&self, _epoch: u64, slot: u64) -> Result { + let validator_index = self + .beacon_chain + .validator_index(&self.pubkey) + .ok_or_else(|| DutiesReaderError::UnknownValidator)?; + + match self.beacon_chain.block_proposer(slot) { + Some(proposer) if proposer == validator_index => Ok(true), + Some(_) => Ok(false), + None => Err(DutiesReaderError::UnknownEpoch), + } + } +} diff --git a/beacon_node/beacon_chain/tests/utils/mod.rs b/beacon_node/beacon_chain/tests/utils/mod.rs new file mode 100644 index 0000000000..6460fe421f --- /dev/null +++ b/beacon_node/beacon_chain/tests/utils/mod.rs @@ -0,0 +1,9 @@ +mod direct_beacon_node; +mod direct_duties; +mod test_rig; +mod validator; + +pub use self::direct_beacon_node::DirectBeaconNode; +pub use self::direct_duties::DirectDuties; +pub use self::test_rig::TestRig; +pub use self::validator::TestValidator; diff --git a/beacon_node/beacon_chain/tests/utils/test_rig.rs b/beacon_node/beacon_chain/tests/utils/test_rig.rs new file mode 100644 index 0000000000..0bdf0a36bb --- /dev/null +++ b/beacon_node/beacon_chain/tests/utils/test_rig.rs @@ -0,0 +1,85 @@ +use super::{DirectBeaconNode, DirectDuties, TestValidator}; +use beacon_chain::BeaconChain; +#[cfg(test)] +use block_producer::{test_utils::TestSigner, BlockProducer}; +use db::{ + stores::{BeaconBlockStore, BeaconStateStore}, + MemoryDB, +}; +use slot_clock::TestingSlotClock; +use spec::ChainSpec; +use std::sync::{Arc, RwLock}; +use types::{Keypair, Validator}; + +pub struct TestRig<'a> { + db: Arc, + beacon_chain: BeaconChain, + block_store: Arc>, + state_store: Arc>, + validators: Vec>, +} + +impl<'a> TestRig<'a> { + pub fn new(spec: ChainSpec) -> Self { + let db = Arc::new(MemoryDB::open()); + let block_store = Arc::new(BeaconBlockStore::new(db.clone())); + let state_store = Arc::new(BeaconStateStore::new(db.clone())); + + let slot_clock = TestingSlotClock::new(0); + + let mut beacon_chain = + BeaconChain::genesis(state_store.clone(), block_store.clone(), slot_clock, spec) + .unwrap(); + + /* + let validators = generate_validators(validator_count, &beacon_chain); + beacon_chain.spec = inject_validators_into_spec(beacon_chain.spec.clone(), &validators[..]); + */ + + Self { + db, + beacon_chain, + block_store, + state_store, + validators: vec![], + } + } + + pub fn generate_validators(&'a mut self, validator_count: usize) { + self.validators = Vec::with_capacity(validator_count); + for _ in 0..validator_count { + self.validators.push(TestValidator::new(&self.beacon_chain)); + } + self.beacon_chain.spec = + inject_validators_into_spec(self.beacon_chain.spec.clone(), &self.validators[..]); + } + + pub fn process_next_slot(&mut self) { + let slot = self + .beacon_chain + .present_slot() + .expect("Unable to determine slot.") + + 1; + self.beacon_chain.slot_clock.set_slot(slot); + + let block_proposer = self + .beacon_chain + .block_proposer(slot) + .expect("Unable to determine proposer."); + + let validator = self + .validators + .get(block_proposer) + .expect("Block proposer unknown"); + } +} + +fn inject_validators_into_spec(mut spec: ChainSpec, validators: &[TestValidator]) -> ChainSpec { + spec.initial_validators = Vec::with_capacity(validators.len()); + spec.initial_balances = Vec::with_capacity(validators.len()); + for validator in validators { + spec.initial_validators.push(validator.validator_record()); + spec.initial_balances.push(32_000_000_000); // 32 ETH + } + spec +} diff --git a/beacon_node/beacon_chain/tests/utils/validator.rs b/beacon_node/beacon_chain/tests/utils/validator.rs new file mode 100644 index 0000000000..a6a348dced --- /dev/null +++ b/beacon_node/beacon_chain/tests/utils/validator.rs @@ -0,0 +1,61 @@ +use super::{DirectBeaconNode, DirectDuties}; +use beacon_chain::BeaconChain; +#[cfg(test)] +use block_producer::{test_utils::TestSigner, BlockProducer}; +use db::MemoryDB; +use slot_clock::TestingSlotClock; +use spec::ChainSpec; +use std::sync::{Arc, RwLock}; +use types::{Keypair, Validator}; + +pub struct TestValidator<'a> { + block_producer: BlockProducer< + TestingSlotClock, + DirectBeaconNode<'a, MemoryDB, TestingSlotClock>, + DirectDuties<'a, MemoryDB, TestingSlotClock>, + TestSigner, + >, + spec: Arc, + epoch_map: Arc>, + keypair: Keypair, + beacon_node: Arc>, + slot_clock: Arc>, + signer: Arc, +} + +impl<'a> TestValidator<'a> { + pub fn new(beacon_chain: &'a BeaconChain) -> Self { + let spec = Arc::new(ChainSpec::foundation()); + let keypair = Keypair::random(); + let slot_clock = Arc::new(RwLock::new(TestingSlotClock::new(0))); + let signer = Arc::new(TestSigner::new(keypair.clone())); + let beacon_node = Arc::new(DirectBeaconNode::new(beacon_chain)); + let epoch_map = Arc::new(DirectDuties::new(keypair.pk.clone(), beacon_chain)); + + let block_producer = BlockProducer::new( + spec.clone(), + keypair.pk.clone(), + epoch_map.clone(), + slot_clock.clone(), + beacon_node.clone(), + signer.clone(), + ); + + Self { + block_producer, + spec, + epoch_map, + keypair, + beacon_node, + slot_clock, + signer, + } + } + + pub fn validator_record(&self) -> Validator { + Validator { + pubkey: self.keypair.pk.clone(), + ..std::default::Default::default() + } + } +} diff --git a/eth2/block_producer/Cargo.toml b/eth2/block_producer/Cargo.toml index 76ea78ea3c..7203dbe316 100644 --- a/eth2/block_producer/Cargo.toml +++ b/eth2/block_producer/Cargo.toml @@ -7,4 +7,5 @@ edition = "2018" [dependencies] slot_clock = { path = "../../eth2/utils/slot_clock" } spec = { path = "../../eth2/spec" } +ssz = { path = "../../eth2/utils/ssz" } types = { path = "../../eth2/types" } diff --git a/eth2/block_producer/src/lib.rs b/eth2/block_producer/src/lib.rs index fc06ffa46f..48c36fa9f5 100644 --- a/eth2/block_producer/src/lib.rs +++ b/eth2/block_producer/src/lib.rs @@ -3,8 +3,9 @@ mod traits; use slot_clock::SlotClock; use spec::ChainSpec; +use ssz::ssz_encode; use std::sync::{Arc, RwLock}; -use types::{BeaconBlock, Hash256, ProposalSignedData}; +use types::{BeaconBlock, Hash256, ProposalSignedData, PublicKey}; pub use self::traits::{BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, Signer}; @@ -24,6 +25,8 @@ pub enum PollOutcome { BeaconNodeUnableToProduceBlock(u64), /// The signer failed to sign the message. SignerRejection(u64), + /// The public key for this validator is not an active validator. + ValidatorIsUnknown(u64), } #[derive(Debug, PartialEq)] @@ -44,6 +47,7 @@ pub enum Error { /// Relies upon an external service to keep the `EpochDutiesMap` updated. pub struct BlockProducer { pub last_processed_slot: u64, + pubkey: PublicKey, spec: Arc, epoch_map: Arc, slot_clock: Arc>, @@ -55,6 +59,7 @@ impl BlockProducer, + pubkey: PublicKey, epoch_map: Arc, slot_clock: Arc>, beacon_node: Arc, @@ -62,6 +67,7 @@ impl BlockProducer Self { Self { last_processed_slot: 0, + pubkey, spec, epoch_map, slot_clock, @@ -96,6 +102,9 @@ impl BlockProducer { return Ok(PollOutcome::ProducerDutiesUnknown(slot)) } + Err(DutiesReaderError::UnknownValidator) => { + return Ok(PollOutcome::ValidatorIsUnknown(slot)) + } Err(DutiesReaderError::Poisoned) => return Err(Error::EpochMapPoisoned), }; @@ -122,7 +131,20 @@ impl BlockProducer Result { - if let Some(block) = self.beacon_node.produce_beacon_block(slot)? { + let randao_reveal = { + let producer_nonce = self.beacon_node.proposer_nonce(&self.pubkey)?; + // TODO: add domain, etc to this message. + let message = ssz_encode(&producer_nonce); + match self.signer.bls_sign(&message) { + None => return Ok(PollOutcome::SignerRejection(slot)), + Some(signature) => signature, + } + }; + + if let Some(block) = self + .beacon_node + .produce_beacon_block(slot, &randao_reveal)? + { if self.safe_to_produce(&block) { if let Some(block) = self.sign_block(block) { self.beacon_node.publish_beacon_block(block)?; diff --git a/eth2/block_producer/src/test_utils/beacon_node.rs b/eth2/block_producer/src/test_utils/beacon_node.rs index bca01d9c7d..59527ce60e 100644 --- a/eth2/block_producer/src/test_utils/beacon_node.rs +++ b/eth2/block_producer/src/test_utils/beacon_node.rs @@ -1,20 +1,30 @@ use crate::traits::{BeaconNode, BeaconNodeError}; use std::sync::RwLock; -use types::BeaconBlock; +use types::{BeaconBlock, PublicKey, Signature}; +type NonceResult = Result; type ProduceResult = Result, BeaconNodeError>; type PublishResult = Result; /// A test-only struct used to simulate a Beacon Node. #[derive(Default)] pub struct TestBeaconNode { - pub produce_input: RwLock>, + pub nonce_input: RwLock>, + pub nonce_result: RwLock>, + + pub produce_input: RwLock>, pub produce_result: RwLock>, + pub publish_input: RwLock>, pub publish_result: RwLock>, } impl TestBeaconNode { + /// Set the result to be returned when `produce_beacon_block` is called. + pub fn set_next_nonce_result(&self, result: NonceResult) { + *self.nonce_result.write().unwrap() = Some(result); + } + /// Set the result to be returned when `produce_beacon_block` is called. pub fn set_next_produce_result(&self, result: ProduceResult) { *self.produce_result.write().unwrap() = Some(result); @@ -27,9 +37,17 @@ impl TestBeaconNode { } impl BeaconNode for TestBeaconNode { + fn proposer_nonce(&self, pubkey: &PublicKey) -> NonceResult { + *self.nonce_input.write().unwrap() = Some(pubkey.clone()); + match *self.nonce_result.read().unwrap() { + Some(ref r) => r.clone(), + None => panic!("TestBeaconNode: nonce_result == None"), + } + } + /// Returns the value specified by the `set_next_produce_result`. - fn produce_beacon_block(&self, slot: u64) -> ProduceResult { - *self.produce_input.write().unwrap() = Some(slot); + fn produce_beacon_block(&self, slot: u64, randao_reveal: &Signature) -> ProduceResult { + *self.produce_input.write().unwrap() = Some((slot, randao_reveal.clone())); match *self.produce_result.read().unwrap() { Some(ref r) => r.clone(), None => panic!("TestBeaconNode: produce_result == None"), diff --git a/eth2/block_producer/src/traits.rs b/eth2/block_producer/src/traits.rs index c9716701b2..0145328a35 100644 --- a/eth2/block_producer/src/traits.rs +++ b/eth2/block_producer/src/traits.rs @@ -1,4 +1,4 @@ -use types::{BeaconBlock, Signature}; +use types::{BeaconBlock, PublicKey, Signature}; #[derive(Debug, PartialEq, Clone)] pub enum BeaconNodeError { @@ -8,10 +8,18 @@ pub enum BeaconNodeError { /// Defines the methods required to produce and publish blocks on a Beacon Node. pub trait BeaconNode: Send + Sync { + /// Requests the proposer nonce (presently named `proposer_slots`). + fn proposer_nonce(&self, pubkey: &PublicKey) -> Result; + /// Request that the node produces a block. /// /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. - fn produce_beacon_block(&self, slot: u64) -> Result, BeaconNodeError>; + fn produce_beacon_block( + &self, + slot: u64, + randao_reveal: &Signature, + ) -> Result, BeaconNodeError>; + /// Request that the node publishes a block. /// /// Returns `true` if the publish was sucessful. @@ -20,6 +28,7 @@ pub trait BeaconNode: Send + Sync { #[derive(Debug, PartialEq, Clone)] pub enum DutiesReaderError { + UnknownValidator, UnknownEpoch, Poisoned, } diff --git a/eth2/spec/src/lib.rs b/eth2/spec/src/lib.rs index 3e7d5dd2b6..aa3906076f 100644 --- a/eth2/spec/src/lib.rs +++ b/eth2/spec/src/lib.rs @@ -6,7 +6,7 @@ mod foundation; use bls::Signature; use types::{Address, Eth1Data, Hash256, Validator}; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct ChainSpec { /* * Misc