Skip to content

Commit

Permalink
Use JsonTypeDef for PostStateValidators request (Consensys#7985)
Browse files Browse the repository at this point in the history
  • Loading branch information
courtneyeh authored Feb 28, 2024
1 parent 11720af commit 53d456c
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<ValidatorResponse> 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<List<ValidatorResponse>> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -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<Integer> 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<StateValidatorData> expected =
List.of(generateStateValidatorData(), generateStateValidatorData());
final ObjectAndMetaData<List<StateValidatorData>> 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<List<StateValidatorData>> 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");
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -136,29 +135,24 @@ public SafeFuture<Map<BLSPublicKey, Integer>> getValidatorIndices(
}
return sendRequest(
() ->
makeValidatorRequest(
publicKeys, StateValidatorData::getIntegerIndex, ValidatorResponse::getIndex)
makeValidatorRequest(publicKeys, StateValidatorData::getIntegerIndex)
.orElse(emptyMap()));
}

@Override
public SafeFuture<Optional<Map<BLSPublicKey, ValidatorStatus>>> getValidatorStatuses(
final Collection<BLSPublicKey> publicKeys) {
return sendRequest(
() ->
makeValidatorRequest(
publicKeys, StateValidatorData::getStatus, ValidatorResponse::getStatus));
return sendRequest(() -> makeValidatorRequest(publicKeys, StateValidatorData::getStatus));
}

private <T> Optional<Map<BLSPublicKey, T>> makeValidatorRequest(
final Collection<BLSPublicKey> publicKeys,
final Function<StateValidatorData, T> valueExtractor,
final Function<ValidatorResponse, T> validatorResponseExtractor) {
final Function<StateValidatorData, T> 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.");
Expand All @@ -175,10 +169,9 @@ private List<String> convertPublicKeysToValidatorIds(final Collection<BLSPublicK
}

private <T> Map<BLSPublicKey, T> convertToValidatorMap(
final List<ValidatorResponse> validatorResponses,
final Function<ValidatorResponse, T> valueExtractor) {
return validatorResponses.stream()
.collect(toMap(ValidatorResponse::getPublicKey, valueExtractor));
final List<StateValidatorData> validatorData,
final Function<StateValidatorData, T> valueExtractor) {
return validatorData.stream().collect(toMap(StateValidatorData::getPublicKey, valueExtractor));
}

private <T> Optional<Map<BLSPublicKey, T>> makeBatchedValidatorRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -131,29 +128,6 @@ public Optional<List<ValidatorResponse>> getValidators(final List<String> valida
.map(response -> response.data);
}

/**
* <a
* href="https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/postStateValidators">POST
* Get validators from state</a>
*/
@Override
public Optional<List<ValidatorResponse>> postValidators(final List<String> 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<PostAttesterDutiesResponse> getAttestationDuties(
final UInt64 epoch, final Collection<Integer> validatorIndices) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ public interface ValidatorRestApiClient {

Optional<List<ValidatorResponse>> getValidators(List<String> validatorIds);

Optional<List<ValidatorResponse>> postValidators(List<String> validatorIds);

Optional<PostAttesterDutiesResponse> getAttestationDuties(
final UInt64 epoch, final Collection<Integer> validatorIndices);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 =
Expand Down Expand Up @@ -110,6 +113,12 @@ public Optional<List<StateValidatorData>> getStateValidators(final List<String>
.map(ObjectAndMetaData::getData);
}

public Optional<List<StateValidatorData>> postStateValidators(final List<String> validatorIds) {
return postStateValidatorsRequest
.postStateValidators(validatorIds)
.map(ObjectAndMetaData::getData);
}

public SendSignedBlockResult sendSignedBlock(final SignedBlockContainer blockContainer) {
return sendSignedBlockRequest.sendSignedBlock(blockContainer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ public ResponseHandler<TObject> withHandler(
return this;
}

public ResponseHandler<TObject> withHandler(
final Handler<TObject> handler, final int... responseCodes) {
for (final int responseCode : responseCodes) {
handlers.put(responseCode, handler);
}
return this;
}

private Optional<TObject> defaultOkHandler(final Request request, final Response response)
throws IOException {
final ResponseBody responseBody = response.body();
Expand Down
Loading

0 comments on commit 53d456c

Please sign in to comment.