diff --git a/beacon_chain/chain/Cargo.toml b/beacon_chain/chain/Cargo.toml index fd20d8100..dc4053cbb 100644 --- a/beacon_chain/chain/Cargo.toml +++ b/beacon_chain/chain/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Paul Hauner "] [dependencies] bls = { path = "../utils/bls" } +db = { path = "../../lighthouse/db" } +ssz_helpers = { path = "../utils/ssz_helpers" } types = { path = "../types" } +validation = { path = "../validation" } validator_induction = { path = "../validator_induction" } validator_shuffling = { path = "../validator_shuffling" } diff --git a/beacon_chain/chain/src/blocks.rs b/beacon_chain/chain/src/blocks.rs new file mode 100644 index 000000000..2ff8a245d --- /dev/null +++ b/beacon_chain/chain/src/blocks.rs @@ -0,0 +1,135 @@ +extern crate ssz_helpers; +extern crate validation; + +use db::{ + ClientDB, +}; +use db::stores::{ + BeaconBlockAtSlotError, +}; +use self::validation::block_validation::{ + BeaconBlockValidationContext, + SszBeaconBlockValidationError, +}; +use super::{ + BeaconChain, + BeaconChainError, +}; +use self::ssz_helpers::ssz_beacon_block::{ + SszBeaconBlock, + SszBeaconBlockError, +}; +use std::sync::Arc; +use types::{ + BeaconBlock, + Hash256, +}; + +pub use self::validation::block_validation::BeaconBlockStatus; + +pub enum BeaconChainBlockError { + UnknownCrystallizedState, + UnknownActiveState, + UnknownAttesterProposerMaps, + NoParentHash, + UnknownJustifiedBlock, + BlockAlreadyKnown, + BlockSlotLookupError(BeaconBlockAtSlotError), + BadSsz(SszBeaconBlockError), + BlockValidationError(SszBeaconBlockValidationError), + DBError(String), +} + +impl From for BeaconChainBlockError { + fn from(e: BeaconBlockAtSlotError) -> BeaconChainBlockError { + BeaconChainBlockError::BlockSlotLookupError(e) + } +} + +impl From for BeaconChainBlockError { + fn from(e: SszBeaconBlockValidationError) -> BeaconChainBlockError { + BeaconChainBlockError::BlockValidationError(e) + } +} + +pub type BlockStatusTriple = (BeaconBlockStatus, Hash256, BeaconBlock); + + +impl BeaconChain + where T: ClientDB + Sized +{ + pub fn process_incoming_block(&self, ssz: &[u8], rx_time: u64) + -> Result + { + /* + * Generate a SszBlock to read directly from the serialized SSZ. + */ + let block = SszBeaconBlock::from_slice(ssz)?; + let block_hash = Hash256::from(&block.block_hash()[..]); + + /* + * 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) + .ok_or(BeaconChainBlockError::UnknownCrystallizedState)?; + + /* + * Load the active state for this block from our caches. + * + * 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) + .ok_or(BeaconChainBlockError::UnknownActiveState)?; + + /* + * Learn the last justified slot from the crystallized state and load + * the hash of this block from the database + */ + let last_justified_slot = cry_state.last_justified_slot; + let parent_block_hash = block.parent_hash() + .ok_or(BeaconChainBlockError::NoParentHash)?; + let (last_justified_block_hash, _) = self.store.block.block_at_slot( + &parent_block_hash, last_justified_slot)? + .ok_or(BeaconChainBlockError::UnknownJustifiedBlock)?; + + /* + * Load the attester and proposer maps for the crystallized state. + */ + let (attester_map, proposer_map) = self.attester_proposer_maps.get(&cry_state_root) + .ok_or(BeaconChainBlockError::UnknownAttesterProposerMaps)?; + + let present_slot = 100; // TODO: fix this + + /* + * Build a block validation context to test the block against. + */ + let validation_context = BeaconBlockValidationContext { + present_slot, + cycle_length: self.config.cycle_length, + last_justified_slot: cry_state.last_justified_slot, + last_justified_block_hash: Hash256::from(&last_justified_block_hash[..]), + last_finalized_slot: self.last_finalized_slot, + recent_block_hashes: Arc::new(act_state.recent_block_hashes.clone()), + proposer_map: proposer_map.clone(), + attester_map: attester_map.clone(), + block_store: self.store.block.clone(), + validator_store: self.store.validator.clone(), + pow_store: self.store.pow_chain.clone(), + }; + let (block_status, deserialized_block) = validation_context.validate_ssz_block(&block_hash, &block)?; + match deserialized_block { + Some(b) => Ok((block_status, block_hash, b)), + None => Err(BeaconChainBlockError::BlockAlreadyKnown) + } + } +} + +impl From for BeaconChainBlockError { + fn from(e: SszBeaconBlockError) -> BeaconChainBlockError { + BeaconChainBlockError::BadSsz(e) + } +} diff --git a/beacon_chain/chain/src/genesis.rs b/beacon_chain/chain/src/genesis.rs index 54ff7969f..646c18894 100644 --- a/beacon_chain/chain/src/genesis.rs +++ b/beacon_chain/chain/src/genesis.rs @@ -6,7 +6,6 @@ use types::{ use super::{ ActiveState, CrystallizedState, - BeaconChain, BeaconChainError, ChainConfig, }; @@ -24,89 +23,87 @@ impl From for BeaconChainError { } } -impl BeaconChain { - /// Initialize a new ChainHead with genesis parameters. - /// - /// Used when syncing a chain from scratch. - pub fn genesis_states(config: &ChainConfig) - -> Result<(ActiveState, CrystallizedState), ValidatorAssignmentError> - { - /* - * Parse the ValidatorRegistrations into ValidatorRecords and induct them. - * - * Ignore any records which fail proof-of-possession or are invalid. - */ - let validators = { - let mut inductor = ValidatorInductor::new(0, config.shard_count, vec![]); - for registration in &config.initial_validators { - let _ = inductor.induct(®istration); - }; - inductor.to_vec() +/// Initialize a new ChainHead with genesis parameters. +/// +/// Used when syncing a chain from scratch. +pub fn genesis_states(config: &ChainConfig) + -> Result<(ActiveState, CrystallizedState), ValidatorAssignmentError> +{ + /* + * Parse the ValidatorRegistrations into ValidatorRecords and induct them. + * + * Ignore any records which fail proof-of-possession or are invalid. + */ + let validators = { + let mut inductor = ValidatorInductor::new(0, config.shard_count, vec![]); + for registration in &config.initial_validators { + let _ = inductor.induct(®istration); }; + inductor.to_vec() + }; - /* - * Assign the validators to shards, using all zeros as the seed. - * - * Crystallizedstate stores two cycles, so we simply repeat the same assignment twice. - */ - let shard_and_committee_for_slots = { - let mut a = shard_and_committees_for_cycle(&vec![0; 32], &validators, 0, &config)?; - let mut b = a.clone(); - a.append(&mut b); - a - }; + /* + * Assign the validators to shards, using all zeros as the seed. + * + * Crystallizedstate stores two cycles, so we simply repeat the same assignment twice. + */ + let shard_and_committee_for_slots = { + let mut a = shard_and_committees_for_cycle(&vec![0; 32], &validators, 0, &config)?; + let mut b = a.clone(); + a.append(&mut b); + a + }; - /* - * Set all the crosslink records to reference zero hashes. - */ - let crosslinks = { - let mut c = vec![]; - for _ in 0..config.shard_count { - c.push(CrosslinkRecord { - recently_changed: false, - slot: 0, - hash: Hash256::zero(), - }); - } - c - }; + /* + * Set all the crosslink records to reference zero hashes. + */ + let crosslinks = { + let mut c = vec![]; + for _ in 0..config.shard_count { + c.push(CrosslinkRecord { + recently_changed: false, + slot: 0, + hash: Hash256::zero(), + }); + } + c + }; - /* - * Initialize a genesis `Crystallizedstate` - */ - let crystallized_state = CrystallizedState { - validator_set_change_slot: 0, - validators: validators.to_vec(), - crosslinks, - last_state_recalculation_slot: 0, - last_finalized_slot: 0, - last_justified_slot: 0, - justified_streak: 0, - shard_and_committee_for_slots, - deposits_penalized_in_period: vec![], - validator_set_delta_hash_chain: Hash256::zero(), - pre_fork_version: INITIAL_FORK_VERSION, - post_fork_version: INITIAL_FORK_VERSION, - fork_slot_number: 0, - }; + /* + * Initialize a genesis `Crystallizedstate` + */ + let crystallized_state = CrystallizedState { + validator_set_change_slot: 0, + validators: validators.to_vec(), + crosslinks, + last_state_recalculation_slot: 0, + last_finalized_slot: 0, + last_justified_slot: 0, + justified_streak: 0, + shard_and_committee_for_slots, + deposits_penalized_in_period: vec![], + validator_set_delta_hash_chain: Hash256::zero(), + pre_fork_version: INITIAL_FORK_VERSION, + post_fork_version: INITIAL_FORK_VERSION, + fork_slot_number: 0, + }; - /* - * Set all recent block hashes to zero. - */ - let recent_block_hashes = vec![Hash256::zero(); config.cycle_length as usize]; + /* + * Set all recent block hashes to zero. + */ + let recent_block_hashes = vec![Hash256::zero(); config.cycle_length as usize]; - /* - * Create an active state. - */ - let active_state = ActiveState { - pending_attestations: vec![], - pending_specials: vec![], - recent_block_hashes, - randao_mix: Hash256::zero(), - }; + /* + * Create an active state. + */ + let active_state = ActiveState { + pending_attestations: vec![], + pending_specials: vec![], + recent_block_hashes, + randao_mix: Hash256::zero(), + }; - Ok((active_state, crystallized_state)) - } + Ok((active_state, crystallized_state)) } @@ -128,7 +125,7 @@ mod tests { #[test] fn test_genesis_no_validators() { let config = ChainConfig::standard(); - let (act, cry) = BeaconChain::genesis_states(&config).unwrap(); + let (act, cry) = genesis_states(&config).unwrap(); assert_eq!(cry.validator_set_change_slot, 0); assert_eq!(cry.validators.len(), 0); @@ -175,7 +172,7 @@ mod tests { config.initial_validators.push(random_registration()); } - let (_, cry) = BeaconChain::genesis_states(&config).unwrap(); + let (_, cry) = genesis_states(&config).unwrap(); assert_eq!(cry.validators.len(), validator_count); } @@ -198,7 +195,7 @@ mod tests { bad_v.withdrawal_shard = config.shard_count + 1; config.initial_validators.push(bad_v); - let (_, cry) = BeaconChain::genesis_states(&config).unwrap(); + let (_, cry) = genesis_states(&config).unwrap(); 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 808a9c641..27ce34faa 100644 --- a/beacon_chain/chain/src/lib.rs +++ b/beacon_chain/chain/src/lib.rs @@ -1,52 +1,99 @@ +extern crate db; extern crate types; extern crate validator_induction; extern crate validator_shuffling; +mod stores; +mod blocks; +mod maps; mod genesis; +use db::ClientDB; +use genesis::genesis_states; +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, }; #[derive(Debug, PartialEq)] pub enum BeaconChainError { InvalidGenesis, + InsufficientValidators, + UnableToGenerateMaps(AttesterAndProposerMapError), DBError(String), } -pub struct BeaconChain { - pub last_finalized_slot: Option, +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, + /// The hash of the head of the canonical chain. pub canonical_latest_block_hash: Hash256, + /// A vec of hashes of heads of fork (non-canonical) chains. pub fork_latest_block_hashes: Vec, + /// A map where the value is an active state the the key is its hash. pub active_states: HashMap, + /// A map where the value is crystallized state the the key is its hash. pub crystallized_states: HashMap, + /// A map of crystallized state to a proposer and attester map. + pub attester_proposer_maps: HashMap, Arc)>, + /// A collection of database stores used by the chain. + pub store: BeaconChainStore, + /// The chain configuration. pub config: ChainConfig, } -impl BeaconChain { - pub fn new(config: ChainConfig) +impl BeaconChain + where T: ClientDB + Sized +{ + pub fn new(store: BeaconChainStore, config: ChainConfig) -> Result { - let (active_state, crystallized_state) = BeaconChain::genesis_states(&config)?; + if config.initial_validators.is_empty() { + return Err(BeaconChainError::InsufficientValidators); + } + + let (active_state, crystallized_state) = genesis_states(&config)?; let canonical_latest_block_hash = Hash256::zero(); let fork_latest_block_hashes = vec![]; let mut active_states = HashMap::new(); let mut crystallized_states = HashMap::new(); + let mut attester_proposer_maps = HashMap::new(); + + let (attester_map, proposer_map) = generate_attester_and_proposer_maps( + &crystallized_state, 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))); Ok(Self{ - last_finalized_slot: None, + last_finalized_slot: 0, canonical_latest_block_hash, fork_latest_block_hashes, active_states, crystallized_states, + attester_proposer_maps, + store, config, }) } @@ -55,21 +102,32 @@ impl BeaconChain { #[cfg(test)] mod tests { + use std::sync::Arc; use super::*; use types::ValidatorRegistration; + use db::MemoryDB; + use db::stores::*; #[test] fn test_new_chain() { let mut config = ChainConfig::standard(); + config.cycle_length = 4; + config.shard_count = 4; + let db = Arc::new(MemoryDB::open()); + let store = BeaconChainStore { + block: Arc::new(BeaconBlockStore::new(db.clone())), + pow_chain: Arc::new(PoWChainStore::new(db.clone())), + validator: Arc::new(ValidatorStore::new(db.clone())), + }; - for _ in 0..4 { + for _ in 0..config.cycle_length * 2 { config.initial_validators.push(ValidatorRegistration::random()) } - let chain = BeaconChain::new(config.clone()).unwrap(); - let (act, cry) = BeaconChain::genesis_states(&config).unwrap(); + let chain = BeaconChain::new(store, config.clone()).unwrap(); + let (act, cry) = genesis_states(&config).unwrap(); - assert_eq!(chain.last_finalized_slot, None); + assert_eq!(chain.last_finalized_slot, 0); assert_eq!(chain.canonical_latest_block_hash, Hash256::zero()); let stored_act = chain.active_states.get(&Hash256::zero()).unwrap(); diff --git a/beacon_chain/chain/src/maps.rs b/beacon_chain/chain/src/maps.rs new file mode 100644 index 000000000..722cd73f8 --- /dev/null +++ b/beacon_chain/chain/src/maps.rs @@ -0,0 +1,45 @@ +use types::{ + AttesterMap, + CrystallizedState, + ProposerMap, +}; + +#[derive(Debug, PartialEq)] +pub enum AttesterAndProposerMapError { + NoShardAndCommitteeForSlot, + NoAvailableProposer, +} + +/// Generate a map of `(slot, shard) |--> committee`. +/// +/// The attester map is used to optimise the lookup of a committee. +pub fn generate_attester_and_proposer_maps(cry_state: &CrystallizedState, start_slot: u64) + -> Result<(AttesterMap, ProposerMap), AttesterAndProposerMapError> +{ + let mut attester_map = AttesterMap::new(); + let mut proposer_map = ProposerMap::new(); + for (i, slot) in cry_state.shard_and_committee_for_slots.iter().enumerate() { + /* + * Store the proposer for the block. + */ + let slot_number = (i as u64).saturating_add(start_slot); + let first_committee = { + let first_shard_and_committee = slot.get(0) + .ok_or(AttesterAndProposerMapError::NoShardAndCommitteeForSlot)?; + first_shard_and_committee.committee.clone() + }; + println!("{:?}", slot); + let proposer_index = (slot_number as usize).checked_rem(first_committee.len()) + .ok_or(AttesterAndProposerMapError::NoAvailableProposer)?; + proposer_map.insert(slot_number, proposer_index); + + /* + * Loop through the shards and extend the attester map. + */ + for shard_and_committee in slot { + let committee = shard_and_committee.committee.clone(); + attester_map.insert((slot_number, shard_and_committee.shard), committee); + } + }; + Ok((attester_map, proposer_map)) +} diff --git a/beacon_chain/chain/src/stores.rs b/beacon_chain/chain/src/stores.rs new file mode 100644 index 000000000..e5bacdda8 --- /dev/null +++ b/beacon_chain/chain/src/stores.rs @@ -0,0 +1,15 @@ +use db::{ + ClientDB, +}; +use db::stores::{ + BeaconBlockStore, + PoWChainStore, + ValidatorStore, +}; +use std::sync::Arc; + +pub struct BeaconChainStore { + pub block: Arc>, + pub pow_chain: Arc>, + pub validator: Arc>, +}