diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index a04f6b1c86..a9c8791334 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -88,15 +88,28 @@ pub trait BeaconChainTypes { type EthSpec: types::EthSpec; } +/// Represents the "Beacon Chain" component of Ethereum 2.0. Allows import of blocks and block +/// operations and chooses a canonical head. pub struct BeaconChain { + /// Persistent storage for blocks, states, etc. Typically an on-disk store, such as LevelDB. pub store: Arc, + /// Reports the current slot, typically based upon the system clock. pub slot_clock: T::SlotClock, + /// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for + /// inclusion in a block. pub op_pool: OperationPool, + /// Stores a "snapshot" of the chain at the time the head-of-the-chain block was recieved. canonical_head: RwLock>, + /// Stores a "snapshot" of the chain at the latest finalized point. finalized_head: RwLock>, + /// The same state from `self.canonical_head`, but updated at the start of each slot with a + /// skip slot if no block is recieved. This is effectively a cache that avoids repeating calls + /// to `per_slot_processing`. state: RwLock>, - pub spec: ChainSpec, + /// A state-machine that is updated with information from the network and chooses a canonical + /// head block. pub fork_choice: RwLock, + /// Stores metrics about this `BeaconChain`. pub metrics: Metrics, } @@ -138,7 +151,6 @@ impl BeaconChain { state: RwLock::new(genesis_state), finalized_head, canonical_head, - spec, fork_choice: RwLock::new(fork_choice), metrics: Metrics::new()?, }) @@ -170,7 +182,6 @@ impl BeaconChain { canonical_head: RwLock::new(p.canonical_head), finalized_head: RwLock::new(p.finalized_head), state: RwLock::new(p.state), - spec, fork_choice: RwLock::new(fork_choice), metrics: Metrics::new()?, })) @@ -309,103 +320,37 @@ impl BeaconChain { Ok(self.store.get(block_root)?) } - /// Update the canonical head to some new values. - fn update_canonical_head( - &self, - new_beacon_block: BeaconBlock, - new_beacon_block_root: Hash256, - new_beacon_state: BeaconState, - new_beacon_state_root: Hash256, - ) { - debug!( - "Updating canonical head with block at slot: {}", - new_beacon_block.slot - ); - let mut head = self.canonical_head.write(); - head.update( - new_beacon_block, - new_beacon_block_root, - new_beacon_state, - new_beacon_state_root, - ); - } + /// Update the canonical head to `new_head`. + fn update_canonical_head(&self, new_head: CheckPoint) -> Result<(), Error> { + // Update the checkpoint that stores the head of the chain at the time it received the + // block. + *self.canonical_head.write() = new_head; - /// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the - /// fork-choice rule). - /// - /// It is important to note that the `beacon_state` returned may not match the present slot. It - /// is the state as it was when the head block was received, which could be some slots prior to - /// now. - pub fn head(&self) -> RwLockReadGuard> { - self.canonical_head.read() - } + // Update the always-at-the-present-slot state we keep around for performance gains. + *self.state.write() = { + let mut state = self.canonical_head.read().beacon_state.clone(); - /// Returns the slot of the highest block in the canonical chain. - pub fn best_slot(&self) -> Slot { - self.canonical_head.read().beacon_block.slot - } + let present_slot = match self.slot_clock.present_slot() { + Ok(Some(slot)) => slot, + _ => return Err(Error::UnableToReadSlot), + }; - /// Updates the canonical `BeaconState` with the supplied state. - /// - /// Advances the chain forward to the present slot. This method is better than just setting - /// state and calling `catchup_state` as it will not result in an old state being installed and - /// then having it iteratively updated -- in such a case it's possible for another thread to - /// find the state at an old slot. - /// - /// Also persists the `BeaconChain` to the store, in the case the client does not exit - /// gracefully. - fn update_state(&self, mut state: BeaconState) -> Result<(), Error> { - let present_slot = match self.slot_clock.present_slot() { - Ok(Some(slot)) => slot, - _ => return Err(Error::UnableToReadSlot), + // If required, transition the new state to the present slot. + for _ in state.slot.as_u64()..present_slot.as_u64() { + per_slot_processing(&mut state, &T::EthSpec::spec())?; + } + + state.build_all_caches(&T::EthSpec::spec())?; + + state }; - // If required, transition the new state to the present slot. - for _ in state.slot.as_u64()..present_slot.as_u64() { - per_slot_processing(&mut state, &self.spec)?; - } - - state.build_all_caches(&self.spec)?; - - *self.state.write() = state; - + // Save `self` to `self.store`. self.persist()?; Ok(()) } - /// Ensures the current canonical `BeaconState` has been transitioned to match the `slot_clock`. - pub fn catchup_state(&self) -> Result<(), Error> { - let present_slot = match self.slot_clock.present_slot() { - Ok(Some(slot)) => slot, - _ => return Err(Error::UnableToReadSlot), - }; - - let mut state = self.state.write(); - - // If required, transition the new state to the present slot. - for _ in state.slot.as_u64()..present_slot.as_u64() { - // Ensure the next epoch state caches are built in case of an epoch transition. - state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &self.spec)?; - state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &self.spec)?; - - per_slot_processing(&mut *state, &self.spec)?; - } - - state.build_all_caches(&self.spec)?; - - Ok(()) - } - - /// Build all of the caches on the current state. - /// - /// Ideally this shouldn't be required, however we leave it here for testing. - pub fn ensure_state_caches_are_built(&self) -> Result<(), Error> { - self.state.write().build_all_caches(&self.spec)?; - - Ok(()) - } - /// Update the justified head to some new values. fn update_finalized_head( &self, @@ -423,6 +368,86 @@ impl BeaconChain { ); } + /* + /// Updates the canonical `BeaconState` with the supplied state. + /// + /// Advances the chain forward to the present slot. This method is better than just setting + /// state and calling `catchup_state` as it will not result in an old state being installed and + /// then having it iteratively updated -- in such a case it's possible for another thread to + /// find the state at an old slot. + /// + /// Also persists the `BeaconChain` to the store, in the case the client does not exit + /// gracefully. + fn update_state(&self, mut state: BeaconState) -> Result<(), Error> { + let present_slot = match self.slot_clock.present_slot() { + Ok(Some(slot)) => slot, + _ => return Err(Error::UnableToReadSlot), + }; + + // If required, transition the new state to the present slot. + for _ in state.slot.as_u64()..present_slot.as_u64() { + per_slot_processing(&mut state, &T::EthSpec::spec())?; + } + + state.build_all_caches(&T::EthSpec::spec())?; + + *self.state.write() = state; + + self.persist()?; + + Ok(()) + } + */ + + /// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the + /// fork-choice rule). + /// + /// It is important to note that the `beacon_state` returned may not match the present slot. It + /// is the state as it was when the head block was received, which could be some slots prior to + /// now. + pub fn head(&self) -> RwLockReadGuard> { + self.canonical_head.read() + } + + /// Returns the slot of the highest block in the canonical chain. + pub fn best_slot(&self) -> Slot { + self.canonical_head.read().beacon_block.slot + } + + /// Ensures the current canonical `BeaconState` has been transitioned to match the `slot_clock`. + pub fn catchup_state(&self) -> Result<(), Error> { + let spec = &T::EthSpec::spec(); + + let present_slot = match self.slot_clock.present_slot() { + Ok(Some(slot)) => slot, + _ => return Err(Error::UnableToReadSlot), + }; + + let mut state = self.state.write(); + + // If required, transition the new state to the present slot. + for _ in state.slot.as_u64()..present_slot.as_u64() { + // Ensure the next epoch state caches are built in case of an epoch transition. + state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?; + + per_slot_processing(&mut *state, spec)?; + } + + state.build_all_caches(spec)?; + + Ok(()) + } + + /// Build all of the caches on the current state. + /// + /// Ideally this shouldn't be required, however we leave it here for testing. + pub fn ensure_state_caches_are_built(&self) -> Result<(), Error> { + self.state.write().build_all_caches(&T::EthSpec::spec())?; + + Ok(()) + } + /// Returns a read-lock guarded `CheckPoint` struct for reading the justified head (as chosen, /// indirectly, by the fork-choice rule). pub fn finalized_head(&self) -> RwLockReadGuard> { @@ -467,13 +492,12 @@ impl BeaconChain { /// genesis. pub fn slots_since_genesis(&self) -> Option { let now = self.read_slot_clock()?; + let genesis_slot = T::EthSpec::spec().genesis_slot; - if now < self.spec.genesis_slot { + if now < genesis_slot { None } else { - Some(SlotHeight::from( - now.as_u64() - self.spec.genesis_slot.as_u64(), - )) + Some(SlotHeight::from(now.as_u64() - genesis_slot.as_u64())) } } @@ -493,12 +517,12 @@ impl BeaconChain { pub fn block_proposer(&self, slot: Slot) -> Result { self.state .write() - .build_epoch_cache(RelativeEpoch::Current, &self.spec)?; + .build_epoch_cache(RelativeEpoch::Current, &T::EthSpec::spec())?; let index = self.state.read().get_beacon_proposer_index( slot, RelativeEpoch::Current, - &self.spec, + &T::EthSpec::spec(), )?; Ok(index) @@ -519,7 +543,7 @@ impl BeaconChain { if let Some(attestation_duty) = self .state .read() - .get_attestation_duties(validator_index, &self.spec)? + .get_attestation_duties(validator_index, &T::EthSpec::spec())? { Ok(Some((attestation_duty.slot, attestation_duty.shard))) } else { @@ -529,7 +553,7 @@ impl BeaconChain { /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. pub fn produce_attestation_data(&self, shard: u64) -> Result { - trace!("BeaconChain::produce_attestation: shard: {}", shard); + let slots_per_epoch = T::EthSpec::spec().slots_per_epoch; self.metrics.attestation_production_requests.inc(); let timer = self.metrics.attestation_production_times.start_timer(); @@ -540,8 +564,8 @@ impl BeaconChain { .state .read() .slot - .epoch(self.spec.slots_per_epoch) - .start_slot(self.spec.slots_per_epoch); + .epoch(slots_per_epoch) + .start_slot(slots_per_epoch); let target_root = if state.slot == current_epoch_start_slot { // If we're on the first slot of the state's epoch. @@ -554,7 +578,7 @@ impl BeaconChain { *self .state .read() - .get_block_root(current_epoch_start_slot - self.spec.slots_per_epoch)? + .get_block_root(current_epoch_start_slot - slots_per_epoch)? } } else { // If we're not on the first slot of the epoch. @@ -587,9 +611,9 @@ impl BeaconChain { self.metrics.attestation_processing_requests.inc(); let timer = self.metrics.attestation_processing_times.start_timer(); - let result = self - .op_pool - .insert_attestation(attestation, &*self.state.read(), &self.spec); + let result = + self.op_pool + .insert_attestation(attestation, &*self.state.read(), &T::EthSpec::spec()); if result.is_ok() { self.metrics.attestation_production_successes.inc(); @@ -606,19 +630,19 @@ impl BeaconChain { deposit: Deposit, ) -> Result { self.op_pool - .insert_deposit(deposit, &*self.state.read(), &self.spec) + .insert_deposit(deposit, &*self.state.read(), &T::EthSpec::spec()) } /// Accept some exit and queue it for inclusion in an appropriate block. pub fn process_voluntary_exit(&self, exit: VoluntaryExit) -> Result<(), ExitValidationError> { self.op_pool - .insert_voluntary_exit(exit, &*self.state.read(), &self.spec) + .insert_voluntary_exit(exit, &*self.state.read(), &T::EthSpec::spec()) } /// Accept some transfer and queue it for inclusion in an appropriate block. pub fn process_transfer(&self, transfer: Transfer) -> Result<(), TransferValidationError> { self.op_pool - .insert_transfer(transfer, &*self.state.read(), &self.spec) + .insert_transfer(transfer, &*self.state.read(), &T::EthSpec::spec()) } /// Accept some proposer slashing and queue it for inclusion in an appropriate block. @@ -626,8 +650,11 @@ impl BeaconChain { &self, proposer_slashing: ProposerSlashing, ) -> Result<(), ProposerSlashingValidationError> { - self.op_pool - .insert_proposer_slashing(proposer_slashing, &*self.state.read(), &self.spec) + self.op_pool.insert_proposer_slashing( + proposer_slashing, + &*self.state.read(), + &T::EthSpec::spec(), + ) } /// Accept some attester slashing and queue it for inclusion in an appropriate block. @@ -635,8 +662,11 @@ impl BeaconChain { &self, attester_slashing: AttesterSlashing, ) -> Result<(), AttesterSlashingValidationError> { - self.op_pool - .insert_attester_slashing(attester_slashing, &*self.state.read(), &self.spec) + self.op_pool.insert_attester_slashing( + attester_slashing, + &*self.state.read(), + &T::EthSpec::spec(), + ) } /// Accept some block and attempt to add it to block DAG. @@ -686,7 +716,7 @@ impl BeaconChain { // Transition the parent state to the block slot. let mut state: BeaconState = parent_state; for _ in state.slot.as_u64()..block.slot.as_u64() { - if let Err(e) = per_slot_processing(&mut state, &self.spec) { + if let Err(e) = per_slot_processing(&mut state, &T::EthSpec::spec()) { return Ok(BlockProcessingOutcome::InvalidBlock( InvalidBlock::SlotProcessingError(e), )); @@ -695,7 +725,7 @@ impl BeaconChain { // Apply the received block to its parent state (which has been transitioned into this // slot). - if let Err(e) = per_block_processing(&mut state, &block, &self.spec) { + if let Err(e) = per_block_processing(&mut state, &block, &T::EthSpec::spec()) { return Ok(BlockProcessingOutcome::InvalidBlock( InvalidBlock::PerBlockProcessingError(e), )); @@ -716,7 +746,7 @@ impl BeaconChain { // Register the new block with the fork choice service. self.fork_choice .write() - .add_block(&block, &block_root, &self.spec)?; + .add_block(&block, &block_root, &T::EthSpec::spec())?; // Execute the fork choice algorithm, enthroning a new head if discovered. // @@ -744,7 +774,7 @@ impl BeaconChain { let mut state = self.state.read().clone(); - state.build_epoch_cache(RelativeEpoch::Current, &self.spec)?; + state.build_epoch_cache(RelativeEpoch::Current, &T::EthSpec::spec())?; trace!("Finding attestations for new block..."); @@ -752,14 +782,15 @@ impl BeaconChain { .get_block_root(state.slot - 1) .map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?; - let (proposer_slashings, attester_slashings) = - self.op_pool.get_slashings(&*self.state.read(), &self.spec); + let (proposer_slashings, attester_slashings) = self + .op_pool + .get_slashings(&*self.state.read(), &T::EthSpec::spec()); let mut block = BeaconBlock { slot: state.slot, previous_block_root, state_root: Hash256::zero(), // Updated after the state is calculated. - signature: self.spec.empty_signature.clone(), // To be completed by a validator. + signature: T::EthSpec::spec().empty_signature.clone(), // To be completed by a validator. body: BeaconBlockBody { randao_reveal, eth1_data: Eth1Data { @@ -771,12 +802,16 @@ impl BeaconChain { attester_slashings, attestations: self .op_pool - .get_attestations(&*self.state.read(), &self.spec), - deposits: self.op_pool.get_deposits(&*self.state.read(), &self.spec), + .get_attestations(&*self.state.read(), &T::EthSpec::spec()), + deposits: self + .op_pool + .get_deposits(&*self.state.read(), &T::EthSpec::spec()), voluntary_exits: self .op_pool - .get_voluntary_exits(&*self.state.read(), &self.spec), - transfers: self.op_pool.get_transfers(&*self.state.read(), &self.spec), + .get_voluntary_exits(&*self.state.read(), &T::EthSpec::spec()), + transfers: self + .op_pool + .get_transfers(&*self.state.read(), &T::EthSpec::spec()), }, }; @@ -785,7 +820,11 @@ impl BeaconChain { block.body.attestations.len() ); - per_block_processing_without_verifying_block_signature(&mut state, &block, &self.spec)?; + per_block_processing_without_verifying_block_signature( + &mut state, + &block, + &T::EthSpec::spec(), + )?; let state_root = state.canonical_root(); @@ -808,7 +847,7 @@ impl BeaconChain { let new_head_root = self .fork_choice .write() - .find_head(&present_head_root, &self.spec)?; + .find_head(&present_head_root, &T::EthSpec::spec())?; timer.observe_duration(); @@ -830,10 +869,12 @@ impl BeaconChain { }; let state_root = block.state_root; - self.update_canonical_head(block, new_head_root, state.clone(), state_root); - - // Update the canonical `BeaconState`. - self.update_state(state)?; + self.update_canonical_head(CheckPoint { + beacon_block: block, + beacon_block_root: new_head_root, + beacon_state: state.clone(), + beacon_state_root: state_root, + })?; } Ok(()) @@ -863,7 +904,7 @@ impl BeaconChain { loop { let beacon_block_root = last_slot.beacon_block.previous_block_root; - if beacon_block_root == self.spec.zero_hash { + if beacon_block_root == T::EthSpec::spec().zero_hash { break; // Genesis has been reached. } diff --git a/beacon_node/http_server/src/api.rs b/beacon_node/http_server/src/api.rs index a910808998..2594f9c288 100644 --- a/beacon_node/http_server/src/api.rs +++ b/beacon_node/http_server/src/api.rs @@ -10,6 +10,7 @@ use persistent::Read; use router::Router; use serde_json::json; use std::sync::Arc; +use types::EthSpec; /// Yields a handler for the HTTP API. pub fn build_handler( @@ -64,7 +65,7 @@ fn handle_fork(req: &mut Request) -> IronResult(beacon_chain: &BeaconChain) -> HelloMes latest_finalized_root: state.finalized_root, latest_finalized_epoch: state.finalized_epoch, best_root: beacon_chain.head().beacon_block_root, - best_slot: beacon_chain.head().beacon_block.slot, + best_slot: state.slot, } } diff --git a/validator_client/src/error.rs b/validator_client/src/error.rs index 97500f900b..7740c4f2b6 100644 --- a/validator_client/src/error.rs +++ b/validator_client/src/error.rs @@ -1,6 +1,8 @@ use slot_clock; -use error_chain::error_chain; +use error_chain::{ + error_chain +}; error_chain! { links { }