diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java index 7ea6bf77856..9fa42ea52c3 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/DataProvider.java @@ -233,7 +233,9 @@ public DataProvider build() { isLivenessTrackingEnabled, activeValidatorChannel, proposersDataManager, - forkChoiceNotifier); + forkChoiceNotifier, + recentChainData, + spec); final ChainDataProvider chainDataProvider = new ChainDataProvider(spec, recentChainData, combinedChainDataClient, rewardCalculator); final SyncDataProvider syncDataProvider = diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java index cd33ea4cdd1..220807bb76d 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/NodeDataProvider.java @@ -26,7 +26,9 @@ import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ProcessedAttestationListener; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -47,6 +49,7 @@ import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; import tech.pegasys.teku.statetransition.validatorcache.ActiveValidatorChannel; +import tech.pegasys.teku.storage.client.RecentChainData; import tech.pegasys.teku.validator.api.SubmitDataError; public class NodeDataProvider { @@ -63,6 +66,8 @@ public class NodeDataProvider { private final boolean isLivenessTrackingEnabled; private final ProposersDataManager proposersDataManager; private final ForkChoiceNotifier forkChoiceNotifier; + private final RecentChainData recentChainData; + private final Spec spec; public NodeDataProvider( final AggregatingAttestationPool attestationPool, @@ -76,7 +81,9 @@ public NodeDataProvider( final boolean isLivenessTrackingEnabled, final ActiveValidatorChannel activeValidatorChannel, final ProposersDataManager proposersDataManager, - final ForkChoiceNotifier forkChoiceNotifier) { + final ForkChoiceNotifier forkChoiceNotifier, + final RecentChainData recentChainData, + final Spec spec) { this.attestationPool = attestationPool; this.attesterSlashingPool = attesterSlashingsPool; this.proposerSlashingPool = proposerSlashingPool; @@ -89,6 +96,8 @@ public NodeDataProvider( this.isLivenessTrackingEnabled = isLivenessTrackingEnabled; this.proposersDataManager = proposersDataManager; this.forkChoiceNotifier = forkChoiceNotifier; + this.recentChainData = recentChainData; + this.spec = spec; } public List getAttestations( @@ -96,6 +105,28 @@ public List getAttestations( return attestationPool.getAttestations(maybeSlot, maybeCommitteeIndex); } + public ObjectAndMetaData> getAttestationsAndMetaData( + final Optional maybeSlot, final Optional maybeCommitteeIndex) { + return lookupMetaData( + attestationPool.getAttestations(maybeSlot, maybeCommitteeIndex), maybeSlot); + } + + private ObjectAndMetaData> lookupMetaData( + final List attestations, final Optional maybeSlot) { + final UInt64 slot = getSlot(attestations, maybeSlot); + return new ObjectAndMetaData<>( + attestations, spec.atSlot(slot).getMilestone(), false, false, false); + } + + private UInt64 getSlot(final List attestations, final Optional maybeSlot) { + return maybeSlot.orElseGet( + () -> + attestations.stream() + .findFirst() + .map(attestation -> attestation.getData().getSlot()) + .orElseGet(() -> recentChainData.getCurrentSlot().orElse(UInt64.ZERO))); + } + public List getAttesterSlashings() { return new ArrayList<>(attesterSlashingPool.getAll()); } diff --git a/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java b/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java index a43ee09a500..051cfb5617c 100644 --- a/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java +++ b/data/provider/src/test/java/tech/pegasys/teku/api/NodeDataProviderTest.java @@ -15,16 +15,22 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -40,6 +46,7 @@ import tech.pegasys.teku.statetransition.synccommittee.SyncCommitteeContributionPool; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; import tech.pegasys.teku.statetransition.validatorcache.ActiveValidatorChannel; +import tech.pegasys.teku.storage.client.RecentChainData; import tech.pegasys.teku.validator.api.SubmitDataError; @SuppressWarnings("unchecked") @@ -53,6 +60,7 @@ public class NodeDataProviderTest { private final ActiveValidatorChannel validatorChannel = mock(ActiveValidatorChannel.class); private final ProposersDataManager proposersDataManager = mock(ProposersDataManager.class); private final ForkChoiceNotifier forkChoiceNotifier = mock(ForkChoiceNotifier.class); + private final RecentChainData recentChainData = mock(RecentChainData.class); private final OperationPool attesterSlashingPool = mock(OperationPool.class); @@ -82,7 +90,9 @@ public void setup() { false, validatorChannel, proposersDataManager, - forkChoiceNotifier); + forkChoiceNotifier, + recentChainData, + spec); } @Test @@ -113,4 +123,70 @@ void blsToExecutionChanges_ReturnsListOfErrors() throws ExecutionException, Inte assertThat(future.get()) .isEqualTo(List.of(new SubmitDataError(UInt64.ONE, "Computer says no"))); } + + @Test + void attestationsMetaDataLookUp_UseFirstAttestationSlot_WhenSlotParamNotProvided() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())) + .thenReturn( + List.of( + dataStructureUtil.randomAttestation(5), dataStructureUtil.randomAttestation(10))); + provider.getAttestationsAndMetaData(Optional.empty(), Optional.empty()); + verify(specMock).atSlot(eq(UInt64.valueOf(5))); + } + + @Test + void attestationsMetaDataLookUp_UseSlot_WhenSlotParamProvided() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())) + .thenReturn( + List.of( + dataStructureUtil.randomAttestation(5), dataStructureUtil.randomAttestation(10))); + provider.getAttestationsAndMetaData(Optional.of(UInt64.valueOf(8)), Optional.empty()); + verify(specMock).atSlot(eq(UInt64.valueOf(8))); + } + + @Test + void attestationsMetaDataLookUp_UseCurrentSlot_WhenSlotParamNotProvided_EmptyList() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())).thenReturn(Collections.emptyList()); + final UInt64 currentSlot = UInt64.valueOf(8); + when(recentChainData.getCurrentSlot()).thenReturn(Optional.of(currentSlot)); + provider.getAttestationsAndMetaData(Optional.empty(), Optional.empty()); + verify(specMock).atSlot(eq(currentSlot)); + } + + @Test + void attestationsMetaDataLookUp_UseSlotZero_WhenSlotParamNotProvided_EmptyList_NoCurrentSlot() { + final Spec specMock = setUpMockedSpec(); + when(attestationPool.getAttestations(any(), any())).thenReturn(Collections.emptyList()); + when(recentChainData.getCurrentSlot()).thenReturn(Optional.empty()); + provider.getAttestationsAndMetaData(Optional.empty(), Optional.empty()); + verify(specMock).atSlot(eq(UInt64.ZERO)); + } + + private Spec setUpMockedSpec() { + final Spec specMock = mock(Spec.class); + final SpecVersion specVersionMock = mock(SpecVersion.class); + final SpecMilestone specMilestone = mock(SpecMilestone.class); + when(specVersionMock.getMilestone()).thenReturn(specMilestone); + when(specMock.atSlot(any())).thenReturn(specVersionMock); + provider = + new NodeDataProvider( + attestationPool, + attesterSlashingPool, + proposerSlashingPool, + voluntaryExitPool, + blsToExecutionChangePool, + syncCommitteeContributionPool, + blockBlobSidecarsTrackersPool, + attestationManager, + false, + validatorChannel, + proposersDataManager, + forkChoiceNotifier, + recentChainData, + specMock); + return specMock; + } } diff --git a/teku/src/integration-test/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommandTest.java b/teku/src/integration-test/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommandTest.java index b0124348c0f..f816f407d1b 100644 --- a/teku/src/integration-test/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommandTest.java +++ b/teku/src/integration-test/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommandTest.java @@ -57,9 +57,13 @@ import tech.pegasys.teku.cli.BeaconNodeCommand; import tech.pegasys.teku.infrastructure.json.JsonUtil; import tech.pegasys.teku.infrastructure.logging.LoggingConfigurator; +import tech.pegasys.teku.infrastructure.time.SystemTimeProvider; +import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.constants.Domain; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; @@ -110,8 +114,6 @@ public class VoluntaryExitCommandTest { @BeforeEach public void setup(final ClientAndServer server) throws IOException { this.mockBeaconServer = server; - configureSuccessfulHeadResponse(mockBeaconServer); - configureSuccessfulGenesisResponse(mockBeaconServer); configureSuccessfulValidatorResponses(mockBeaconServer); originalSystemIn = System.in; originalSystemOut = System.out; @@ -148,6 +150,7 @@ public void tearDown() { @Test public void shouldExitAllLoadedValidators() throws JsonProcessingException { configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); configureSuccessfulVoluntaryExitResponse(mockBeaconServer); final List args = getCommandArguments(true, false, List.of()); @@ -162,6 +165,7 @@ public void shouldExitAllLoadedValidators() throws JsonProcessingException { public void shouldExitLoadedValidatorsUsingConfirmationMessage() throws JsonProcessingException { setUserInput("yes"); configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); configureSuccessfulVoluntaryExitResponse(mockBeaconServer); final List args = getCommandArguments(true, true, List.of()); @@ -175,6 +179,7 @@ public void shouldExitLoadedValidatorsUsingConfirmationMessage() throws JsonProc @Test public void shouldExitValidatorWithPubKeyFromKeyManagerOnly() throws JsonProcessingException { configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); configureSuccessfulVoluntaryExitResponse(mockBeaconServer); final List args = @@ -193,6 +198,7 @@ public void shouldExitValidatorWithPubKeyFromKeyManagerOnly() throws JsonProcess @Test public void shouldAcceptNetworkOnCommandLine() { configureSuccessfulVoluntaryExitResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); // No beacon-api offered by spec, so would need to be loaded from local network option final List args = @@ -210,6 +216,7 @@ public void shouldAcceptNetworkOnCommandLine() { @Test public void shouldReturnRejectedReasonWhenExitIsRejectedByBeaconNode() throws IOException { configureRejectedVoluntaryExitResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); final List args = getCommandArguments( @@ -239,6 +246,7 @@ public void shouldFailToRunExitWithoutASpec() { @Test public void shouldExitValidatorWithPubKeyFromPathOnly() throws JsonProcessingException { configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); configureSuccessfulVoluntaryExitResponse(mockBeaconServer); final List args = @@ -257,6 +265,7 @@ public void shouldExitValidatorWithPubKeyFromPathOnly() throws JsonProcessingExc @Test public void shouldSkipKeyManagerKeys() throws JsonProcessingException { configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); configureSuccessfulVoluntaryExitResponse(mockBeaconServer); final List args = @@ -275,6 +284,7 @@ public void shouldSkipKeyManagerKeys() throws JsonProcessingException { @Test void shouldNotWarn_NotWithdrawableIfCapellaEnabled() throws JsonProcessingException { configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella()); + configureSuccessfulGenesisResponse(mockBeaconServer); final List args = getCommandArguments(false, true, List.of()); setUserInput("no"); @@ -290,6 +300,7 @@ void shouldNotWarn_NotWithdrawableIfCapellaEnabled() throws JsonProcessingExcept @Test void shouldGenerateExitWithoutSendingToNode(@TempDir final Path tempDir) throws IOException { configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella()); + configureSuccessfulGenesisResponse(mockBeaconServer); final Path outputFolder = tempDir.resolve("out"); final List args = new ArrayList<>(); args.addAll(commandArgs); @@ -317,6 +328,8 @@ void shouldGenerateExitWithoutSendingToNode(@TempDir final Path tempDir) throws @Test void shouldFailIfSaveFolderCannotBeCreated(@TempDir final Path tempDir) throws IOException { configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella()); + configureSuccessfulGenesisResponse(mockBeaconServer); + final Path invalidOutputDestination = tempDir.resolve("testFile"); Files.writeString(invalidOutputDestination, "test"); final List args = new ArrayList<>(); @@ -337,6 +350,8 @@ void shouldFailIfSaveFolderCannotBeCreated(@TempDir final Path tempDir) throws I @DisabledOnOs(OS.WINDOWS) // can't set permissions on windows void shouldFailIfSaveFolderHasInsufficientAccess(@TempDir final Path tempDir) throws IOException { configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella()); + configureSuccessfulGenesisResponse(mockBeaconServer); + final Path invalidOutputDestination = tempDir.resolve("testFile"); tempDir.toFile().mkdir(); tempDir.toFile().setWritable(false); @@ -353,9 +368,29 @@ void shouldFailIfSaveFolderHasInsufficientAccess(@TempDir final Path tempDir) th assertThat(stdErr.toString(UTF_8)).contains("Failed to store exit for a756543"); } + @Test + void shouldFailIfGenesisDataNotAvailableAndNoEpochSpecified() throws JsonProcessingException { + configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella()); + final List args = + getCommandArguments(false, true, List.of("--validator-public-keys", validatorPubKey1)); + final int parseResult = beaconNodeCommand.parse(args.toArray(new String[0])); + assertThat(parseResult).isEqualTo(1); + assertThat(stdErr.toString(UTF_8)).contains("Could not calculate epoch from genesis data"); + } + + @Test + void shouldFailToGenerateExitWithoutBeaconNodeAvailable() { + final List args = + List.of("voluntary-exit", "--validator-public-keys", validatorPubKey1); + final int parseResult = beaconNodeCommand.parse(args.toArray(new String[0])); + assertThat(parseResult).isEqualTo(1); + assertThat(stdErr.toString(UTF_8)).contains("Failed to connect to beacon node."); + } + @Test void shouldExitFailureWithNoValidatorKeysFound() throws JsonProcessingException { configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); final List args = commandArgs.subList(0, 5); int parseResult = beaconNodeCommand.parse(args.toArray(new String[0])); @@ -365,8 +400,9 @@ void shouldExitFailureWithNoValidatorKeysFound() throws JsonProcessingException } @Test - void shouldExitFailureFutureEpoch() throws JsonProcessingException { + void shouldExitFailureFutureEpoch() throws IOException { configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); final List args = getCommandArguments(false, true, List.of("--epoch=1024")); int parseResult = beaconNodeCommand.parse(args.toArray(new String[0])); @@ -376,10 +412,33 @@ void shouldExitFailureFutureEpoch() throws JsonProcessingException { .contains("The specified epoch 1024 is greater than current epoch"); } + @Test + void shouldCreateExitForFutureEpochIfOutputFolderDefined(@TempDir final Path tempDir) + throws IOException { + configureSuccessfulSpecResponse(mockBeaconServer, TestSpecFactory.createMinimalCapella()); + configureSuccessfulGenesisResponse(mockBeaconServer); + final List args = new ArrayList<>(); + args.addAll(commandArgs); + args.addAll( + List.of( + "--epoch", + "1024", + "--validator-public-keys", + validatorPubKey1, + "--save-exits-path", + tempDir.toAbsolutePath().toString())); + + beaconNodeCommand.parse(args.toArray(new String[0])); + final String outString = stdOut.toString(UTF_8); + assertThat(StringUtils.countMatches(outString, "Writing signed exit for")).isEqualTo(1); + } + @Test void shouldUseCurrentForkDomainForSignatureBeforeDeneb() throws JsonProcessingException { setUserInput("yes"); configureSuccessfulSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); + final Supplier> exitsCapture = configureSuccessfulVoluntaryExitResponseWithCapture(mockBeaconServer); @@ -404,6 +463,8 @@ void shouldUseCurrentForkDomainForSignatureBeforeDeneb() throws JsonProcessingEx void shouldUseCapellaForkDomainForSignatureAfterCapella() throws JsonProcessingException { setUserInput("yes"); configureSuccessfulDenebSpecResponse(mockBeaconServer); + configureSuccessfulGenesisResponse(mockBeaconServer); + final Supplier> exitsCapture = configureSuccessfulVoluntaryExitResponseWithCapture(mockBeaconServer); @@ -464,23 +525,21 @@ private void configureSuccessfulSpecResponse( .respond(response().withStatusCode(200).withBody(getTestSpecJsonString(spec))); } - private void configureSuccessfulHeadResponse(final ClientAndServer mockBeaconServer) - throws IOException { - final String testHead = - Resources.toString( - Resources.getResource("tech/pegasys/teku/cli/subcommand/voluntary-exit/head.json"), - UTF_8); - mockBeaconServer - .when(request().withPath("/eth/v1/beacon/headers/head")) - .respond(response().withStatusCode(200).withBody(testHead)); - } + private void configureSuccessfulGenesisResponse(final ClientAndServer mockBeaconServer) { + final TimeProvider timeProvider = new SystemTimeProvider(); + final SpecConfig config = spec.getGenesisSpec().getConfig(); + final UInt64 genesisTime = + timeProvider + .getTimeInSeconds() + .minus(1020L * config.getSecondsPerSlot() * config.getSlotsPerEpoch()); - private void configureSuccessfulGenesisResponse(final ClientAndServer mockBeaconServer) - throws IOException { final String testHead = - Resources.toString( - Resources.getResource("tech/pegasys/teku/cli/subcommand/voluntary-exit/genesis.json"), - UTF_8); + String.format( + "{ \"data\": {\"genesis_time\": \"%s\"," + + "\"genesis_validators_root\": \"0xf03f804ff1c97ada13050eb617e66e88e1199c2ce1be0b6b27e36fafb8d3ee48\"," + + "\"genesis_fork_version\": \"0x00004105\"}}", + genesisTime); + mockBeaconServer .when(request().withPath("/eth/v1/beacon/genesis")) .respond(response().withStatusCode(200).withBody(testHead)); diff --git a/teku/src/integration-test/resources/tech/pegasys/teku/cli/subcommand/voluntary-exit/genesis.json b/teku/src/integration-test/resources/tech/pegasys/teku/cli/subcommand/voluntary-exit/genesis.json deleted file mode 100644 index 889497930b1..00000000000 --- a/teku/src/integration-test/resources/tech/pegasys/teku/cli/subcommand/voluntary-exit/genesis.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "data": { - "genesis_time": "1663181025", - "genesis_validators_root": "0xf03f804ff1c97ada13050eb617e66e88e1199c2ce1be0b6b27e36fafb8d3ee48", - "genesis_fork_version": "0x00004105" - } -} \ No newline at end of file diff --git a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommand.java b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommand.java index fd2137a0060..a91bbab057e 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommand.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/VoluntaryExitCommand.java @@ -59,6 +59,8 @@ import tech.pegasys.teku.infrastructure.json.JsonUtil; import tech.pegasys.teku.infrastructure.logging.SubCommandLogger; import tech.pegasys.teku.infrastructure.logging.ValidatorLogger; +import tech.pegasys.teku.infrastructure.time.SystemTimeProvider; +import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.service.serviceutils.layout.DataDirLayout; import tech.pegasys.teku.spec.Spec; @@ -102,6 +104,7 @@ public class VoluntaryExitCommand implements Callable { private TekuConfiguration config; private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private Spec spec; + private final TimeProvider timeProvider = new SystemTimeProvider(); static final String WITHDRAWALS_PERMANENT_MESASGE = "These validators won't be able to be re-activated once this operation is complete."; @@ -355,14 +358,12 @@ private String prettyExitMessage( }); } - private Optional getEpoch() { - return apiClient - .getBlockHeader("head") - .map(response -> spec.computeEpochAtSlot(response.data.header.message.slot)); - } - - private Optional getGenesisRoot() { - return typeDefClient.getGenesis().map(GenesisData::getGenesisValidatorsRoot); + private UInt64 getEpochFromGenesisData(final GenesisData genesisData) { + final UInt64 genesisTime = genesisData.getGenesisTime(); + final UInt64 currentTime = timeProvider.getTimeInSeconds(); + final UInt64 slot = + spec.getGenesisSpec().miscHelpers().computeSlotAtTime(genesisTime, currentTime); + return spec.computeEpochAtSlot(slot); } private void initialise() { @@ -400,11 +401,12 @@ private void initialise() { spec = SpecFactory.create(network); } - validateOrDefaultEpoch(); + final Optional maybeGenesisData = typeDefClient.getGenesis(); + validateOrDefaultEpoch(maybeGenesisData); fork = spec.getForkSchedule().getFork(epoch); // get genesis time - final Optional maybeRoot = getGenesisRoot(); + final Optional maybeRoot = maybeGenesisData.map(GenesisData::getGenesisValidatorsRoot); if (maybeRoot.isEmpty()) { throw new InvalidConfigurationException( "Unable to fetch genesis data, cannot generate an exit."); @@ -462,13 +464,13 @@ private void initialise() { } } - private void validateOrDefaultEpoch() { - final Optional maybeEpoch = getEpoch(); + private void validateOrDefaultEpoch(final Optional maybeGenesisData) { + final Optional maybeEpoch = maybeGenesisData.map(this::getEpochFromGenesisData); if (epoch == null) { if (maybeEpoch.isEmpty()) { throw new InvalidConfigurationException( - "Could not calculate epoch from latest block header, please specify --epoch"); + "Could not calculate epoch from genesis data, please specify --epoch"); } epoch = maybeEpoch.orElseThrow(); } else if (maybeEpoch.isPresent() diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClient.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClient.java index 9358406542e..c5c4912dd0d 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClient.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClient.java @@ -16,7 +16,6 @@ import static java.util.Collections.emptyMap; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_AGGREGATE; -import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_BLOCK_HEADER; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_GENESIS; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_PROPOSER_DUTIES; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_SYNC_COMMITTEE_CONTRIBUTION; @@ -52,7 +51,6 @@ import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.api.request.v1.validator.BeaconCommitteeSubscriptionRequest; -import tech.pegasys.teku.api.response.v1.beacon.GetBlockHeaderResponse; import tech.pegasys.teku.api.response.v1.beacon.GetGenesisResponse; import tech.pegasys.teku.api.response.v1.beacon.GetStateValidatorsResponse; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse; @@ -96,15 +94,6 @@ public Optional getGenesis() { return get(GET_GENESIS, EMPTY_MAP, createHandler(GetGenesisResponse.class)); } - public Optional getBlockHeader(final String blockId) { - return get( - GET_BLOCK_HEADER, - Map.of("block_id", blockId), - EMPTY_MAP, - EMPTY_MAP, - createHandler(GetBlockHeaderResponse.class)); - } - /** * GET