diff --git a/beacon_node/rest_api/src/validator.rs b/beacon_node/rest_api/src/validator.rs index d9e915d202..9eb8526236 100644 --- a/beacon_node/rest_api/src/validator.rs +++ b/beacon_node/rest_api/src/validator.rs @@ -93,16 +93,27 @@ fn return_validator_duties( epoch: Epoch, validator_pubkeys: Vec, ) -> Result, ApiError> { + let slots_per_epoch = T::EthSpec::slots_per_epoch(); let head_epoch = beacon_chain.head().beacon_state.current_epoch(); - let mut state = if RelativeEpoch::from_epoch(head_epoch, epoch).is_err() { + let mut state = if RelativeEpoch::from_epoch(head_epoch, epoch).is_ok() { beacon_chain.head().beacon_state } else { - beacon_chain - .state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch())) - .map_err(|e| { - ApiError::ServerError(format!("Unable to load state for epoch {}: {:?}", epoch, e)) - })? + let slot = if epoch > head_epoch { + // Move to the first slot of the epoch prior to the request. + // + // Taking advantage of saturating epoch subtraction. + (epoch - 1).start_slot(slots_per_epoch) + } else { + // Move to the end of the epoch following the target. + // + // Taking advantage of saturating epoch subtraction. + (epoch + 2).start_slot(slots_per_epoch) - 1 + }; + + beacon_chain.state_at_slot(slot).map_err(|e| { + ApiError::ServerError(format!("Unable to load state for epoch {}: {:?}", epoch, e)) + })? }; let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), epoch) diff --git a/beacon_node/rest_api/tests/test.rs b/beacon_node/rest_api/tests/test.rs index 888dfc735b..0fabfd08ff 100644 --- a/beacon_node/rest_api/tests/test.rs +++ b/beacon_node/rest_api/tests/test.rs @@ -205,7 +205,7 @@ fn validator_duties() { .beacon_chain() .expect("client should have beacon chain"); - let epoch = Epoch::new(0); + let mut epoch = Epoch::new(0); let validators = beacon_chain .head() @@ -220,7 +220,26 @@ fn validator_duties() { .block_on(remote_node.http.validator().get_duties(epoch, &validators)) .expect("should fetch duties from http api"); + // 1. Check at the current epoch. + check_duties( + duties, + epoch, + validators.clone(), + beacon_chain.clone(), + spec, + ); + + epoch += 4; + let duties = env + .runtime() + .block_on(remote_node.http.validator().get_duties(epoch, &validators)) + .expect("should fetch duties from http api"); + + // 2. Check with a long skip forward. check_duties(duties, epoch, validators, beacon_chain, spec); + + // TODO: test an epoch in the past. Blocked because the `LocalBeaconNode` cannot produce a + // chain, yet. } fn check_duties( @@ -236,7 +255,9 @@ fn check_duties( "there should be a duty for each validator" ); - let state = beacon_chain.head().beacon_state.clone(); + let state = beacon_chain + .state_at_slot(epoch.start_slot(T::EthSpec::slots_per_epoch())) + .expect("should get state at slot"); validators .iter()