diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index f552dbd27..56923ab6a 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5,7 +5,6 @@ use crate::iter::{ReverseBlockRootIterator, ReverseStateRootIterator}; use crate::metrics; use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY}; use lmd_ghost::LmdGhost; -use log::trace; use operation_pool::DepositInsertStatus; use operation_pool::{OperationPool, PersistedOperationPool}; use parking_lot::{RwLock, RwLockReadGuard}; @@ -77,6 +76,20 @@ pub enum AttestationProcessingOutcome { Invalid(AttestationValidationError), } +pub enum StateCow<'a, T: EthSpec> { + Borrowed(RwLockReadGuard<'a, CheckPoint>), + Owned(BeaconState), +} + +impl<'a, T: EthSpec> AsRef> for StateCow<'a, T> { + fn as_ref(&self) -> &BeaconState { + match self { + StateCow::Borrowed(checkpoint) => &checkpoint.beacon_state, + StateCow::Owned(state) => &state, + } + } +} + pub trait BeaconChainTypes: Send + Sync + 'static { type Store: store::Store; type SlotClock: slot_clock::SlotClock; @@ -97,10 +110,6 @@ pub struct BeaconChain { pub op_pool: OperationPool, /// Stores a "snapshot" of the chain at the time the head-of-the-chain block was received. canonical_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 received. This is effectively a cache that avoids repeating calls - /// to `per_slot_processing`. - state: RwLock>, /// The root of the genesis block. pub genesis_block_root: Hash256, /// A state-machine that is updated with information from the network and chooses a canonical @@ -158,7 +167,6 @@ impl BeaconChain { spec, slot_clock, op_pool: OperationPool::new(), - state: RwLock::new(genesis_state), canonical_head, genesis_block_root, fork_choice: ForkChoice::new(store.clone(), &genesis_block, genesis_block_root), @@ -180,9 +188,11 @@ impl BeaconChain { Ok(Some(p)) => p, }; + let state = &p.canonical_head.beacon_state; + let slot_clock = T::SlotClock::from_eth2_genesis( spec.genesis_slot, - p.state.genesis_time, + state.genesis_time, Duration::from_millis(spec.milliseconds_per_slot), ) .ok_or_else(|| Error::SlotClockDidNotStart)?; @@ -190,7 +200,7 @@ impl BeaconChain { let last_finalized_root = p.canonical_head.beacon_state.finalized_checkpoint.root; let last_finalized_block = &p.canonical_head.beacon_block; - let op_pool = p.op_pool.into_operation_pool(&p.state, &spec); + let op_pool = p.op_pool.into_operation_pool(state, &spec); Ok(Some(BeaconChain { spec, @@ -198,7 +208,6 @@ impl BeaconChain { fork_choice: ForkChoice::new(store.clone(), last_finalized_block, last_finalized_root), op_pool, canonical_head: RwLock::new(p.canonical_head), - state: RwLock::new(p.state), genesis_block_root: p.genesis_block_root, store, log, @@ -213,7 +222,6 @@ impl BeaconChain { canonical_head: self.canonical_head.read().clone(), op_pool: PersistedOperationPool::from_operation_pool(&self.op_pool), genesis_block_root: self.genesis_block_root, - state: self.state.read().clone(), }; let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes()); @@ -233,6 +241,16 @@ impl BeaconChain { self.slot_clock.now().ok_or_else(|| Error::UnableToReadSlot) } + /// Returns the epoch _right now_ according to `self.slot_clock`. Returns `Err` if the epoch is + /// unavailable. + /// + /// The epoch might be unavailable due to an error with the system clock, or if the present time + /// is before genesis (i.e., a negative epoch). + pub fn epoch(&self) -> Result { + self.slot() + .map(|slot| slot.epoch(T::EthSpec::slots_per_epoch())) + } + /// Returns the beacon block body for each beacon block root in `roots`. /// /// Fails if any root in `roots` does not have a corresponding block. @@ -318,12 +336,6 @@ impl BeaconChain { Ok(self.store.get(block_root)?) } - /// Returns a read-lock guarded `BeaconState` which is the `canonical_head` that has been - /// updated to match the current slot clock. - pub fn speculative_state(&self) -> Result>, Error> { - Ok(self.state.read()) - } - /// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the /// fork-choice rule). /// @@ -334,43 +346,74 @@ impl BeaconChain { self.canonical_head.read() } + /// Returns the `BeaconState` at the given slot. + /// + /// May return: + /// + /// - A new state loaded from the database (for states prior to the head) + /// - A reference to the head state (note: this keeps a read lock on the head, try to use + /// sparingly). + /// - The head state, but with skipped slots (for states later than the head). + /// + /// Returns `None` when the state is not found in the database or there is an error skipping + /// to a future state. + pub fn state_at_slot(&self, slot: Slot) -> Result, Error> { + let head_state = &self.head().beacon_state; + + if slot == head_state.slot { + Ok(StateCow::Borrowed(self.head())) + } else if slot > head_state.slot { + let head_state_slot = head_state.slot; + let mut state = head_state.clone(); + drop(head_state); + while state.slot < slot { + match per_slot_processing(&mut state, &self.spec) { + Ok(()) => (), + Err(e) => { + warn!( + self.log, + "Unable to load state at slot"; + "error" => format!("{:?}", e), + "head_slot" => head_state_slot, + "requested_slot" => slot + ); + return Err(Error::NoStateForSlot(slot)); + } + }; + } + Ok(StateCow::Owned(state)) + } else { + let state_root = self + .rev_iter_state_roots() + .find(|(_root, s)| *s == slot) + .map(|(root, _slot)| root) + .ok_or_else(|| Error::NoStateForSlot(slot))?; + + Ok(StateCow::Owned( + self.store + .get(&state_root)? + .ok_or_else(|| Error::NoStateForSlot(slot))?, + )) + } + } + + /// Returns the `BeaconState` the current slot (viz., `self.slot()`). + /// + /// - A reference to the head state (note: this keeps a read lock on the head, try to use + /// sparingly). + /// - The head state, but with skipped slots (for states later than the head). + /// + /// Returns `None` when there is an error skipping to a future state or the slot clock cannot + /// be read. + pub fn state_now(&self) -> Result, Error> { + self.state_at_slot(self.slot()?) + } + /// 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 = &self.spec; - - let present_slot = self.slot()?; - - if self.state.read().slot < present_slot { - 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_committee_cache(RelativeEpoch::Next, 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(&self.spec)?; - - Ok(()) - } - /// Returns the validator index (if any) for the given public key. /// /// Information is retrieved from the present `beacon_state.validators`. @@ -401,18 +444,19 @@ impl BeaconChain { /// Information is read from the present `beacon_state` shuffling, only information from the /// present epoch is available. pub fn block_proposer(&self, slot: Slot) -> Result { - // Ensures that the present state has been advanced to the present slot, skipping slots if - // blocks are not present. - self.catchup_state()?; + let epoch = |slot: Slot| slot.epoch(T::EthSpec::slots_per_epoch()); + let head_state = &self.head().beacon_state; - // TODO: permit lookups of the proposer at any slot. - let index = self.state.read().get_beacon_proposer_index( - slot, - RelativeEpoch::Current, - &self.spec, - )?; + let state = if epoch(slot) == epoch(head_state.slot) { + StateCow::Borrowed(self.head()) + } else { + self.state_at_slot(slot)? + }; - Ok(index) + state + .as_ref() + .get_beacon_proposer_index(slot, RelativeEpoch::Current, &self.spec) + .map_err(Into::into) } /// Returns the attestation slot and shard for a given validator index. @@ -422,14 +466,19 @@ impl BeaconChain { pub fn validator_attestation_slot_and_shard( &self, validator_index: usize, - ) -> Result, BeaconStateError> { - trace!( - "BeaconChain::validator_attestation_slot_and_shard: validator_index: {}", - validator_index - ); - if let Some(attestation_duty) = self - .state - .read() + epoch: Epoch, + ) -> Result, Error> { + let as_epoch = |slot: Slot| slot.epoch(T::EthSpec::slots_per_epoch()); + let head_state = &self.head().beacon_state; + + let state = if epoch == as_epoch(head_state.slot) { + StateCow::Borrowed(self.head()) + } else { + self.state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch()))? + }; + + if let Some(attestation_duty) = state + .as_ref() .get_attestation_duties(validator_index, RelativeEpoch::Current)? { Ok(Some((attestation_duty.slot, attestation_duty.shard))) @@ -438,15 +487,25 @@ impl BeaconChain { } } - /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. + /// Produce an `AttestationData` that is valid for the given `slot` `shard`. /// - /// Attests to the canonical chain. - pub fn produce_attestation_data(&self, shard: u64) -> Result { - let state = self.state.read(); + /// Always attests to the canonical chain. + pub fn produce_attestation_data( + &self, + shard: u64, + slot: Slot, + ) -> Result { + let state = self.state_at_slot(slot)?; + let head_block_root = self.head().beacon_block_root; let head_block_slot = self.head().beacon_block.slot; - self.produce_attestation_data_for_block(shard, head_block_root, head_block_slot, &*state) + self.produce_attestation_data_for_block( + shard, + head_block_root, + head_block_slot, + state.as_ref(), + ) } /// Produce an `AttestationData` that attests to the chain denoted by `block_root` and `state`. @@ -765,14 +824,38 @@ impl BeaconChain { /// 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) + match self.state_now() { + Ok(state) => self + .op_pool + .insert_voluntary_exit(exit, state.as_ref(), &self.spec), + Err(e) => { + error!( + &self.log, + "Unable to process voluntary exit"; + "error" => format!("{:?}", e), + "reason" => "no state" + ); + Ok(()) + } + } } /// 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) + match self.state_now() { + Ok(state) => self + .op_pool + .insert_transfer(transfer, state.as_ref(), &self.spec), + Err(e) => { + error!( + &self.log, + "Unable to process transfer"; + "error" => format!("{:?}", e), + "reason" => "no state" + ); + Ok(()) + } + } } /// Accept some proposer slashing and queue it for inclusion in an appropriate block. @@ -780,8 +863,21 @@ impl BeaconChain { &self, proposer_slashing: ProposerSlashing, ) -> Result<(), ProposerSlashingValidationError> { - self.op_pool - .insert_proposer_slashing(proposer_slashing, &*self.state.read(), &self.spec) + match self.state_now() { + Ok(state) => { + self.op_pool + .insert_proposer_slashing(proposer_slashing, state.as_ref(), &self.spec) + } + Err(e) => { + error!( + &self.log, + "Unable to process proposer slashing"; + "error" => format!("{:?}", e), + "reason" => "no state" + ); + Ok(()) + } + } } /// Accept some attester slashing and queue it for inclusion in an appropriate block. @@ -789,8 +885,21 @@ impl BeaconChain { &self, attester_slashing: AttesterSlashing, ) -> Result<(), AttesterSlashingValidationError> { - self.op_pool - .insert_attester_slashing(attester_slashing, &*self.state.read(), &self.spec) + match self.state_now() { + Ok(state) => { + self.op_pool + .insert_attester_slashing(attester_slashing, state.as_ref(), &self.spec) + } + Err(e) => { + error!( + &self.log, + "Unable to process attester slashing"; + "error" => format!("{:?}", e), + "reason" => "no state" + ); + Ok(()) + } + } } /// Accept some block and attempt to add it to block DAG. @@ -804,8 +913,8 @@ impl BeaconChain { let full_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_TIMES); let finalized_slot = self - .state - .read() + .head() + .beacon_state .finalized_checkpoint .epoch .start_slot(T::EthSpec::slots_per_epoch()); @@ -987,20 +1096,24 @@ impl BeaconChain { Ok(BlockProcessingOutcome::Processed { block_root }) } - /// Produce a new block at the present slot. + /// Produce a new block at the given `slot`. /// /// The produced block will not be inherently valid, it must be signed by a block producer. /// Block signing is out of the scope of this function and should be done by a separate program. pub fn produce_block( &self, randao_reveal: Signature, + slot: Slot, ) -> Result<(BeaconBlock, BeaconState), BlockProductionError> { - let state = self.state.read().clone(); + let state = self + .state_at_slot(slot) + .map_err(|_| BlockProductionError::UnableToProduceAtSlot(slot))?; + let slot = self .slot() .map_err(|_| BlockProductionError::UnableToReadSlot)?; - self.produce_block_on_state(state, slot, randao_reveal) + self.produce_block_on_state(state.as_ref().clone(), slot, randao_reveal) } /// Produce a block for some `slot` upon the given `state`. @@ -1169,29 +1282,15 @@ impl BeaconChain { } /// Update the canonical head to `new_head`. - fn update_canonical_head(&self, new_head: CheckPoint) -> Result<(), Error> { + fn update_canonical_head(&self, mut new_head: CheckPoint) -> Result<(), Error> { let timer = metrics::start_timer(&metrics::UPDATE_HEAD_TIMES); + new_head.beacon_state.build_all_caches(&self.spec)?; + // Update the checkpoint that stores the head of the chain at the time it received the // block. *self.canonical_head.write() = new_head; - // 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(); - - let present_slot = self.slot()?; - - // 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)?; - - state - }; - // Save `self` to `self.store`. self.persist()?; diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 75dbb655f..cd8d6aad6 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -24,6 +24,7 @@ pub enum BeaconChainError { new_epoch: Epoch, }, SlotClockDidNotStart, + NoStateForSlot(Slot), UnableToFindTargetRoot(Slot), BeaconStateError(BeaconStateError), DBInconsistent(String), @@ -44,6 +45,7 @@ easy_from_to!(SlotProcessingError, BeaconChainError); pub enum BlockProductionError { UnableToGetBlockRootFromState, UnableToReadSlot, + UnableToProduceAtSlot(Slot), SlotProcessingError(SlotProcessingError), BlockProcessingError(BlockProcessingError), BeaconStateError(BeaconStateError), diff --git a/beacon_node/beacon_chain/src/persisted_beacon_chain.rs b/beacon_node/beacon_chain/src/persisted_beacon_chain.rs index 8b9f78dc5..a85f78ac8 100644 --- a/beacon_node/beacon_chain/src/persisted_beacon_chain.rs +++ b/beacon_node/beacon_chain/src/persisted_beacon_chain.rs @@ -3,7 +3,7 @@ use operation_pool::PersistedOperationPool; use ssz::{Decode, Encode}; use ssz_derive::{Decode, Encode}; use store::{DBColumn, Error as StoreError, StoreItem}; -use types::{BeaconState, Hash256}; +use types::Hash256; /// 32-byte key for accessing the `PersistedBeaconChain`. pub const BEACON_CHAIN_DB_KEY: &str = "PERSISTEDBEACONCHAINPERSISTEDBEA"; @@ -13,7 +13,6 @@ pub struct PersistedBeaconChain { pub canonical_head: CheckPoint, pub op_pool: PersistedOperationPool, pub genesis_block_root: Hash256, - pub state: BeaconState, } impl StoreItem for PersistedBeaconChain { diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index 52e1ec8de..1006fabf5 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -130,7 +130,6 @@ where /// Does not produce blocks or attestations. pub fn advance_slot(&self) { self.chain.slot_clock.advance_slot(); - self.chain.catchup_state().expect("should catchup state"); } /// Extend the `BeaconChain` with some blocks and attestations. Returns the root of the diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs index 22b667f15..ba7f7bf84 100644 --- a/beacon_node/beacon_chain/tests/tests.rs +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -322,7 +322,9 @@ fn roundtrip_operation_pool() { let p: PersistedBeaconChain> = harness.chain.store.get(&key).unwrap().unwrap(); - let restored_op_pool = p.op_pool.into_operation_pool(&p.state, &harness.spec); + let restored_op_pool = p + .op_pool + .into_operation_pool(&p.canonical_head.beacon_state, &harness.spec); assert_eq!(harness.chain.op_pool, restored_op_pool); } diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 4554ff9a1..9876e9672 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -143,7 +143,6 @@ where "catchup_distance" => wall_clock_slot - state_slot, ); } - do_state_catchup(&beacon_chain, &log); let network_config = &client_config.network; let (network, network_send) = @@ -199,7 +198,7 @@ where exit.until( interval .for_each(move |_| { - do_state_catchup(&chain, &log); + log_new_slot(&chain, &log); Ok(()) }) @@ -229,35 +228,19 @@ impl Drop for Client { } } -fn do_state_catchup(chain: &Arc>, log: &slog::Logger) { - // Only attempt to `catchup_state` if we can read the slot clock. +fn log_new_slot(chain: &Arc>, log: &slog::Logger) { + let best_slot = chain.head().beacon_block.slot; + let latest_block_root = chain.head().beacon_block_root; + if let Ok(current_slot) = chain.slot() { - let state_catchup_result = chain.catchup_state(); - - let best_slot = chain.head().beacon_block.slot; - let latest_block_root = chain.head().beacon_block_root; - - let common = o!( + info!( + log, + "Slot start"; "skip_slots" => current_slot.saturating_sub(best_slot), "best_block_root" => format!("{}", latest_block_root), "best_block_slot" => best_slot, "slot" => current_slot, - ); - - if let Err(e) = state_catchup_result { - error!( - log, - "State catchup failed"; - "error" => format!("{:?}", e), - common - ) - } else { - info!( - log, - "Slot start"; - common - ) - } + ) } else { error!( log, diff --git a/beacon_node/rest_api/src/validator.rs b/beacon_node/rest_api/src/validator.rs index 4294f9c20..365b7e552 100644 --- a/beacon_node/rest_api/src/validator.rs +++ b/beacon_node/rest_api/src/validator.rs @@ -39,12 +39,7 @@ pub fn get_validator_duties(req: Request) - .extensions() .get::>>() .ok_or_else(|| ApiError::ServerError("Beacon chain extension missing".to_string()))?; - let _ = beacon_chain - .ensure_state_caches_are_built() - .map_err(|e| ApiError::ServerError(format!("Unable to build state caches: {:?}", e)))?; - let head_state = beacon_chain - .speculative_state() - .expect("This is legacy code and should be removed."); + let head_state = &beacon_chain.head().beacon_state; // Parse and check query parameters let query = UrlQuery::from_request(&req)?; diff --git a/beacon_node/rpc/src/attestation.rs b/beacon_node/rpc/src/attestation.rs index 68d3829ee..f4b49049a 100644 --- a/beacon_node/rpc/src/attestation.rs +++ b/beacon_node/rpc/src/attestation.rs @@ -14,7 +14,7 @@ use slog::{error, info, trace, warn}; use ssz::{ssz_encode, Decode, Encode}; use std::sync::Arc; use tokio::sync::mpsc; -use types::Attestation; +use types::{Attestation, Slot}; #[derive(Clone)] pub struct AttestationServiceInstance { @@ -37,49 +37,13 @@ impl AttestationService for AttestationServiceInstance { req.get_slot() ); - // verify the slot, drop lock on state afterwards - { - let slot_requested = req.get_slot(); - // TODO: this whole module is legacy and not maintained well. - let state = &self - .chain - .speculative_state() - .expect("This is legacy code and should be removed"); - - // Start by performing some checks - // Check that the AttestationData is for the current slot (otherwise it will not be valid) - if slot_requested > state.slot.as_u64() { - let log_clone = self.log.clone(); - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::OutOfRange, - Some( - "AttestationData request for a slot that is in the future.".to_string(), - ), - )) - .map_err(move |e| { - error!(log_clone, "Failed to reply with failure {:?}: {:?}", req, e) - }); - return ctx.spawn(f); - } - // currently cannot handle past slots. TODO: Handle this case - else if slot_requested < state.slot.as_u64() { - let log_clone = self.log.clone(); - let f = sink - .fail(RpcStatus::new( - RpcStatusCode::InvalidArgument, - Some("AttestationData request for a slot that is in the past.".to_string()), - )) - .map_err(move |e| { - error!(log_clone, "Failed to reply with failure {:?}: {:?}", req, e) - }); - return ctx.spawn(f); - } - } - // Then get the AttestationData from the beacon chain let shard = req.get_shard(); - let attestation_data = match self.chain.produce_attestation_data(shard) { + let slot_requested = req.get_slot(); + let attestation_data = match self + .chain + .produce_attestation_data(shard, Slot::from(slot_requested)) + { Ok(v) => v, Err(e) => { // Could not produce an attestation diff --git a/beacon_node/rpc/src/beacon_block.rs b/beacon_node/rpc/src/beacon_block.rs index 92a543ef3..b7332b395 100644 --- a/beacon_node/rpc/src/beacon_block.rs +++ b/beacon_node/rpc/src/beacon_block.rs @@ -35,7 +35,7 @@ impl BeaconBlockService for BeaconBlockServiceInstance { // decode the request // TODO: requested slot currently unused, see: https://github.com/sigp/lighthouse/issues/336 - let _requested_slot = Slot::from(req.get_slot()); + let requested_slot = Slot::from(req.get_slot()); let randao_reveal = match Signature::from_ssz_bytes(req.get_randao_reveal()) { Ok(reveal) => reveal, Err(_) => { @@ -51,7 +51,7 @@ impl BeaconBlockService for BeaconBlockServiceInstance { } }; - let produced_block = match self.chain.produce_block(randao_reveal) { + let produced_block = match self.chain.produce_block(randao_reveal, requested_slot) { Ok((block, _state)) => block, Err(e) => { // could not produce a block diff --git a/beacon_node/rpc/src/validator.rs b/beacon_node/rpc/src/validator.rs index 080c828a7..fd6d7f3d1 100644 --- a/beacon_node/rpc/src/validator.rs +++ b/beacon_node/rpc/src/validator.rs @@ -30,10 +30,7 @@ impl ValidatorService for ValidatorServiceInstance { let spec = &self.chain.spec; // TODO: this whole module is legacy and not maintained well. - let state = &self - .chain - .speculative_state() - .expect("This is legacy code and should be removed"); + let state = &self.chain.head().beacon_state; let epoch = Epoch::from(req.get_epoch()); let mut resp = GetDutiesResponse::new(); let resp_validators = resp.mut_active_validators();