diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java index 998eb56b59b..98713f69066 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCache.java @@ -53,14 +53,15 @@ public ValidatorIndexCache() { public Optional getValidatorIndex( final BeaconState state, final BLSPublicKey publicKey) { - // Store latestFinalizedIndex here in case we need to scan keys from the state. - // This ensures we're adding from a point that we're confident the cache is at - // when we scan for more keys through the state later. - final int latestFinalizedIndexSnapshot = latestFinalizedIndex.get(); final SszList validators = state.getValidators(); - return validatorIndices - .getCached(publicKey) - .or(() -> findIndexFromFinalizedState(validators, publicKey, latestFinalizedIndexSnapshot)) + final Optional validatorIndex = validatorIndices.getCached(publicKey); + if (validatorIndex.isPresent()) { + return validatorIndex.filter(index -> index < validators.size()); + } + // Using the same latestFinalizedIndex when scanning through + // the finalized and the non-finalized states ensures consistency + final int latestFinalizedIndexSnapshot = latestFinalizedIndex.get(); + return findIndexFromFinalizedState(validators, publicKey, latestFinalizedIndexSnapshot) .or( () -> findIndexFromNonFinalizedState( diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCacheTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCacheTest.java index 21da5ab67fc..3e09ce9b3c8 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCacheTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/ValidatorIndexCacheTest.java @@ -43,6 +43,23 @@ public class ValidatorIndexCacheTest { @SuppressWarnings("unchecked") final Cache cache = mock(Cache.class); + @Test + public void shouldReturnEmptyIfValidatorIndexIsNotConsistentWithNumberOfValidatorsInState() { + final SszList validators = state.getValidators(); + final int latestFinalizedIndex = NUMBER_OF_VALIDATORS - 1; + final ValidatorIndexCache validatorIndexCache = new ValidatorIndexCache(); + validatorIndexCache.updateLatestFinalizedIndex(state); + + final BLSPublicKey publicKey = validators.get(latestFinalizedIndex).getPublicKey(); + // cache eagerly the last validator public key + validatorIndexCache.invalidateWithNewValue(publicKey, latestFinalizedIndex); + + // state with one less validator + final BeaconState state = dataStructureUtil.randomBeaconState(NUMBER_OF_VALIDATORS - 1); + + assertThat(validatorIndexCache.getValidatorIndex(state, publicKey)).isEmpty(); + } + @Test public void shouldScanFinalizedStateAndCache() { final SszList validators = state.getValidators();