From 46da9b670f716572495b2c94674e75f5cb752381 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 29 Oct 2018 20:29:15 +0100 Subject: [PATCH] Add untested minimum viable block processing --- Cargo.toml | 2 + beacon_chain/chain/Cargo.toml | 3 + beacon_chain/chain/src/block_context.rs | 61 ++--- beacon_chain/chain/src/block_processing.rs | 247 ++++++++++++++---- beacon_chain/chain/src/genesis.rs | 61 ++--- beacon_chain/chain/src/lib.rs | 63 +++-- beacon_chain/chain/src/maps.rs | 41 +-- beacon_chain/chain/src/stores.rs | 10 +- beacon_chain/chain/src/transition.rs | 29 ++ beacon_chain/chain/tests/main.rs | 7 + beacon_chain/naive_fork_choice/Cargo.toml | 9 + beacon_chain/naive_fork_choice/src/lib.rs | 97 +++++++ beacon_chain/state-transition/Cargo.toml | 7 + beacon_chain/state-transition/src/lib.rs | 194 ++++++++++++++ beacon_chain/types/src/active_state.rs | 12 +- beacon_chain/types/src/crystallized_state.rs | 10 +- .../utils/ssz_helpers/src/ssz_beacon_block.rs | 11 +- .../validation/src/block_validation.rs | 132 ++++------ .../tests/block_validation/helpers.rs | 129 ++++----- .../tests/block_validation/tests.rs | 156 +++++------ .../db/src/stores/beacon_block_store.rs | 75 +++--- 21 files changed, 891 insertions(+), 465 deletions(-) create mode 100644 beacon_chain/chain/src/transition.rs create mode 100644 beacon_chain/chain/tests/main.rs create mode 100644 beacon_chain/naive_fork_choice/Cargo.toml create mode 100644 beacon_chain/naive_fork_choice/src/lib.rs create mode 100644 beacon_chain/state-transition/Cargo.toml create mode 100644 beacon_chain/state-transition/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index b0872b8e0..f2fe2aa7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,8 @@ name = "lighthouse" [workspace] members = [ "beacon_chain/chain", + "beacon_chain/naive_fork_choice", + "beacon_chain/state-transition", "beacon_chain/types", "beacon_chain/utils/active-validators", "beacon_chain/utils/bls", diff --git a/beacon_chain/chain/Cargo.toml b/beacon_chain/chain/Cargo.toml index dc4053cbb..65a261731 100644 --- a/beacon_chain/chain/Cargo.toml +++ b/beacon_chain/chain/Cargo.toml @@ -6,7 +6,10 @@ authors = ["Paul Hauner "] [dependencies] bls = { path = "../utils/bls" } db = { path = "../../lighthouse/db" } +naive_fork_choice = { path = "../naive_fork_choice" } +ssz = { path = "../utils/ssz" } ssz_helpers = { path = "../utils/ssz_helpers" } +state-transition = { path = "../state-transition" } types = { path = "../types" } validation = { path = "../validation" } validator_induction = { path = "../validator_induction" } diff --git a/beacon_chain/chain/src/block_context.rs b/beacon_chain/chain/src/block_context.rs index 05219e3dd..75c4352ec 100644 --- a/beacon_chain/chain/src/block_context.rs +++ b/beacon_chain/chain/src/block_context.rs @@ -1,22 +1,10 @@ -use db::{ - ClientDB, -}; -use db::stores::{ - BeaconBlockAtSlotError, -}; -use validation::block_validation::{ - BeaconBlockValidationContext, -}; -use super::{ - BeaconChain, -}; -use ssz_helpers::ssz_beacon_block::{ - SszBeaconBlock, -}; +use super::BeaconChain; +use db::stores::BeaconBlockAtSlotError; +use db::ClientDB; +use ssz_helpers::ssz_beacon_block::SszBeaconBlock; use std::sync::Arc; -use types::{ - Hash256, -}; +use types::Hash256; +use validation::block_validation::BeaconBlockValidationContext; pub enum BlockValidationContextError { UnknownCrystallizedState, @@ -35,18 +23,24 @@ impl From for BlockValidationContextError { } impl BeaconChain - where T: ClientDB + Sized +where + T: ClientDB + Sized, { - pub(crate) fn block_validation_context(&self, block: &SszBeaconBlock, present_slot: u64) - -> Result, BlockValidationContextError> - { + pub(crate) fn block_validation_context( + &self, + block: &SszBeaconBlock, + parent_block: &SszBeaconBlock, + present_slot: u64, + ) -> Result, BlockValidationContextError> { /* * Load the crystallized state for this block from our caches. * * Fail if the crystallized state is unknown. */ - let cry_state_root = Hash256::from(block.cry_state_root()); - let cry_state = self.crystallized_states.get(&cry_state_root) + let cry_state_root = Hash256::from(parent_block.cry_state_root()); + let cry_state = self + .crystallized_states + .get(&cry_state_root) .ok_or(BlockValidationContextError::UnknownCrystallizedState)?; /* @@ -54,8 +48,10 @@ impl BeaconChain * * Fail if the active state is unknown. */ - let act_state_root = Hash256::from(block.act_state_root()); - let act_state = self.active_states.get(&act_state_root) + let act_state_root = Hash256::from(parent_block.act_state_root()); + let act_state = self + .active_states + .get(&act_state_root) .ok_or(BlockValidationContextError::UnknownActiveState)?; /* @@ -63,16 +59,21 @@ impl BeaconChain * the hash of this block from the database */ let last_justified_slot = cry_state.last_justified_slot; - let parent_block_hash = block.parent_hash() + let parent_block_hash = block + .parent_hash() .ok_or(BlockValidationContextError::NoParentHash)?; - let (last_justified_block_hash, _) = self.store.block.block_at_slot( - &parent_block_hash, last_justified_slot)? + let (last_justified_block_hash, _) = self + .store + .block + .block_at_slot(&parent_block_hash, last_justified_slot)? .ok_or(BlockValidationContextError::UnknownJustifiedBlock)?; /* * Load the attester and proposer maps for the crystallized state. */ - let (attester_map, proposer_map) = self.attester_proposer_maps.get(&cry_state_root) + let (attester_map, proposer_map) = self + .attester_proposer_maps + .get(&cry_state_root) .ok_or(BlockValidationContextError::UnknownAttesterProposerMaps)?; Ok(BeaconBlockValidationContext { diff --git a/beacon_chain/chain/src/block_processing.rs b/beacon_chain/chain/src/block_processing.rs index 5ba44cab0..1847549fd 100644 --- a/beacon_chain/chain/src/block_processing.rs +++ b/beacon_chain/chain/src/block_processing.rs @@ -1,98 +1,219 @@ -use super::{ - BeaconChain, - ClientDB, -}; -use super::block_context::{ - BlockValidationContextError, -}; -use ssz_helpers::ssz_beacon_block::{ - SszBeaconBlock, - SszBeaconBlockError, -}; -use types::{ - Hash256, -}; -use validation::block_validation::{ - BeaconBlockStatus, - SszBeaconBlockValidationError, -}; +use super::block_context::BlockValidationContextError; +use super::state_transition::StateTransitionError; +use super::BeaconChain; +use db::{ClientDB, DBError}; +use naive_fork_choice::{naive_fork_choice, ForkChoiceError}; +use ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; +use types::Hash256; +use validation::block_validation::SszBeaconBlockValidationError; pub enum BlockProcessingOutcome { BlockAlreadyKnown, NewCanonicalBlock, + NewReorgBlock, NewForkBlock, } pub enum BlockProcessingError { - ContextGenerationError(BlockValidationContextError), + ParentBlockNotFound, + ActiveStateRootInvalid, + CrystallizedStateRootInvalid, + NoHeadHashes, + ForkChoiceFailed(ForkChoiceError), + ContextGenerationFailed(BlockValidationContextError), DeserializationFailed(SszBeaconBlockError), ValidationFailed(SszBeaconBlockValidationError), + StateTransitionFailed(StateTransitionError), + DBError(String), } impl BeaconChain - where T: ClientDB + Sized +where + T: ClientDB + Sized, { - pub fn process_block(&mut self, ssz: &[u8], present_slot: u64) - -> Result<(BlockProcessingOutcome, Hash256), BlockProcessingError> - { + pub fn process_block( + &mut self, + ssz: &[u8], + present_slot: u64, + ) -> Result<(BlockProcessingOutcome, Hash256), BlockProcessingError> { /* * Generate a SszBlock to read directly from the serialized SSZ. */ let ssz_block = SszBeaconBlock::from_slice(ssz)?; let block_hash = Hash256::from(&ssz_block.block_hash()[..]); - let parent_hash = ssz_block.parent_hash() + + /* + * If this block is already known, return immediately and indicate the the block is + * known. Don't attempt to deserialize the block. + */ + if self.store.block.block_exists(&block_hash)? { + return Ok((BlockProcessingOutcome::BlockAlreadyKnown, block_hash)); + } + + /* + * Determine the hash of the blocks parent + */ + let parent_hash = ssz_block + .parent_hash() .ok_or(BlockProcessingError::ValidationFailed( - SszBeaconBlockValidationError::UnknownParentHash))?; + SszBeaconBlockValidationError::UnknownParentHash, + ))?; + + /* + * Load the parent block from the database and create an SszBeaconBlock for reading it. + */ + let parent_block_ssz_bytes = self + .store + .block + .get_serialized_block(&parent_hash[..])? + .ok_or(BlockProcessingError::ParentBlockNotFound)?; + let parent_ssz_block = SszBeaconBlock::from_slice(&parent_block_ssz_bytes)?; /* * Generate the context in which to validate this block. */ - let validation_context = self.block_validation_context(&ssz_block, present_slot)?; + let validation_context = + self.block_validation_context(&ssz_block, &parent_ssz_block, present_slot)?; /* * Validate the block against the context, checking signatures, parent_hashes, etc. */ - let (block_status, block) = validation_context.validate_ssz_block(&block_hash, &block)?; + let block = validation_context.validate_ssz_block(&ssz_block)?; - match block_status { + let (new_act_state, new_cry_state_option) = { /* + * Load the states from memory. * + * Note: this is the second time we load these, the first was in + * `block_validation_context`. Theres an opportunity for some opimisation here. + * It was left out because it made the code more cumbersome. */ - BeaconBlockStatus::KnownBlock => { - Ok((BlockProcessingOutcome::BlockAlreadyKnown, block_hash)) - } - BeaconBlockStatus::NewBlock => { - let head_hash_index = { - match self.head_block_hashes.iter().position(|x| *x == Hash256::from(parent_hash)) { - Some(i) => i, - None => { - self.head_block_hashes.push(block_hash); - self.head_block_hashes.len() - 1 - } - } - }; + let act_state = self + .active_states + .get(&block.active_state_root) + .ok_or(BlockValidationContextError::UnknownActiveState)?; + let cry_state = self + .crystallized_states + .get(&block.crystallized_state_root) + .ok_or(BlockValidationContextError::UnknownCrystallizedState)?; - if head_hash_index == self.canonical_head_block_hash { - Ok((BlockProcessingOutcome::NewCanonicalBlock, block_hash)) - } else { - Ok((BlockProcessingOutcome::NewForkBlock, block_hash)) + self.transition_states(act_state, cry_state, &block, &block_hash)? + }; + + /* + * Calculate the new active state root and ensure the block state root matches. + */ + let new_act_state_root = new_act_state.canonical_root(); + if new_act_state_root != block.active_state_root { + return Err(BlockProcessingError::ActiveStateRootInvalid); + } + + /* + * Determine the crystallized state root and ensure the block state root matches. + * + * If a new crystallized state was created, store it in memory. + */ + let (new_cry_state_root, cry_state_transitioned) = match new_cry_state_option { + None => { + /* + * A new crystallized state was not created, therefore the + * `crystallized_state_root` of this block must match its parent. + */ + if Hash256::from(parent_ssz_block.cry_state_root()) != block.crystallized_state_root + { + return Err(BlockProcessingError::ActiveStateRootInvalid); } + // Return the old root + (block.crystallized_state_root, false) + } + Some(new_cry_state) => { + /* + * A new crystallized state was created. Check to ensure the crystallized + * state root in the block is the same as the calculated on this node. + */ + let cry_state_root = new_cry_state.canonical_root(); + if cry_state_root != block.crystallized_state_root { + return Err(BlockProcessingError::ActiveStateRootInvalid); + } + /* + * Store the new crystallized state in memory. + */ + self.crystallized_states + .insert(cry_state_root, new_cry_state); + // Return the new root + (cry_state_root, true) + } + }; + + /* + * Store the new block as a leaf in the block tree. + */ + let mut new_head_block_hashes = self.head_block_hashes.clone(); + let new_parent_head_hash_index = match new_head_block_hashes + .iter() + .position(|x| *x == Hash256::from(parent_hash)) + { + Some(i) => { + new_head_block_hashes[i] = block_hash.clone(); + i + } + None => { + new_head_block_hashes.push(block_hash.clone()); + new_head_block_hashes.len() - 1 + } + }; + + /* + * Store the new block in the database. + */ + self.store + .block + .put_serialized_block(&block_hash[..], ssz_block.block_ssz())?; + + /* + * Store the active state in memory. + */ + self.active_states.insert(new_act_state_root, new_act_state); + + let new_canonical_head_block_hash_index = + match naive_fork_choice(&self.head_block_hashes, self.store.block.clone())? { + None => { + /* + * Fork choice failed, therefore the block, active state and crystallized state + * can be removed from storage (i.e., forgotten). + */ + if cry_state_transitioned { + // A new crystallized state was generated, so it should be deleted. + self.crystallized_states.remove(&new_cry_state_root); + } + self.active_states.remove(&new_act_state_root); + self.store.block.delete_block(&block_hash[..])?; + return Err(BlockProcessingError::NoHeadHashes); + } + Some(i) => i, + }; + + if new_canonical_head_block_hash_index != self.canonical_head_block_hash { + /* + * The block caused a re-org (switch of chains). + */ + Ok((BlockProcessingOutcome::NewReorgBlock, block_hash)) + } else { + /* + * The block did not cause a re-org. + */ + if new_parent_head_hash_index == self.canonical_head_block_hash { + Ok((BlockProcessingOutcome::NewCanonicalBlock, block_hash)) + } else { + Ok((BlockProcessingOutcome::NewForkBlock, block_hash)) } } } - - pub fn extend_chain( - &self, - block: &Block, - block_hash: &Hash256, - head_hash_index: usize) - -> Result<> } - impl From for BlockProcessingError { fn from(e: BlockValidationContextError) -> Self { - BlockProcessingError::ContextGenerationError(e) + BlockProcessingError::ContextGenerationFailed(e) } } @@ -102,8 +223,26 @@ impl From for BlockProcessingError { } } +impl From for BlockProcessingError { + fn from(e: DBError) -> Self { + BlockProcessingError::DBError(e.message) + } +} + +impl From for BlockProcessingError { + fn from(e: ForkChoiceError) -> Self { + BlockProcessingError::ForkChoiceFailed(e) + } +} + impl From for BlockProcessingError { fn from(e: SszBeaconBlockValidationError) -> Self { BlockProcessingError::ValidationFailed(e) } } + +impl From for BlockProcessingError { + fn from(e: StateTransitionError) -> Self { + BlockProcessingError::StateTransitionFailed(e) + } +} diff --git a/beacon_chain/chain/src/genesis.rs b/beacon_chain/chain/src/genesis.rs index f3426729c..fb0bc58f8 100644 --- a/beacon_chain/chain/src/genesis.rs +++ b/beacon_chain/chain/src/genesis.rs @@ -1,20 +1,7 @@ -use types::{ - CrosslinkRecord, - Hash256, - ValidatorRegistration, - ValidatorStatus, -}; -use super::{ - ActiveState, - CrystallizedState, - BeaconChainError, - ChainConfig, -}; +use super::{ActiveState, BeaconChainError, ChainConfig, CrystallizedState}; +use types::{CrosslinkRecord, Hash256, ValidatorStatus}; use validator_induction::ValidatorInductor; -use validator_shuffling::{ - shard_and_committees_for_cycle, - ValidatorAssignmentError, -}; +use validator_shuffling::{shard_and_committees_for_cycle, ValidatorAssignmentError}; pub const INITIAL_FORK_VERSION: u32 = 0; @@ -27,9 +14,9 @@ impl From for BeaconChainError { /// Initialize a new ChainHead with genesis parameters. /// /// Used when syncing a chain from scratch. -pub fn genesis_states(config: &ChainConfig) - -> Result<(ActiveState, CrystallizedState), ValidatorAssignmentError> -{ +pub fn genesis_states( + config: &ChainConfig, +) -> Result<(ActiveState, CrystallizedState), ValidatorAssignmentError> { /* * Parse the ValidatorRegistrations into ValidatorRecords and induct them. * @@ -39,7 +26,7 @@ pub fn genesis_states(config: &ChainConfig) let mut inductor = ValidatorInductor::new(0, config.shard_count, vec![]); for registration in &config.initial_validators { let _ = inductor.induct(®istration, ValidatorStatus::Active); - }; + } inductor.to_vec() }; @@ -107,21 +94,14 @@ pub fn genesis_states(config: &ChainConfig) Ok((active_state, crystallized_state)) } - #[cfg(test)] mod tests { - extern crate validator_induction; extern crate bls; + extern crate validator_induction; + use self::bls::{create_proof_of_possession, Keypair}; use super::*; - use self::bls::{ - create_proof_of_possession, - Keypair, - }; - use types::{ - Hash256, - Address, - }; + use types::{Address, Hash256, ValidatorRegistration}; #[test] fn test_genesis_no_validators() { @@ -140,7 +120,10 @@ mod tests { assert_eq!(cry.last_finalized_slot, 0); assert_eq!(cry.last_justified_slot, 0); assert_eq!(cry.justified_streak, 0); - assert_eq!(cry.shard_and_committee_for_slots.len(), (config.cycle_length as usize) * 2); + assert_eq!( + cry.shard_and_committee_for_slots.len(), + (config.cycle_length as usize) * 2 + ); assert_eq!(cry.deposits_penalized_in_period.len(), 0); assert_eq!(cry.validator_set_delta_hash_chain, Hash256::zero()); assert_eq!(cry.pre_fork_version, INITIAL_FORK_VERSION); @@ -149,7 +132,10 @@ mod tests { assert_eq!(act.pending_attestations.len(), 0); assert_eq!(act.pending_specials.len(), 0); - assert_eq!(act.recent_block_hashes, vec![Hash256::zero(); config.cycle_length as usize]); + assert_eq!( + act.recent_block_hashes, + vec![Hash256::zero(); config.cycle_length as usize] + ); assert_eq!(act.randao_mix, Hash256::zero()); } @@ -160,7 +146,7 @@ mod tests { withdrawal_shard: 0, withdrawal_address: Address::random(), randao_commitment: Hash256::random(), - proof_of_possession: create_proof_of_possession(&keypair) + proof_of_possession: create_proof_of_possession(&keypair), } } @@ -189,16 +175,19 @@ mod tests { let mut bad_v = random_registration(); let bad_kp = Keypair::random(); - bad_v.proof_of_possession = create_proof_of_possession(&bad_kp); + bad_v.proof_of_possession = create_proof_of_possession(&bad_kp); config.initial_validators.push(bad_v); let mut bad_v = random_registration(); - bad_v.withdrawal_shard = config.shard_count + 1; + bad_v.withdrawal_shard = config.shard_count + 1; config.initial_validators.push(bad_v); let (_, cry) = genesis_states(&config).unwrap(); - assert!(config.initial_validators.len() != good_validator_count, "test is invalid"); + assert!( + config.initial_validators.len() != good_validator_count, + "test is invalid" + ); assert_eq!(cry.validators.len(), good_validator_count); } } diff --git a/beacon_chain/chain/src/lib.rs b/beacon_chain/chain/src/lib.rs index 42aab7ef0..95c4a28d4 100644 --- a/beacon_chain/chain/src/lib.rs +++ b/beacon_chain/chain/src/lib.rs @@ -1,33 +1,27 @@ extern crate db; -extern crate types; +extern crate naive_fork_choice; +extern crate state_transition; +extern crate ssz; extern crate ssz_helpers; +extern crate types; extern crate validation; extern crate validator_induction; extern crate validator_shuffling; -mod stores; mod block_context; mod block_processing; -mod maps; mod genesis; +mod maps; +mod transition; +mod stores; use db::ClientDB; use genesis::genesis_states; -use maps::{ - generate_attester_and_proposer_maps, - AttesterAndProposerMapError, -}; +use maps::{generate_attester_and_proposer_maps, AttesterAndProposerMapError}; use std::collections::HashMap; use std::sync::Arc; use stores::BeaconChainStore; -use types::{ - ActiveState, - AttesterMap, - ChainConfig, - CrystallizedState, - Hash256, - ProposerMap, -}; +use types::{ActiveState, AttesterMap, ChainConfig, CrystallizedState, Hash256, ProposerMap}; #[derive(Debug, PartialEq)] pub enum BeaconChainError { @@ -37,12 +31,6 @@ pub enum BeaconChainError { DBError(String), } -impl From for BeaconChainError { - fn from(e: AttesterAndProposerMapError) -> BeaconChainError { - BeaconChainError::UnableToGenerateMaps(e) - } -} - pub struct BeaconChain { /// The last slot which has been finalized, this is common to all forks. pub last_finalized_slot: u64, @@ -63,11 +51,10 @@ pub struct BeaconChain { } impl BeaconChain - where T: ClientDB + Sized +where + T: ClientDB + Sized, { - pub fn new(store: BeaconChainStore, config: ChainConfig) - -> Result - { + pub fn new(store: BeaconChainStore, config: ChainConfig) -> Result { if config.initial_validators.is_empty() { return Err(BeaconChainError::InsufficientValidators); } @@ -82,15 +69,18 @@ impl BeaconChain let mut attester_proposer_maps = HashMap::new(); let (attester_map, proposer_map) = generate_attester_and_proposer_maps( - &crystallized_state.shard_and_committee_for_slots, 0)?; + &crystallized_state.shard_and_committee_for_slots, + 0, + )?; active_states.insert(canonical_latest_block_hash, active_state); crystallized_states.insert(canonical_latest_block_hash, crystallized_state); attester_proposer_maps.insert( canonical_latest_block_hash, - (Arc::new(attester_map), Arc::new(proposer_map))); + (Arc::new(attester_map), Arc::new(proposer_map)), + ); - Ok(Self{ + Ok(Self { last_finalized_slot: 0, head_block_hashes, canonical_head_block_hash, @@ -102,19 +92,24 @@ impl BeaconChain }) } - pub fn canonical_block_hash(self) -> Hash256 { + pub fn canonical_block_hash(&self) -> Hash256 { self.head_block_hashes[self.canonical_head_block_hash] } } +impl From for BeaconChainError { + fn from(e: AttesterAndProposerMapError) -> BeaconChainError { + BeaconChainError::UnableToGenerateMaps(e) + } +} #[cfg(test)] mod tests { - use std::sync::Arc; use super::*; - use types::ValidatorRegistration; - use db::MemoryDB; use db::stores::*; + use db::MemoryDB; + use std::sync::Arc; + use types::ValidatorRegistration; #[test] fn test_new_chain() { @@ -129,7 +124,9 @@ mod tests { }; for _ in 0..config.cycle_length * 2 { - config.initial_validators.push(ValidatorRegistration::random()) + config + .initial_validators + .push(ValidatorRegistration::random()) } let chain = BeaconChain::new(store, config.clone()).unwrap(); diff --git a/beacon_chain/chain/src/maps.rs b/beacon_chain/chain/src/maps.rs index dfed49e7a..9188531f2 100644 --- a/beacon_chain/chain/src/maps.rs +++ b/beacon_chain/chain/src/maps.rs @@ -1,8 +1,4 @@ -use types::{ - AttesterMap, - ProposerMap, - ShardAndCommittee, -}; +use types::{AttesterMap, ProposerMap, ShardAndCommittee}; #[derive(Debug, PartialEq)] pub enum AttesterAndProposerMapError { @@ -15,9 +11,8 @@ pub enum AttesterAndProposerMapError { /// The attester map is used to optimise the lookup of a committee. pub fn generate_attester_and_proposer_maps( shard_and_committee_for_slots: &Vec>, - start_slot: u64) - -> Result<(AttesterMap, ProposerMap), AttesterAndProposerMapError> -{ + start_slot: u64, +) -> Result<(AttesterMap, ProposerMap), AttesterAndProposerMapError> { let mut attester_map = AttesterMap::new(); let mut proposer_map = ProposerMap::new(); for (i, slot) in shard_and_committee_for_slots.iter().enumerate() { @@ -25,10 +20,12 @@ pub fn generate_attester_and_proposer_maps( * Store the proposer for the block. */ let slot_number = (i as u64).saturating_add(start_slot); - let first_committee = &slot.get(0) + let first_committee = &slot + .get(0) .ok_or(AttesterAndProposerMapError::NoShardAndCommitteeForSlot)? .committee; - let proposer_index = (slot_number as usize).checked_rem(first_committee.len()) + let proposer_index = (slot_number as usize) + .checked_rem(first_committee.len()) .ok_or(AttesterAndProposerMapError::NoAvailableProposer)?; proposer_map.insert(slot_number, first_committee[proposer_index]); @@ -39,7 +36,7 @@ pub fn generate_attester_and_proposer_maps( let committee = shard_and_committee.committee.clone(); attester_map.insert((slot_number, shard_and_committee.shard), committee); } - }; + } Ok((attester_map, proposer_map)) } @@ -47,12 +44,12 @@ pub fn generate_attester_and_proposer_maps( mod tests { use super::*; - fn sac_generator(shard_count: u16, - slot_count: usize, - sac_per_slot: usize, - committee_size: usize) - -> Vec> - { + fn sac_generator( + shard_count: u16, + slot_count: usize, + sac_per_slot: usize, + committee_size: usize, + ) -> Vec> { let mut shard = 0; let mut validator = 0; let mut cycle = vec![]; @@ -80,14 +77,20 @@ mod tests { fn test_attester_proposer_maps_empty_slots() { let sac = sac_generator(4, 4, 0, 1); let result = generate_attester_and_proposer_maps(&sac, 0); - assert_eq!(result, Err(AttesterAndProposerMapError::NoShardAndCommitteeForSlot)); + assert_eq!( + result, + Err(AttesterAndProposerMapError::NoShardAndCommitteeForSlot) + ); } #[test] fn test_attester_proposer_maps_empty_committees() { let sac = sac_generator(4, 4, 1, 0); let result = generate_attester_and_proposer_maps(&sac, 0); - assert_eq!(result, Err(AttesterAndProposerMapError::NoAvailableProposer)); + assert_eq!( + result, + Err(AttesterAndProposerMapError::NoAvailableProposer) + ); } #[test] diff --git a/beacon_chain/chain/src/stores.rs b/beacon_chain/chain/src/stores.rs index e5bacdda8..fae097899 100644 --- a/beacon_chain/chain/src/stores.rs +++ b/beacon_chain/chain/src/stores.rs @@ -1,11 +1,5 @@ -use db::{ - ClientDB, -}; -use db::stores::{ - BeaconBlockStore, - PoWChainStore, - ValidatorStore, -}; +use db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; +use db::ClientDB; use std::sync::Arc; pub struct BeaconChainStore { diff --git a/beacon_chain/chain/src/transition.rs b/beacon_chain/chain/src/transition.rs new file mode 100644 index 000000000..7598f2517 --- /dev/null +++ b/beacon_chain/chain/src/transition.rs @@ -0,0 +1,29 @@ +use super::BeaconChain; +use db::ClientDB; +use state_transition::{extend_active_state, StateTransitionError}; +use types::{ActiveState, BeaconBlock, CrystallizedState, Hash256}; + +impl BeaconChain +where + T: ClientDB + Sized, +{ + pub(crate) fn transition_states( + &self, + act_state: &ActiveState, + cry_state: &CrystallizedState, + block: &BeaconBlock, + block_hash: &Hash256, + ) -> Result<(ActiveState, Option), StateTransitionError> { + let state_recalc_distance = block + .slot + .checked_sub(cry_state.last_state_recalculation_slot) + .ok_or(StateTransitionError::BlockSlotBeforeRecalcSlot)?; + + if state_recalc_distance >= u64::from(self.config.cycle_length) { + panic!("Not implemented!") + } else { + let new_act_state = extend_active_state(act_state, block, block_hash)?; + Ok((new_act_state, None)) + } + } +} diff --git a/beacon_chain/chain/tests/main.rs b/beacon_chain/chain/tests/main.rs new file mode 100644 index 000000000..8b926693e --- /dev/null +++ b/beacon_chain/chain/tests/main.rs @@ -0,0 +1,7 @@ +extern crate chain; + +#[cfg(test)] +mod tests { + use chain::{BeaconChain, BeaconChainError}; + +} diff --git a/beacon_chain/naive_fork_choice/Cargo.toml b/beacon_chain/naive_fork_choice/Cargo.toml new file mode 100644 index 000000000..679575556 --- /dev/null +++ b/beacon_chain/naive_fork_choice/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "naive_fork_choice" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +db = { path = "../../lighthouse/db" } +ssz = { path = "../utils/ssz" } +types = { path = "../types" } diff --git a/beacon_chain/naive_fork_choice/src/lib.rs b/beacon_chain/naive_fork_choice/src/lib.rs new file mode 100644 index 000000000..5270fb1f9 --- /dev/null +++ b/beacon_chain/naive_fork_choice/src/lib.rs @@ -0,0 +1,97 @@ +extern crate db; +extern crate ssz; +extern crate types; + +use db::stores::BeaconBlockStore; +use db::{ClientDB, DBError}; +use ssz::{Decodable, DecodeError}; +use std::sync::Arc; +use types::{BeaconBlock, Hash256}; + +pub enum ForkChoiceError { + BadSszInDatabase, + MissingBlock, + DBError(String), +} + +pub fn naive_fork_choice( + head_block_hashes: &Vec, + block_store: Arc>, +) -> Result, ForkChoiceError> +where + T: ClientDB + Sized, +{ + let mut head_blocks: Vec<(usize, BeaconBlock)> = vec![]; + + /* + * Load all the head_block hashes from the DB as SszBeaconBlocks. + */ + for (index, block_hash) in head_block_hashes.iter().enumerate() { + let ssz = block_store + .get_serialized_block(&block_hash.to_vec()[..])? + .ok_or(ForkChoiceError::MissingBlock)?; + let (block, _) = BeaconBlock::ssz_decode(&ssz, 0)?; + head_blocks.push((index, block)); + } + + /* + * Loop through all the head blocks and find the highest slot. + */ + let highest_slot: Option = None; + for (_, block) in &head_blocks { + let slot = block.slot; + + match highest_slot { + None => Some(slot), + Some(winning_slot) => { + if slot > winning_slot { + Some(slot) + } else { + Some(winning_slot) + } + } + }; + } + + /* + * Loop through all the highest blocks and sort them by highest hash. + * + * Ultimately, the index of the head_block hash with the highest slot and highest block + * hash will be the winner. + */ + match highest_slot { + None => Ok(None), + Some(highest_slot) => { + let mut highest_blocks = vec![]; + for (index, block) in head_blocks { + if block.slot == highest_slot { + highest_blocks.push((index, block)) + } + } + + highest_blocks.sort_by(|a, b| head_block_hashes[a.0].cmp(&head_block_hashes[b.0])); + let (index, _) = highest_blocks[0]; + Ok(Some(index)) + } + } +} + +impl From for ForkChoiceError { + fn from(_: DecodeError) -> Self { + ForkChoiceError::BadSszInDatabase + } +} + +impl From for ForkChoiceError { + fn from(e: DBError) -> Self { + ForkChoiceError::DBError(e.message) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_naive_fork_choice() { + assert_eq!(2 + 2, 4); + } +} diff --git a/beacon_chain/state-transition/Cargo.toml b/beacon_chain/state-transition/Cargo.toml new file mode 100644 index 000000000..7beb8f613 --- /dev/null +++ b/beacon_chain/state-transition/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "state-transition" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +types = { path = "../types" } diff --git a/beacon_chain/state-transition/src/lib.rs b/beacon_chain/state-transition/src/lib.rs new file mode 100644 index 000000000..ee8d841e6 --- /dev/null +++ b/beacon_chain/state-transition/src/lib.rs @@ -0,0 +1,194 @@ +extern crate types; + +use types::{ActiveState, BeaconBlock, Hash256}; + +#[derive(Debug, PartialEq)] +pub enum StateTransitionError { + BlockSlotBeforeRecalcSlot, + InvalidParentHashes, + DBError(String), +} + +pub fn extend_active_state( + act_state: &ActiveState, + block: &BeaconBlock, + block_hash: &Hash256, +) -> Result { + /* + * Extend the pending attestations in the active state with the new attestations included + * in the block. + * + * Using the concat method to avoid reallocations. + */ + let pending_attestations = + [&act_state.pending_attestations[..], &block.attestations[..]].concat(); + + /* + * Extend the pending specials in the active state with the new specials included in the + * block. + * + * Using the concat method to avoid reallocations. + */ + let pending_specials = [&act_state.pending_specials[..], &block.specials[..]].concat(); + + /* + * Update the active state recent_block_hashes: + * + * - Drop the hash from the earliest position. + * - Push the block_hash into the latest position. + * + * Using the concat method to avoid reallocations. + */ + let (_first_hash, last_hashes) = act_state + .recent_block_hashes + .split_first() + .ok_or(StateTransitionError::InvalidParentHashes)?; + let new_hash = &[block_hash.clone()]; + let recent_block_hashes = [&last_hashes, &new_hash[..]].concat(); + + /* + * The new `randao_mix` is set to the XOR of the previous active state randao mix and the + * randao reveal in this block. + */ + let randao_mix = act_state.randao_mix ^ block.randao_reveal; + + Ok(ActiveState { + pending_attestations, + pending_specials, + recent_block_hashes, + randao_mix, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use types::SpecialRecord; + + fn empty_active_state() -> ActiveState { + ActiveState { + pending_attestations: vec![], + pending_specials: vec![], + recent_block_hashes: vec![], + randao_mix: Hash256::zero(), + } + } + + #[test] + fn test_extend_active_state_minimal() { + let mut act_state = empty_active_state(); + + let parent_hash = Hash256::from("parent_hash".as_bytes()); + act_state.recent_block_hashes = vec![parent_hash]; + + let block = BeaconBlock::zero(); + let block_hash = Hash256::from("block_hash".as_bytes()); + + let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap(); + + assert_eq!(new_act_state.pending_attestations, vec![]); + assert_eq!(new_act_state.pending_specials, vec![]); + assert_eq!(new_act_state.recent_block_hashes, vec![block_hash]); + assert_eq!(new_act_state.randao_mix, Hash256::zero()); + } + + #[test] + fn test_extend_active_state_specials() { + let mut act_state = empty_active_state(); + + let parent_hash = Hash256::from("parent_hash".as_bytes()); + act_state.recent_block_hashes = vec![parent_hash]; + + let mut block = BeaconBlock::zero(); + let special = SpecialRecord { + kind: 0, + data: vec![42, 42], + }; + + block.specials.push(special.clone()); + + let block_hash = Hash256::from("block_hash".as_bytes()); + + let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap(); + + assert_eq!(new_act_state.pending_attestations, vec![]); + assert_eq!(new_act_state.pending_specials, vec![special.clone()]); + assert_eq!(new_act_state.recent_block_hashes, vec![block_hash]); + assert_eq!(new_act_state.randao_mix, Hash256::zero()); + + let new_new_act_state = extend_active_state(&new_act_state, &block, &block_hash).unwrap(); + + assert_eq!(new_new_act_state.pending_attestations, vec![]); + assert_eq!( + new_new_act_state.pending_specials, + vec![special.clone(), special.clone()] + ); + assert_eq!(new_new_act_state.recent_block_hashes, vec![block_hash]); + assert_eq!(new_new_act_state.randao_mix, Hash256::zero()); + } + + #[test] + fn test_extend_active_state_empty_recent_block_hashes() { + let act_state = empty_active_state(); + + let block = BeaconBlock::zero(); + + let block_hash = Hash256::from("block_hash".as_bytes()); + + let result = extend_active_state(&act_state, &block, &block_hash); + + assert_eq!(result, Err(StateTransitionError::InvalidParentHashes)); + } + + #[test] + fn test_extend_active_recent_block_hashes() { + let mut act_state = empty_active_state(); + + let parent_hashes = vec![ + Hash256::from("one".as_bytes()), + Hash256::from("two".as_bytes()), + Hash256::from("three".as_bytes()), + ]; + act_state.recent_block_hashes = parent_hashes.clone(); + + let block = BeaconBlock::zero(); + + let block_hash = Hash256::from("four".as_bytes()); + + let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap(); + + assert_eq!(new_act_state.pending_attestations, vec![]); + assert_eq!(new_act_state.pending_specials, vec![]); + assert_eq!( + new_act_state.recent_block_hashes, + vec![ + Hash256::from("two".as_bytes()), + Hash256::from("three".as_bytes()), + Hash256::from("four".as_bytes()), + ] + ); + assert_eq!(new_act_state.randao_mix, Hash256::zero()); + } + + #[test] + fn test_extend_active_state_randao() { + let mut act_state = empty_active_state(); + + let parent_hash = Hash256::from("parent_hash".as_bytes()); + act_state.recent_block_hashes = vec![parent_hash]; + + act_state.randao_mix = Hash256::from(0b00000000); + + let mut block = BeaconBlock::zero(); + block.randao_reveal = Hash256::from(0b00000001); + + let block_hash = Hash256::from("block_hash".as_bytes()); + + let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap(); + + assert_eq!(new_act_state.pending_attestations, vec![]); + assert_eq!(new_act_state.pending_specials, vec![]); + assert_eq!(new_act_state.recent_block_hashes, vec![block_hash]); + assert_eq!(new_act_state.randao_mix, Hash256::from(0b00000001)); + } +} diff --git a/beacon_chain/types/src/active_state.rs b/beacon_chain/types/src/active_state.rs index b31f8447f..cd5548cef 100644 --- a/beacon_chain/types/src/active_state.rs +++ b/beacon_chain/types/src/active_state.rs @@ -1,8 +1,5 @@ use super::Hash256; -use super::{ - AttestationRecord, - SpecialRecord, -}; +use super::{AttestationRecord, SpecialRecord}; #[derive(Debug, PartialEq)] pub struct ActiveState { @@ -11,3 +8,10 @@ pub struct ActiveState { pub recent_block_hashes: Vec, pub randao_mix: Hash256, } + +impl ActiveState { + // TODO: implement this. + pub fn canonical_root(&self) -> Hash256 { + Hash256::zero() + } +} diff --git a/beacon_chain/types/src/crystallized_state.rs b/beacon_chain/types/src/crystallized_state.rs index 71d31fe20..ff81202cf 100644 --- a/beacon_chain/types/src/crystallized_state.rs +++ b/beacon_chain/types/src/crystallized_state.rs @@ -1,9 +1,8 @@ -use super::validator_record::ValidatorRecord; use super::crosslink_record::CrosslinkRecord; use super::shard_and_committee::ShardAndCommittee; +use super::validator_record::ValidatorRecord; use super::Hash256; - #[derive(Debug, PartialEq)] pub struct CrystallizedState { pub validator_set_change_slot: u64, @@ -20,3 +19,10 @@ pub struct CrystallizedState { pub post_fork_version: u32, pub fork_slot_number: u32, } + +impl CrystallizedState { + // TODO: implement this. + pub fn canonical_root(&self) -> Hash256 { + Hash256::zero() + } +} diff --git a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs index d466d3abc..de70e5c35 100644 --- a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs +++ b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs @@ -40,6 +40,7 @@ const CRYSTALLIZED_STATE_BYTES: usize = HASH_SIZE; #[derive(Debug, PartialEq)] pub struct SszBeaconBlock<'a> { ssz: &'a [u8], + block_ssz_len: usize, // Ancestors ancestors_position: usize, ancestors_len: usize, @@ -117,6 +118,7 @@ impl<'a> SszBeaconBlock<'a> { Ok(Self{ ssz: &untrimmed_ssz[0..block_ssz_len], + block_ssz_len, ancestors_position, ancestors_len, attestations_position, @@ -129,9 +131,16 @@ impl<'a> SszBeaconBlock<'a> { pub fn len(&self) -> usize { self.ssz.len() } pub fn is_empty(&self) -> bool { self.ssz.is_empty() } + /// Returns this block as ssz. + /// + /// Does not include any excess ssz bytes that were supplied to this struct. + pub fn block_ssz(&self) -> &'a [u8] { + &self.ssz[0..self.block_ssz_len] + } + /// Return the canonical hash for this block. pub fn block_hash(&self) -> Vec { - canonical_hash(self.ssz) + canonical_hash(&self.ssz) } /// Return the bytes representing `ancestor_hashes[0]`. diff --git a/beacon_chain/validation/src/block_validation.rs b/beacon_chain/validation/src/block_validation.rs index 492f5f13a..7898e17c8 100644 --- a/beacon_chain/validation/src/block_validation.rs +++ b/beacon_chain/validation/src/block_validation.rs @@ -2,49 +2,17 @@ extern crate rayon; use self::rayon::prelude::*; -use std::sync::{ - Arc, - RwLock, -}; -use super::attestation_validation::{ - AttestationValidationContext, - AttestationValidationError, -}; -use super::types::{ - AttestationRecord, - AttesterMap, - BeaconBlock, - ProposerMap, -}; +use super::attestation_validation::{AttestationValidationContext, AttestationValidationError}; +use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; +use super::db::{ClientDB, DBError}; +use super::ssz::{Decodable, DecodeError}; use super::ssz_helpers::attestation_ssz_splitter::{ - split_one_attestation, - split_all_attestations, - AttestationSplitError, -}; -use super::ssz_helpers::ssz_beacon_block::{ - SszBeaconBlock, - SszBeaconBlockError, -}; -use super::db::{ - ClientDB, - DBError, -}; -use super::db::stores::{ - BeaconBlockStore, - PoWChainStore, - ValidatorStore, -}; -use super::ssz::{ - Decodable, - DecodeError, + split_all_attestations, split_one_attestation, AttestationSplitError, }; +use super::ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; use super::types::Hash256; - -#[derive(Debug, PartialEq)] -pub enum BeaconBlockStatus { - NewBlock, - KnownBlock, -} +use super::types::{AttestationRecord, AttesterMap, BeaconBlock, ProposerMap}; +use std::sync::{Arc, RwLock}; #[derive(Debug, PartialEq)] pub enum SszBeaconBlockValidationError { @@ -67,7 +35,8 @@ pub enum SszBeaconBlockValidationError { /// The context against which a block should be validated. pub struct BeaconBlockValidationContext - where T: ClientDB + Sized +where + T: ClientDB + Sized, { /// The slot as determined by the system time. pub present_slot: u64, @@ -94,7 +63,8 @@ pub struct BeaconBlockValidationContext } impl BeaconBlockValidationContext - where T: ClientDB +where + T: ClientDB, { /// Validate some SszBeaconBlock against a block validation context. An SszBeaconBlock varies from a BeaconBlock in /// that is a read-only structure that reads directly from encoded SSZ. @@ -109,19 +79,13 @@ impl BeaconBlockValidationContext /// Note: this function does not implement randao_reveal checking as it is not in the /// specification. #[allow(dead_code)] - pub fn validate_ssz_block(&self, block_hash: &Hash256, b: &SszBeaconBlock) - -> Result<(BeaconBlockStatus, Option), SszBeaconBlockValidationError> - where T: ClientDB + Sized + pub fn validate_ssz_block( + &self, + b: &SszBeaconBlock, + ) -> Result + where + T: ClientDB + Sized, { - - /* - * If this block is already known, return immediately and indicate the the block is - * known. Don't attempt to deserialize the block. - */ - if self.block_store.block_exists(&block_hash)? { - return Ok((BeaconBlockStatus::KnownBlock, None)); - } - /* * If the block slot corresponds to a slot in the future, return immediately with an error. * @@ -173,11 +137,8 @@ impl BeaconBlockValidationContext * The first attestation must be validated separately as it must contain a signature of the * proposer of the previous block (this is checked later in this function). */ - let (first_attestation_ssz, next_index) = split_one_attestation( - &attestations_ssz, - 0)?; - let (first_attestation, _) = AttestationRecord::ssz_decode( - &first_attestation_ssz, 0)?; + let (first_attestation_ssz, next_index) = split_one_attestation(&attestations_ssz, 0)?; + let (first_attestation, _) = AttestationRecord::ssz_decode(&first_attestation_ssz, 0)?; /* * The first attestation may not have oblique hashes. @@ -197,7 +158,8 @@ impl BeaconBlockValidationContext * * Also, read the slot from the parent block for later use. */ - let parent_hash = b.parent_hash() + let parent_hash = b + .parent_hash() .ok_or(SszBeaconBlockValidationError::BadAncestorHashesSsz)?; let parent_block_slot = match self.block_store.get_serialized_block(&parent_hash)? { None => return Err(SszBeaconBlockValidationError::UnknownParentHash), @@ -233,8 +195,8 @@ impl BeaconBlockValidationContext /* * Validate this first attestation. */ - let attestation_voters = attestation_validation_context - .validate_attestation(&first_attestation)?; + let attestation_voters = + attestation_validation_context.validate_attestation(&first_attestation)?; /* * Attempt to read load the parent block proposer from the proposer map. Return with an @@ -243,7 +205,9 @@ impl BeaconBlockValidationContext * If the signature of proposer for the parent slot was not present in the first (0'th) * attestation of this block, reject the block. */ - let parent_block_proposer = self.proposer_map.get(&parent_block_slot) + let parent_block_proposer = self + .proposer_map + .get(&parent_block_slot) .ok_or(SszBeaconBlockValidationError::BadProposerMap)?; if !attestation_voters.contains(&parent_block_proposer) { return Err(SszBeaconBlockValidationError::NoProposerSignature); @@ -253,8 +217,7 @@ impl BeaconBlockValidationContext * Split the remaining attestations into a vector of slices, each containing * a single serialized attestation record. */ - let other_attestations = split_all_attestations(attestations_ssz, - next_index)?; + let other_attestations = split_all_attestations(attestations_ssz, next_index)?; /* * Verify each other AttestationRecord. @@ -278,7 +241,7 @@ impl BeaconBlockValidationContext */ match failure.read() { Ok(ref option) if option.is_none() => (), - _ => return None + _ => return None, } /* * If there has not been a failure yet, attempt to serialize and validate the @@ -317,22 +280,18 @@ impl BeaconBlockValidationContext /* * Attestation validation succeded. */ - Ok(_) => Some(attestation) + Ok(_) => Some(attestation), } } } - }) - .collect(); + }).collect(); match failure.into_inner() { Err(_) => return Err(SszBeaconBlockValidationError::RwLockPoisoned), - Ok(failure) => { - match failure { - Some(error) => return Err(error), - _ => () - } - - } + Ok(failure) => match failure { + Some(error) => return Err(error), + _ => (), + }, } /* @@ -360,7 +319,7 @@ impl BeaconBlockValidationContext attestations: deserialized_attestations, specials, }; - Ok((BeaconBlockStatus::NewBlock, Some(block))) + Ok(block) } } @@ -373,8 +332,7 @@ impl From for SszBeaconBlockValidationError { impl From for SszBeaconBlockValidationError { fn from(e: AttestationSplitError) -> Self { match e { - AttestationSplitError::TooShort => - SszBeaconBlockValidationError::BadAttestationSsz + AttestationSplitError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, } } } @@ -382,10 +340,12 @@ impl From for SszBeaconBlockValidationError { impl From for SszBeaconBlockValidationError { fn from(e: SszBeaconBlockError) -> Self { match e { - SszBeaconBlockError::TooShort => - SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()), - SszBeaconBlockError::TooLong => - SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()), + SszBeaconBlockError::TooShort => { + SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) + } + SszBeaconBlockError::TooLong => { + SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) + } } } } @@ -393,10 +353,8 @@ impl From for SszBeaconBlockValidationError { impl From for SszBeaconBlockValidationError { fn from(e: DecodeError) -> Self { match e { - DecodeError::TooShort => - SszBeaconBlockValidationError::BadAttestationSsz, - DecodeError::TooLong => - SszBeaconBlockValidationError::BadAttestationSsz, + DecodeError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, + DecodeError::TooLong => SszBeaconBlockValidationError::BadAttestationSsz, } } } diff --git a/beacon_chain/validation/tests/block_validation/helpers.rs b/beacon_chain/validation/tests/block_validation/helpers.rs index 7fd0c364a..40db77a85 100644 --- a/beacon_chain/validation/tests/block_validation/helpers.rs +++ b/beacon_chain/validation/tests/block_validation/helpers.rs @@ -1,35 +1,14 @@ use std::sync::Arc; -use super::attestation_validation::helpers::{ - generate_attestation, - insert_justified_block_hash, -}; -use super::bls::{ - Keypair, -}; -use super::db::{ - MemoryDB, -}; -use super::db::stores::{ - BeaconBlockStore, - PoWChainStore, - ValidatorStore, -}; -use super::types::{ - AttestationRecord, - AttesterMap, - BeaconBlock, - Hash256, - ProposerMap, -}; +use super::attestation_validation::helpers::{generate_attestation, insert_justified_block_hash}; +use super::bls::Keypair; +use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; +use super::db::MemoryDB; +use super::ssz::SszStream; use super::ssz_helpers::ssz_beacon_block::SszBeaconBlock; +use super::types::{AttestationRecord, AttesterMap, BeaconBlock, Hash256, ProposerMap}; use super::validation::block_validation::{ - BeaconBlockValidationContext, - SszBeaconBlockValidationError, - BeaconBlockStatus, -}; -use super::ssz::{ - SszStream, + BeaconBlockValidationContext, SszBeaconBlockValidationError, }; #[derive(Debug)] @@ -74,9 +53,15 @@ type ParentHashes = Vec; /// Setup for a block validation function, without actually executing the /// block validation function. -pub fn setup_block_validation_scenario(params: &BeaconBlockTestParams) - -> (BeaconBlock, ParentHashes, AttesterMap, ProposerMap, TestStore) -{ +pub fn setup_block_validation_scenario( + params: &BeaconBlockTestParams, +) -> ( + BeaconBlock, + ParentHashes, + AttesterMap, + ProposerMap, + TestStore, +) { let stores = TestStore::new(); let cycle_length = params.cycle_length; @@ -100,7 +85,10 @@ pub fn setup_block_validation_scenario(params: &BeaconBlockTestParams) /* * Store a valid PoW chain ref */ - stores.pow_chain.put_block_hash(pow_chain_ref.as_ref()).unwrap(); + stores + .pow_chain + .put_block_hash(pow_chain_ref.as_ref()) + .unwrap(); /* * Generate a minimum viable parent block and store it in the database. @@ -110,7 +98,10 @@ pub fn setup_block_validation_scenario(params: &BeaconBlockTestParams) parent_block.slot = block_slot - 1; parent_block.attestations.push(parent_attestation); let parent_block_ssz = serialize_block(&parent_block); - stores.block.put_serialized_block(parent_hash.as_ref(), &parent_block_ssz).unwrap(); + stores + .block + .put_serialized_block(parent_hash.as_ref(), &parent_block_ssz) + .unwrap(); let proposer_map = { let mut proposer_map = ProposerMap::new(); @@ -132,24 +123,28 @@ pub fn setup_block_validation_scenario(params: &BeaconBlockTestParams) &mut parent_hashes, &justified_block_hash, block_slot, - attestation_slot); + attestation_slot, + ); /* * For each shard in this slot, generate an attestation. */ for shard in 0..shards_per_slot { - let mut signing_keys = vec![]; - let mut attesters = vec![]; - /* - * Generate a random keypair for each validator and clone it into the - * list of keypairs. Store it in the database. - */ + let mut signing_keys = vec![]; + let mut attesters = vec![]; + /* + * Generate a random keypair for each validator and clone it into the + * list of keypairs. Store it in the database. + */ for _ in 0..validators_per_shard { - let keypair = Keypair::random(); - keypairs.push(keypair.clone()); - stores.validator.put_public_key_by_index(i, &keypair.pk).unwrap(); - signing_keys.push(Some(keypair.sk.clone())); - attesters.push(i); - i += 1; + let keypair = Keypair::random(); + keypairs.push(keypair.clone()); + stores + .validator + .put_public_key_by_index(i, &keypair.pk) + .unwrap(); + signing_keys.push(Some(keypair.sk.clone())); + attesters.push(i); + i += 1; } attester_map.insert((attestation_slot, shard), attesters); @@ -163,7 +158,8 @@ pub fn setup_block_validation_scenario(params: &BeaconBlockTestParams) cycle_length, &parent_hashes, &signing_keys[..], - &stores.block); + &stores.block, + ); attestations.push(attestation); } (attester_map, attestations, keypairs) @@ -180,11 +176,7 @@ pub fn setup_block_validation_scenario(params: &BeaconBlockTestParams) specials: vec![], }; - (block, - parent_hashes, - attester_map, - proposer_map, - stores) + (block, parent_hashes, attester_map, proposer_map, stores) } /// Helper function to take some BeaconBlock and SSZ serialize it. @@ -199,25 +191,20 @@ pub fn serialize_block(b: &BeaconBlock) -> Vec { /// Returns the Result returned from the block validation function. pub fn run_block_validation_scenario( params: &BeaconBlockTestParams, - mutator_func: F) - -> Result<(BeaconBlockStatus, Option), SszBeaconBlockValidationError> - where F: FnOnce(BeaconBlock, AttesterMap, ProposerMap, TestStore) - -> (BeaconBlock, AttesterMap, ProposerMap, TestStore) + mutator_func: F, +) -> Result +where + F: FnOnce(BeaconBlock, AttesterMap, ProposerMap, TestStore) + -> (BeaconBlock, AttesterMap, ProposerMap, TestStore), { - let (block, - parent_hashes, - attester_map, - proposer_map, - stores) = setup_block_validation_scenario(¶ms); + let (block, parent_hashes, attester_map, proposer_map, stores) = + setup_block_validation_scenario(¶ms); - let (block, - attester_map, - proposer_map, - stores) = mutator_func(block, attester_map, proposer_map, stores); + let (block, attester_map, proposer_map, stores) = + mutator_func(block, attester_map, proposer_map, stores); let ssz_bytes = serialize_block(&block); - let ssz_block = SszBeaconBlock::from_slice(&ssz_bytes[..]) - .unwrap(); + let ssz_block = SszBeaconBlock::from_slice(&ssz_bytes[..]).unwrap(); let context = BeaconBlockValidationContext { present_slot: params.validation_context_slot, @@ -230,17 +217,17 @@ pub fn run_block_validation_scenario( attester_map: Arc::new(attester_map), block_store: stores.block.clone(), validator_store: stores.validator.clone(), - pow_store: stores.pow_chain.clone() + pow_store: stores.pow_chain.clone(), }; let block_hash = Hash256::from(&ssz_block.block_hash()[..]); - let validation_status = context.validate_ssz_block(&block_hash, &ssz_block); + let validation_result = context.validate_ssz_block(&ssz_block); /* * If validation returned a block, make sure it's the same block we supplied to it. * * I.e., there were no errors during the serialization -> deserialization process. */ - if let Ok((_, Some(returned_block))) = &validation_status { + if let Ok(returned_block) = &validation_result { assert_eq!(*returned_block, block); }; - validation_status + validation_result } diff --git a/beacon_chain/validation/tests/block_validation/tests.rs b/beacon_chain/validation/tests/block_validation/tests.rs index 0c0cba0f3..cdbe14498 100644 --- a/beacon_chain/validation/tests/block_validation/tests.rs +++ b/beacon_chain/validation/tests/block_validation/tests.rs @@ -1,26 +1,12 @@ -use super::bls::{ - AggregateSignature, -}; +use super::bls::AggregateSignature; +use super::hashing::canonical_hash; use super::helpers::{ - BeaconBlockTestParams, - TestStore, - run_block_validation_scenario, - serialize_block, -}; -use super::types::{ - BeaconBlock, - Hash256, - ProposerMap, + run_block_validation_scenario, serialize_block, BeaconBlockTestParams, TestStore, }; use super::ssz_helpers::ssz_beacon_block::SszBeaconBlock; -use super::validation::block_validation::{ - SszBeaconBlockValidationError, - BeaconBlockStatus, -}; -use super::validation::attestation_validation::{ - AttestationValidationError, -}; -use super::hashing::canonical_hash; +use super::types::{BeaconBlock, Hash256, ProposerMap}; +use super::validation::attestation_validation::AttestationValidationError; +use super::validation::block_validation::SszBeaconBlockValidationError; fn get_simple_params() -> BeaconBlockTestParams { let validators_per_shard: usize = 5; @@ -66,11 +52,9 @@ fn test_block_validation_valid() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status.unwrap().0, BeaconBlockStatus::NewBlock); + assert!(status.is_ok()) } #[test] @@ -83,15 +67,21 @@ fn test_block_validation_valid_known_block() { */ let block_ssz = serialize_block(&block); let block_hash = canonical_hash(&block_ssz); - stores.block.put_serialized_block(&block_hash, &block_ssz).unwrap(); + stores + .block + .put_serialized_block(&block_hash, &block_ssz) + .unwrap(); (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status.unwrap(), (BeaconBlockStatus::KnownBlock, None)); + /* + * This function does _not_ check if a block is already known. + * + * Known blocks will appear as valid blocks. + */ + assert!(status.is_ok()) } #[test] @@ -103,11 +93,12 @@ fn test_block_validation_parent_slot_too_high() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot)); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot) + ); } #[test] @@ -119,9 +110,7 @@ fn test_block_validation_invalid_future_slot() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); assert_eq!(status, Err(SszBeaconBlockValidationError::FutureSlot)); } @@ -131,8 +120,8 @@ fn test_block_validation_invalid_slot_already_finalized() { let mut params = get_simple_params(); params.validation_context_finalized_slot = params.block_slot; - params.validation_context_justified_slot = params.validation_context_finalized_slot + - u64::from(params.cycle_length); + params.validation_context_justified_slot = + params.validation_context_finalized_slot + u64::from(params.cycle_length); let mutator = |block, attester_map, proposer_map, stores| { /* @@ -141,11 +130,12 @@ fn test_block_validation_invalid_slot_already_finalized() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::SlotAlreadyFinalized)); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::SlotAlreadyFinalized) + ); } #[test] @@ -157,11 +147,12 @@ fn test_block_validation_invalid_unknown_pow_hash() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::UnknownPoWChainRef)); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::UnknownPoWChainRef) + ); } #[test] @@ -173,11 +164,12 @@ fn test_block_validation_invalid_unknown_parent_hash() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::UnknownParentHash)); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::UnknownParentHash) + ); } #[test] @@ -192,36 +184,44 @@ fn test_block_validation_invalid_1st_attestation_signature() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::AttestationValidationError( - AttestationValidationError::BadAggregateSignature))); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::AttestationValidationError( + AttestationValidationError::BadAggregateSignature + )) + ); } #[test] fn test_block_validation_invalid_no_parent_proposer_signature() { let params = get_simple_params(); - let mutator = |block: BeaconBlock, attester_map, mut proposer_map: ProposerMap, stores: TestStore| { - /* - * Set the proposer for this slot to be a validator that does not exist. - */ - let ssz = { - let parent_hash = block.parent_hash().unwrap().as_ref(); - stores.block.get_serialized_block(parent_hash).unwrap().unwrap() + let mutator = + |block: BeaconBlock, attester_map, mut proposer_map: ProposerMap, stores: TestStore| { + /* + * Set the proposer for this slot to be a validator that does not exist. + */ + let ssz = { + let parent_hash = block.parent_hash().unwrap().as_ref(); + stores + .block + .get_serialized_block(parent_hash) + .unwrap() + .unwrap() + }; + let parent_block_slot = SszBeaconBlock::from_slice(&ssz[..]).unwrap().slot(); + proposer_map.insert(parent_block_slot, params.total_validators + 1); + (block, attester_map, proposer_map, stores) }; - let parent_block_slot = SszBeaconBlock::from_slice(&ssz[..]).unwrap().slot(); - proposer_map.insert(parent_block_slot, params.total_validators + 1); - (block, attester_map, proposer_map, stores) - }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::NoProposerSignature)); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::NoProposerSignature) + ); } #[test] @@ -236,9 +236,7 @@ fn test_block_validation_invalid_bad_proposer_map() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); assert_eq!(status, Err(SszBeaconBlockValidationError::BadProposerMap)); } @@ -255,10 +253,12 @@ fn test_block_validation_invalid_2nd_attestation_signature() { (block, attester_map, proposer_map, stores) }; - let status = run_block_validation_scenario( - ¶ms, - mutator); + let status = run_block_validation_scenario(¶ms, mutator); - assert_eq!(status, Err(SszBeaconBlockValidationError::AttestationValidationError( - AttestationValidationError::BadAggregateSignature))); + assert_eq!( + status, + Err(SszBeaconBlockValidationError::AttestationValidationError( + AttestationValidationError::BadAggregateSignature + )) + ); } diff --git a/lighthouse/db/src/stores/beacon_block_store.rs b/lighthouse/db/src/stores/beacon_block_store.rs index 81c916b31..4ea2882be 100644 --- a/lighthouse/db/src/stores/beacon_block_store.rs +++ b/lighthouse/db/src/stores/beacon_block_store.rs @@ -1,14 +1,9 @@ extern crate ssz_helpers; -use self::ssz_helpers::ssz_beacon_block::{ - SszBeaconBlock, -}; -use std::sync::Arc; -use super::{ - ClientDB, - DBError, -}; +use self::ssz_helpers::ssz_beacon_block::SszBeaconBlock; use super::BLOCKS_DB_COLUMN as DB_COLUMN; +use super::{ClientDB, DBError}; +use std::sync::Arc; type BeaconBlockHash = Vec; type BeaconBlockSsz = Vec; @@ -21,36 +16,33 @@ pub enum BeaconBlockAtSlotError { } pub struct BeaconBlockStore - where T: ClientDB +where + T: ClientDB, { db: Arc, } impl BeaconBlockStore { pub fn new(db: Arc) -> Self { - Self { - db, - } + Self { db } } - pub fn put_serialized_block(&self, hash: &[u8], ssz: &[u8]) - -> Result<(), DBError> - { + pub fn put_serialized_block(&self, hash: &[u8], ssz: &[u8]) -> Result<(), DBError> { self.db.put(DB_COLUMN, hash, ssz) } - pub fn get_serialized_block(&self, hash: &[u8]) - -> Result>, DBError> - { + pub fn get_serialized_block(&self, hash: &[u8]) -> Result>, DBError> { self.db.get(DB_COLUMN, hash) } - pub fn block_exists(&self, hash: &[u8]) - -> Result - { + pub fn block_exists(&self, hash: &[u8]) -> Result { self.db.exists(DB_COLUMN, hash) } + pub fn delete_block(&self, hash: &[u8]) -> Result<(), DBError> { + self.db.delete(DB_COLUMN, hash) + } + /// Retrieve the block at a slot given a "head_hash" and a slot. /// /// A "head_hash" must be a block hash with a slot number greater than or equal to the desired @@ -60,9 +52,11 @@ impl BeaconBlockStore { /// slot number. If the slot is skipped, the function will return None. /// /// If a block is found, a tuple of (block_hash, serialized_block) is returned. - pub fn block_at_slot(&self, head_hash: &[u8], slot: u64) - -> Result, BeaconBlockAtSlotError> - { + pub fn block_at_slot( + &self, + head_hash: &[u8], + slot: u64, + ) -> Result, BeaconBlockAtSlotError> { match self.get_serialized_block(head_hash)? { None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock), Some(ssz) => { @@ -71,12 +65,10 @@ impl BeaconBlockStore { match block.slot() { s if s == slot => Ok(Some((head_hash.to_vec(), ssz.to_vec()))), s if s < slot => Ok(None), - _ => { - match block.parent_hash() { - Some(parent_hash) => self.block_at_slot(parent_hash, slot), - None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock) - } - } + _ => match block.parent_hash() { + Some(parent_hash) => self.block_at_slot(parent_hash, slot), + None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock), + }, } } } @@ -94,15 +86,15 @@ mod tests { extern crate ssz; extern crate types; - use self::types::beacon_block::BeaconBlock; - use self::types::attestation_record::AttestationRecord; - use self::types::Hash256; use self::ssz::SszStream; + use self::types::attestation_record::AttestationRecord; + use self::types::beacon_block::BeaconBlock; + use self::types::Hash256; - use super::*; use super::super::super::MemoryDB; - use std::thread; + use super::*; use std::sync::Arc; + use std::thread; #[test] fn test_block_store_on_memory_db() { @@ -148,13 +140,12 @@ mod tests { let db = Arc::new(MemoryDB::open()); let bs = Arc::new(BeaconBlockStore::new(db.clone())); - let blocks = (0..5).into_iter() - .map(|_| { - let mut block = BeaconBlock::zero(); - let ar = AttestationRecord::zero(); - block.attestations.push(ar); - block - }); + let blocks = (0..5).into_iter().map(|_| { + let mut block = BeaconBlock::zero(); + let ar = AttestationRecord::zero(); + block.attestations.push(ar); + block + }); let hashes = [ Hash256::from("zero".as_bytes()),