diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClientTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClientTest.java index 5eee1f7ad68..02659f25a61 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClientTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/apiclient/OkHttpValidatorRestApiClientTest.java @@ -20,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.fail; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; -import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_METHOD_NOT_ALLOWED; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NO_CONTENT; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; @@ -39,8 +38,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; 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.PostDataFailure; @@ -151,48 +148,6 @@ public void getValidators_WhenSuccess_ReturnsResponse() { assertThat(result.get()).usingRecursiveComparison().isEqualTo(expected); } - @Test - void postValidators_MakesExpectedRequest() throws Exception { - mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); - - apiClient.postValidators(List.of("1", "0x1234")); - - final RecordedRequest request = mockWebServer.takeRequest(); - assertThat(request.getMethod()).isEqualTo("POST"); - assertThat(request.getPath()).contains(ValidatorApiMethod.GET_VALIDATORS.getPath(emptyMap())); - assertThat(request.getBody().readUtf8()).isEqualTo("{\"ids\":[\"1\",\"0x1234\"]}"); - } - - @Test - public void postValidators_WhenNoContent_ReturnsEmpty() { - mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); - - assertThat(apiClient.postValidators(List.of("1"))).isEmpty(); - } - - @ParameterizedTest - @ValueSource(ints = {SC_BAD_REQUEST, SC_NOT_FOUND, SC_METHOD_NOT_ALLOWED}) - public void postValidators_WhenNotExisting_ThrowsException(final int responseCode) { - mockWebServer.enqueue(new MockResponse().setResponseCode(responseCode)); - - assertThatThrownBy(() -> apiClient.postValidators(List.of("1"))) - .isInstanceOf(PostStateValidatorsNotExistingException.class); - } - - @Test - public void postValidators_WhenSuccess_ReturnsResponse() { - final List expected = - List.of(schemaObjects.validatorResponse(), schemaObjects.validatorResponse()); - final GetStateValidatorsResponse response = new GetStateValidatorsResponse(false, expected); - - mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(asJson(response))); - - Optional> result = apiClient.postValidators(List.of("1", "2")); - - assertThat(result).isPresent(); - assertThat(result.get()).usingRecursiveComparison().isEqualTo(expected); - } - @Test public void sendVoluntaryExit_makesExpectedRequest() throws Exception { final SignedVoluntaryExit exit = schemaObjects.signedVoluntaryExit(); diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java index c6250327080..72af906bb16 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java @@ -13,11 +13,22 @@ package tech.pegasys.teku.validator.remote.typedef; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assumptions.assumeThat; +import static tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorDataBuilder.STATE_VALIDATORS_RESPONSE_TYPE; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_METHOD_NOT_ALLOWED; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NO_CONTENT; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.json.JsonUtil.serialize; +import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; import java.util.Optional; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.RecordedRequest; @@ -26,7 +37,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestTemplate; import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; -import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; +import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; import tech.pegasys.teku.infrastructure.ssz.SszDataAssert; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -37,10 +49,14 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.teku.spec.schemas.ApiSchemas; import tech.pegasys.teku.validator.api.SendSignedBlockResult; import tech.pegasys.teku.validator.api.required.SyncingStatus; +import tech.pegasys.teku.validator.remote.apiclient.PostStateValidatorsNotExistingException; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; import tech.pegasys.teku.validator.remote.typedef.handlers.RegisterValidatorsRequest; @TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) @@ -136,7 +152,7 @@ void publishesBlindedBlockJsonEncoded() throws InterruptedException, JsonProcess final RecordedRequest recordedRequest = mockWebServer.takeRequest(); final String expectedRequest = - JsonUtil.serialize( + serialize( signedBeaconBlock, spec.atSlot(UInt64.ONE) .getSchemaDefinitions() @@ -193,7 +209,7 @@ void registerValidators_makesJsonRequest() throws InterruptedException, JsonProc dataStructureUtil.randomSignedValidatorRegistrations(5); final String expectedRequest = - JsonUtil.serialize( + serialize( validatorRegistrations, ApiSchemas.SIGNED_VALIDATOR_REGISTRATIONS_SCHEMA.getJsonTypeDefinition()); @@ -323,6 +339,77 @@ void blockV3ShouldFallbacksToBlockV2WhenNotFound() assertThat(secondRequest.getPath()).startsWith("/eth/v1/validator/blinded_blocks"); } + @TestTemplate + void postValidators_MakesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + okHttpValidatorTypeDefClient.postStateValidators(List.of("1", "0x1234")); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + + assertThat(request.getPath()).contains(ValidatorApiMethod.GET_VALIDATORS.getPath(emptyMap())); + assertThat(request.getBody().readUtf8()).isEqualTo("{\"ids\":[\"1\",\"0x1234\"]}"); + } + + @TestTemplate + public void postValidators_WhenNoContent_ReturnsEmpty() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + assertThat(okHttpValidatorTypeDefClient.postStateValidators(List.of("1"))).isEmpty(); + } + + @TestTemplate + public void postValidators_WhenNotExisting_ThrowsException() { + final List responseCodes = + List.of(SC_BAD_REQUEST, SC_NOT_FOUND, SC_METHOD_NOT_ALLOWED); + for (int code : responseCodes) { + checkThrowsExceptionForCode(code); + } + } + + private void checkThrowsExceptionForCode(final int responseCode) { + mockWebServer.enqueue(new MockResponse().setResponseCode(responseCode)); + assertThatThrownBy(() -> okHttpValidatorTypeDefClient.postStateValidators(List.of("1"))) + .isInstanceOf(PostStateValidatorsNotExistingException.class); + } + + @TestTemplate + public void postValidators_WhenSuccess_ReturnsResponse() throws JsonProcessingException { + final List expected = + List.of(generateStateValidatorData(), generateStateValidatorData()); + final ObjectAndMetaData> response = + new ObjectAndMetaData<>(expected, specMilestone, false, true, false); + + final String body = serialize(response, STATE_VALIDATORS_RESPONSE_TYPE); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(body)); + + Optional> result = + okHttpValidatorTypeDefClient.postStateValidators(List.of("1", "2")); + + assertThat(result).isPresent(); + assertThat(result.get()).isEqualTo(expected); + } + + private StateValidatorData generateStateValidatorData() { + final long index = dataStructureUtil.randomLong(); + final Validator validator = + new Validator( + dataStructureUtil.randomPublicKey(), + dataStructureUtil.randomBytes32(), + dataStructureUtil.randomUInt64(), + false, + UInt64.ZERO, + UInt64.ZERO, + FAR_FUTURE_EPOCH, + FAR_FUTURE_EPOCH); + return new StateValidatorData( + UInt64.valueOf(index), + dataStructureUtil.randomUInt64(), + ValidatorStatus.active_ongoing, + validator); + } + private void verifyRegisterValidatorsPostRequest( final RecordedRequest recordedRequest, final String expectedContentType) { assertThat(recordedRequest.getPath()).isEqualTo("/eth/v1/validator/register_validator"); @@ -341,7 +428,7 @@ private void assertJsonEquals(final String actual, final String expected) { private String serializeBlockContainer(final BlockContainer blockContainer) throws JsonProcessingException { - return JsonUtil.serialize( + return serialize( blockContainer, blockContainer.isBlinded() ? schemaDefinitions.getBlindedBlockContainerSchema().getJsonTypeDefinition() diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java index 12cc636f128..310a449a157 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java @@ -38,7 +38,6 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse; -import tech.pegasys.teku.api.response.v1.beacon.ValidatorResponse; import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; import tech.pegasys.teku.api.response.v1.validator.PostSyncDutiesResponse; import tech.pegasys.teku.api.response.v1.validator.PostValidatorLivenessResponse; @@ -136,29 +135,24 @@ public SafeFuture> getValidatorIndices( } return sendRequest( () -> - makeValidatorRequest( - publicKeys, StateValidatorData::getIntegerIndex, ValidatorResponse::getIndex) + makeValidatorRequest(publicKeys, StateValidatorData::getIntegerIndex) .orElse(emptyMap())); } @Override public SafeFuture>> getValidatorStatuses( final Collection publicKeys) { - return sendRequest( - () -> - makeValidatorRequest( - publicKeys, StateValidatorData::getStatus, ValidatorResponse::getStatus)); + return sendRequest(() -> makeValidatorRequest(publicKeys, StateValidatorData::getStatus)); } private Optional> makeValidatorRequest( final Collection publicKeys, - final Function valueExtractor, - final Function validatorResponseExtractor) { + final Function valueExtractor) { if (usePostValidatorsEndpoint.get()) { try { - return apiClient - .postValidators(convertPublicKeysToValidatorIds(publicKeys)) - .map(responses -> convertToValidatorMap(responses, validatorResponseExtractor)); + return typeDefClient + .postStateValidators(convertPublicKeysToValidatorIds(publicKeys)) + .map(responses -> convertToValidatorMap(responses, valueExtractor)); } catch (final PostStateValidatorsNotExistingException __) { LOG.debug( "POST method is not available for getting validators from state. Will use GET instead."); @@ -175,10 +169,9 @@ private List convertPublicKeysToValidatorIds(final Collection Map convertToValidatorMap( - final List validatorResponses, - final Function valueExtractor) { - return validatorResponses.stream() - .collect(toMap(ValidatorResponse::getPublicKey, valueExtractor)); + final List validatorData, + final Function valueExtractor) { + return validatorData.stream().collect(toMap(StateValidatorData::getPublicKey, valueExtractor)); } private Optional> makeBatchedValidatorRequest( 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 a9bcff6d0d9..ccd9bae21a7 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 @@ -14,8 +14,6 @@ package tech.pegasys.teku.validator.remote.apiclient; import static java.util.Collections.emptyMap; -import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; -import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_METHOD_NOT_ALLOWED; 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_ATTESTATION_DUTIES; @@ -56,7 +54,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.api.request.v1.beacon.PostStateValidatorsRequest; 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; @@ -131,29 +128,6 @@ public Optional> getValidators(final List valida .map(response -> response.data); } - /** - * POST - * Get validators from state - */ - @Override - public Optional> postValidators(final List validatorIds) { - final PostStateValidatorsRequest requestBody = new PostStateValidatorsRequest(validatorIds); - return post( - GET_VALIDATORS, - EMPTY_MAP, - requestBody, - createHandler(GetStateValidatorsResponse.class) - .withHandler( - (request, response) -> { - throw new PostStateValidatorsNotExistingException(); - }, - SC_BAD_REQUEST, - SC_NOT_FOUND, - SC_METHOD_NOT_ALLOWED)) - .map(response -> response.data); - } - @Override public Optional getAttestationDuties( final UInt64 epoch, final Collection validatorIndices) { diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorRestApiClient.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorRestApiClient.java index 4d343fad5bc..c55bb66845b 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorRestApiClient.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorRestApiClient.java @@ -43,8 +43,6 @@ public interface ValidatorRestApiClient { Optional> getValidators(List validatorIds); - Optional> postValidators(List validatorIds); - Optional getAttestationDuties( final UInt64 epoch, final Collection validatorIndices); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java index 5a6c74a3f36..6a8ebd04ecc 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java @@ -43,6 +43,7 @@ import tech.pegasys.teku.validator.remote.typedef.handlers.GetProposerDutiesRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.GetStateValidatorsRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.GetSyncingStatusRequest; +import tech.pegasys.teku.validator.remote.typedef.handlers.PostStateValidatorsRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.ProduceBlockRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.RegisterValidatorsRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.SendSignedBlockRequest; @@ -58,6 +59,7 @@ public class OkHttpValidatorTypeDefClient extends OkHttpValidatorMinimalTypeDefC private final GetGenesisRequest getGenesisRequest; private final GetProposerDutiesRequest getProposerDutiesRequest; private final GetStateValidatorsRequest getStateValidatorsRequest; + private final PostStateValidatorsRequest postStateValidatorsRequest; private final SendSignedBlockRequest sendSignedBlockRequest; private final RegisterValidatorsRequest registerValidatorsRequest; private final CreateAttestationDataRequest createAttestationDataRequest; @@ -76,6 +78,7 @@ public OkHttpValidatorTypeDefClient( this.getGenesisRequest = new GetGenesisRequest(okHttpClient, baseEndpoint); this.getProposerDutiesRequest = new GetProposerDutiesRequest(baseEndpoint, okHttpClient); this.getStateValidatorsRequest = new GetStateValidatorsRequest(baseEndpoint, okHttpClient); + this.postStateValidatorsRequest = new PostStateValidatorsRequest(baseEndpoint, okHttpClient); this.sendSignedBlockRequest = new SendSignedBlockRequest(spec, baseEndpoint, okHttpClient, preferSszBlockEncoding); this.registerValidatorsRequest = @@ -110,6 +113,12 @@ public Optional> getStateValidators(final List .map(ObjectAndMetaData::getData); } + public Optional> postStateValidators(final List validatorIds) { + return postStateValidatorsRequest + .postStateValidators(validatorIds) + .map(ObjectAndMetaData::getData); + } + public SendSignedBlockResult sendSignedBlock(final SignedBlockContainer blockContainer) { return sendSignedBlockRequest.sendSignedBlock(blockContainer); } diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/ResponseHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/ResponseHandler.java index 27a1031bec4..5934fb4c9e7 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/ResponseHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/ResponseHandler.java @@ -78,6 +78,14 @@ public ResponseHandler withHandler( return this; } + public ResponseHandler withHandler( + final Handler handler, final int... responseCodes) { + for (final int responseCode : responseCodes) { + handlers.put(responseCode, handler); + } + return this; + } + private Optional defaultOkHandler(final Request request, final Response response) throws IOException { final ResponseBody responseBody = response.body(); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostStateValidatorsRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostStateValidatorsRequest.java new file mode 100644 index 00000000000..d1cccf58c32 --- /dev/null +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostStateValidatorsRequest.java @@ -0,0 +1,55 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorDataBuilder.STATE_VALIDATORS_RESPONSE_TYPE; +import static tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorRequestBodyType.STATE_VALIDATOR_REQUEST_TYPE; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_METHOD_NOT_ALLOWED; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; +import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_VALIDATORS; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; +import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorRequestBodyType; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.validator.remote.apiclient.PostStateValidatorsNotExistingException; +import tech.pegasys.teku.validator.remote.typedef.ResponseHandler; + +public class PostStateValidatorsRequest extends AbstractTypeDefRequest { + public PostStateValidatorsRequest(final HttpUrl baseEndpoint, final OkHttpClient okHttpClient) { + super(baseEndpoint, okHttpClient); + } + + public Optional>> postStateValidators( + final List validatorIds) { + return postJson( + GET_VALIDATORS, + Map.of(), + new StateValidatorRequestBodyType(validatorIds, List.of()), + STATE_VALIDATOR_REQUEST_TYPE, + new ResponseHandler<>(STATE_VALIDATORS_RESPONSE_TYPE) + .withHandler( + (request, response) -> { + throw new PostStateValidatorsNotExistingException(); + }, + SC_BAD_REQUEST, + SC_NOT_FOUND, + SC_METHOD_NOT_ALLOWED)); + } +} diff --git a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java index caee47e2c9c..57fe8bd35df 100644 --- a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java +++ b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java @@ -50,13 +50,11 @@ import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailure; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse; -import tech.pegasys.teku.api.response.v1.beacon.ValidatorResponse; import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; import tech.pegasys.teku.api.response.v1.validator.PostAttesterDutiesResponse; import tech.pegasys.teku.api.response.v1.validator.PostValidatorLivenessResponse; import tech.pegasys.teku.api.response.v1.validator.ValidatorLiveness; import tech.pegasys.teku.api.schema.BLSPubKey; -import tech.pegasys.teku.api.schema.Validator; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; @@ -82,6 +80,7 @@ import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; +import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.datastructures.validator.SubnetSubscription; import tech.pegasys.teku.spec.util.DataStructureUtil; @@ -224,8 +223,8 @@ void getValidatorIndices_MakesSingleRequestUsingPost() { key1.toBytesCompressed().toHexString(), key2.toBytesCompressed().toHexString(), key3.toBytesCompressed().toHexString()); - when(apiClient.postValidators(expectedValidatorIds)) - .thenReturn(Optional.of(List.of(validatorResponse(1, key1), validatorResponse(2, key2)))); + when(typeDefClient.postStateValidators(expectedValidatorIds)) + .thenReturn(Optional.of(List.of(stateValidatorData(1, key1), stateValidatorData(2, key2)))); final SafeFuture> future = apiHandler.getValidatorIndices(List.of(key1, key2, key3)); @@ -233,7 +232,7 @@ void getValidatorIndices_MakesSingleRequestUsingPost() { asyncRunner.executeQueuedActions(); assertThat(future).isCompleted(); assertThat(safeJoin(future)).containsOnly(entry(key1, 1), entry(key2, 2)); - verify(apiClient).postValidators(expectedValidatorIds); + verify(typeDefClient).postStateValidators(expectedValidatorIds); } @Test @@ -248,7 +247,8 @@ void getValidatorIndices_DoesNotAttemptPostAgainIfNotExisting() { key3.toBytesCompressed().toHexString()); // simulate POST not existing - when(apiClient.postValidators(any())).thenThrow(PostStateValidatorsNotExistingException.class); + when(typeDefClient.postStateValidators(any())) + .thenThrow(PostStateValidatorsNotExistingException.class); when(typeDefClient.getStateValidators(expectedValidatorIds)) .thenReturn(Optional.of(List.of(stateValidatorData(1, key1), stateValidatorData(2, key2)))); @@ -264,7 +264,7 @@ void getValidatorIndices_DoesNotAttemptPostAgainIfNotExisting() { assertThat(safeJoin(future)).containsOnly(entry(key1, 1), entry(key2, 2)); assertThat(safeJoin(future1)).containsOnly(entry(key1, 1), entry(key2, 2)); // POST only called once - verify(apiClient, times(1)).postValidators(expectedValidatorIds); + verify(typeDefClient, times(1)).postStateValidators(expectedValidatorIds); // GET called twice verify(typeDefClient, times(2)).getStateValidators(expectedValidatorIds); } @@ -272,7 +272,8 @@ void getValidatorIndices_DoesNotAttemptPostAgainIfNotExisting() { @Test void getValidatorIndices_WithSmallNumberOfPublicKeys_RequestsSingleBatch() { // simulate POST not existing - when(apiClient.postValidators(any())).thenThrow(PostStateValidatorsNotExistingException.class); + when(typeDefClient.postStateValidators(any())) + .thenThrow(PostStateValidatorsNotExistingException.class); final BLSPublicKey key1 = dataStructureUtil.randomPublicKey(); final BLSPublicKey key2 = dataStructureUtil.randomPublicKey(); final BLSPublicKey key3 = dataStructureUtil.randomPublicKey(); @@ -296,7 +297,8 @@ void getValidatorIndices_WithSmallNumberOfPublicKeys_RequestsSingleBatch() { @Test void getValidatorIndices_WithLargeNumberOfPublicKeys_CombinesMultipleBatches() { // simulate POST not existing - when(apiClient.postValidators(any())).thenThrow(PostStateValidatorsNotExistingException.class); + when(typeDefClient.postStateValidators(any())) + .thenThrow(PostStateValidatorsNotExistingException.class); // Need to ensure the URL length limit isn't exceeded, so send requests in batches final List allKeys = IntStream.range(0, MAX_PUBLIC_KEY_BATCH_SIZE * 3 - 2) @@ -816,25 +818,9 @@ private T unwrapToValue(SafeFuture> future) { } } - private ValidatorResponse validatorResponse(final long index, final BLSPublicKey publicKey) { - return new ValidatorResponse( - UInt64.valueOf(index), - dataStructureUtil.randomUInt64(), - ValidatorStatus.active_ongoing, - new Validator( - new BLSPubKey(publicKey), - dataStructureUtil.randomBytes32(), - dataStructureUtil.randomUInt64(), - false, - UInt64.ZERO, - UInt64.ZERO, - FAR_FUTURE_EPOCH, - FAR_FUTURE_EPOCH)); - } - private StateValidatorData stateValidatorData(final long index, final BLSPublicKey publicKey) { - final tech.pegasys.teku.spec.datastructures.state.Validator validator = - new tech.pegasys.teku.spec.datastructures.state.Validator( + final Validator validator = + new Validator( publicKey, dataStructureUtil.randomBytes32(), dataStructureUtil.randomUInt64(),