diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index 677244a8b50..150a5698d88 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -1285,7 +1285,7 @@ private boolean validatorIsLive( return validatorLivenessAtEpochs.stream() .anyMatch( validatorLivenessAtEpoch -> - validatorLivenessAtEpoch.getIndex().equals(validatorIndex) + validatorLivenessAtEpoch.index().equals(validatorIndex) && validatorLivenessAtEpoch.isLive()); } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/ValidatorLivenessAtEpoch.java b/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/ValidatorLivenessAtEpoch.java index 9911abc090d..919170f94c8 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/ValidatorLivenessAtEpoch.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/migrated/ValidatorLivenessAtEpoch.java @@ -16,53 +16,44 @@ import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.BOOLEAN_TYPE; import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.UINT64_TYPE; -import java.util.Objects; -import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -public class ValidatorLivenessAtEpoch { - private final UInt64 index; - private final boolean isLive; +public record ValidatorLivenessAtEpoch(UInt64 index, boolean isLive) { - public ValidatorLivenessAtEpoch(final UInt64 index, final boolean isLive) { - this.index = index; - this.isLive = isLive; + public static DeserializableTypeDefinition getJsonTypeDefinition() { + return DeserializableTypeDefinition.object( + ValidatorLivenessAtEpoch.class, ValidatorLivenessAtEpoch.Builder.class) + .initializer(Builder::new) + .finisher(Builder::build) + .withField("index", UINT64_TYPE, ValidatorLivenessAtEpoch::index, Builder::index) + .withField("is_live", BOOLEAN_TYPE, ValidatorLivenessAtEpoch::isLive, Builder::isLive) + .build(); } - public UInt64 getIndex() { - return index; + @Override + public String toString() { + return "ValidatorLivenessAtEpoch{" + "index=" + index + ", isLive=" + isLive + '}'; } - public boolean isLive() { - return isLive; - } + public static class Builder { + private UInt64 index; + private boolean isLive; - public static SerializableTypeDefinition getJsonTypeDefinition() { - return SerializableTypeDefinition.object(ValidatorLivenessAtEpoch.class) - .withField("index", UINT64_TYPE, ValidatorLivenessAtEpoch::getIndex) - .withField("is_live", BOOLEAN_TYPE, ValidatorLivenessAtEpoch::isLive) - .build(); - } + public Builder() {} - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; + public Builder isLive(final boolean isLive) { + this.isLive = isLive; + return this; } - final ValidatorLivenessAtEpoch that = (ValidatorLivenessAtEpoch) o; - return isLive == that.isLive && Objects.equals(index, that.index); - } - @Override - public int hashCode() { - return Objects.hash(index, isLive); - } + public Builder index(final UInt64 index) { + this.index = index; + return this; + } - @Override - public String toString() { - return "ValidatorLivenessAtEpoch{" + "index=" + index + ", isLive=" + isLive + '}'; + public ValidatorLivenessAtEpoch build() { + return new ValidatorLivenessAtEpoch(index, isLive); + } } } diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/doppelganger/DoppelgangerDetector.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/doppelganger/DoppelgangerDetector.java index 663b8ea453c..bb4e3b9fc41 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/doppelganger/DoppelgangerDetector.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/doppelganger/DoppelgangerDetector.java @@ -313,7 +313,7 @@ private void checkValidatorDoppelgangers( doppelgangers.forEach( doppelganger -> detectedDoppelgangers.putIfAbsent( - doppelganger.getRight().getIndex(), doppelganger.getLeft())); + doppelganger.getRight().index(), doppelganger.getLeft())); if (allKeysAreActive()) { statusLog.doppelgangerDetectionEnd( mapToAbbreviatedKeys(pubKeys).collect(Collectors.toSet()), @@ -327,7 +327,7 @@ private void checkValidatorDoppelgangers( private Map mapLivenessAtEpochToIndicesByPubKeyStrings( final List> doppelgangers) { return doppelgangers.stream() - .collect(Collectors.toMap(e -> e.getRight().getIndex(), e -> e.getLeft().toString())); + .collect(Collectors.toMap(e -> e.getRight().index(), e -> e.getLeft().toString())); } private Stream mapToAbbreviatedKeys(final Set pubKeys) { @@ -344,7 +344,7 @@ private List> filterLiveValidators( .filter( validatorLivenessAtEpoch -> validatorPubKeysByIndices.containsValue( - validatorLivenessAtEpoch.getIndex()) + validatorLivenessAtEpoch.index()) && validatorLivenessAtEpoch.isLive()) .map( validatorLivenessAtEpoch -> @@ -352,8 +352,7 @@ private List> filterLiveValidators( validatorPubKeysByIndices.entrySet().stream() .filter( e -> - e.getValue() - .equals(validatorLivenessAtEpoch.getIndex())) + e.getValue().equals(validatorLivenessAtEpoch.index())) .findFirst() .get() .getKey(), diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequestTest.java index 685412e70db..4550a87d6ad 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequestTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequestTest.java @@ -31,6 +31,7 @@ import tech.pegasys.teku.spec.TestSpecContext; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; @@ -43,7 +44,8 @@ public class CreateAggregateAttestationRequestTest extends AbstractTypeDefReques @BeforeEach void setupRequest() { createAggregateAttestationRequest = - new CreateAggregateAttestationRequest(mockWebServer.url("/"), okHttpClient, slot, spec); + new CreateAggregateAttestationRequest( + mockWebServer.url("/"), okHttpClient, new SchemaDefinitionCache(spec)); } @TestTemplate @@ -52,7 +54,7 @@ public void getAggregateAttestation_makesExpectedRequest() throws Exception { mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); - createAggregateAttestationRequest.createAggregate(attestationHashTreeRoot); + createAggregateAttestationRequest.createAggregate(slot, attestationHashTreeRoot); final RecordedRequest request = mockWebServer.takeRequest(); assertThat(request.getMethod()).isEqualTo("GET"); @@ -76,7 +78,7 @@ public void shouldGetAggregateAttestation() { mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(mockResponse)); final Optional maybeAttestation = - createAggregateAttestationRequest.createAggregate(attestation.hashTreeRoot()); + createAggregateAttestationRequest.createAggregate(slot, attestation.hashTreeRoot()); assertThat(maybeAttestation).isPresent(); assertThat(maybeAttestation.get()).isEqualTo(getAggregateAttestationResponse.getData()); diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequestTest.java index 9ea4dec2ba9..628ca2c323f 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequestTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequestTest.java @@ -31,7 +31,7 @@ public class GetSyncingStatusRequestTest extends AbstractTypeDefRequestTestBase @BeforeEach void setupRequest() { - request = new GetSyncingStatusRequest(okHttpClient, mockWebServer.url("/")); + request = new GetSyncingStatusRequest(mockWebServer.url("/"), okHttpClient); } @TestTemplate diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequestTest.java new file mode 100644 index 00000000000..abf6d6fba30 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequestTest.java @@ -0,0 +1,73 @@ +/* + * 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 org.assertj.core.api.Assertions.assertThat; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(milestone = SpecMilestone.PHASE0, network = Eth2Network.MINIMAL) +public class SendValidatorLivenessRequestTest extends AbstractTypeDefRequestTestBase { + + private SendValidatorLivenessRequest sendValidatorLivenessRequest; + + @BeforeEach + void setupRequest() { + sendValidatorLivenessRequest = + new SendValidatorLivenessRequest(mockWebServer.url("/"), okHttpClient); + } + + @TestTemplate + public void sendValidatorLiveness_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + sendValidatorLivenessRequest.submit(UInt64.ONE, List.of(UInt64.valueOf(1))); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_VALIDATOR_LIVENESS.getPath(Map.of("epoch", "1"))); + } + + @TestTemplate + public void sendValidatorLiveness_readsResponse() throws Exception { + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(SC_OK) + .setBody("{\"data\":[{\"index\":\"1\",\"is_live\":false}]}")); + final Optional> result = + sendValidatorLivenessRequest.submit(UInt64.ONE, List.of(UInt64.valueOf(1))); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_VALIDATOR_LIVENESS.getPath(Map.of("epoch", "1"))); + + assertThat(result).isPresent(); + assertThat(result.get()).containsExactly(new ValidatorLivenessAtEpoch(UInt64.ONE, false)); + } +} 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 4eba18421f3..ddeebba9574 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 @@ -37,7 +37,6 @@ import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse; import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; -import tech.pegasys.teku.api.response.v1.validator.PostValidatorLivenessResponse; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; @@ -366,11 +365,7 @@ public SafeFuture registerValidators( @Override public SafeFuture>> getValidatorsLiveness( final List validatorIndices, final UInt64 epoch) { - return sendRequest( - () -> - apiClient - .sendValidatorsLiveness(epoch, validatorIndices) - .map(this::responseToValidatorsLivenessResult)); + return sendRequest(() -> typeDefClient.sendValidatorsLiveness(epoch, validatorIndices)); } @Override @@ -385,16 +380,6 @@ public SafeFuture>> getSyncCommitteeS return sendRequest(() -> typeDefClient.getSyncCommitteeSelectionProof(requests)); } - private List responseToValidatorsLivenessResult( - final PostValidatorLivenessResponse response) { - return response.data.stream() - .map( - validatorLivenessAtEpoch -> - new ValidatorLivenessAtEpoch( - validatorLivenessAtEpoch.index, validatorLivenessAtEpoch.isLive)) - .toList(); - } - private SafeFuture sendRequest(final ExceptionThrowingRunnable requestExecutor) { return sendRequest( () -> { 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 2f0b4392852..20c3de645d0 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 @@ -13,15 +13,14 @@ package tech.pegasys.teku.validator.remote.apiclient; -import static java.util.Collections.emptyMap; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SIGNED_AGGREGATE_AND_PROOF; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SIGNED_ATTESTATION; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SYNC_COMMITTEE_MESSAGES; -import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_VALIDATOR_LIVENESS; import com.fasterxml.jackson.core.JsonProcessingException; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -35,11 +34,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse; -import tech.pegasys.teku.api.response.v1.validator.PostValidatorLivenessResponse; import tech.pegasys.teku.api.schema.Attestation; import tech.pegasys.teku.api.schema.SignedAggregateAndProof; import tech.pegasys.teku.api.schema.altair.SyncCommitteeMessage; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.provider.JsonProvider; public class OkHttpValidatorRestApiClient implements ValidatorRestApiClient { @@ -48,7 +45,6 @@ public class OkHttpValidatorRestApiClient implements ValidatorRestApiClient { private static final MediaType APPLICATION_JSON = MediaType.parse("application/json; charset=utf-8"); - private static final Map EMPTY_MAP = emptyMap(); private final JsonProvider jsonProvider = new JsonProvider(); private final OkHttpClient httpClient; @@ -64,6 +60,7 @@ public Optional sendSignedAttestations( final List attestations) { return post( SEND_SIGNED_ATTESTATION, + Collections.emptyMap(), attestations, ResponseHandler.createForEmptyOkAndContentInBadResponse( jsonProvider, PostDataFailureResponse.class)); @@ -74,6 +71,7 @@ public Optional sendAggregateAndProofs( final List signedAggregateAndProof) { return post( SEND_SIGNED_AGGREGATE_AND_PROOF, + Collections.emptyMap(), signedAggregateAndProof, ResponseHandler.createForEmptyOkAndContentInBadResponse( jsonProvider, PostDataFailureResponse.class)); @@ -84,31 +82,19 @@ public Optional sendSyncCommitteeMessages( final List syncCommitteeMessages) { return post( SEND_SYNC_COMMITTEE_MESSAGES, + Collections.emptyMap(), syncCommitteeMessages, ResponseHandler.createForEmptyOkAndContentInBadResponse( jsonProvider, PostDataFailureResponse.class)); } - @Override - public Optional sendValidatorsLiveness( - final UInt64 epoch, final List validatorsIndices) { - return post( - SEND_VALIDATOR_LIVENESS, - Map.of("epoch", epoch.toString()), - validatorsIndices, - createHandler(PostValidatorLivenessResponse.class)); - } - - private ResponseHandler createHandler(final Class responseClass) { - return new ResponseHandler<>(jsonProvider, responseClass); - } - private Optional post( final ValidatorApiMethod apiMethod, final Map urlParams, final Object requestBodyObj, final ResponseHandler responseHandler) { - final HttpUrl.Builder httpUrlBuilder = urlBuilder(apiMethod, urlParams); + final HttpUrl.Builder httpUrlBuilder = + baseEndpoint.resolve(apiMethod.getPath(urlParams)).newBuilder(); final String requestBody; try { requestBody = jsonProvider.objectToJSON(requestBodyObj); @@ -135,18 +121,6 @@ private Request.Builder requestBuilder() { return builder; } - private Optional post( - final ValidatorApiMethod apiMethod, - final Object requestBodyObj, - final ResponseHandler responseHandler) { - return post(apiMethod, EMPTY_MAP, requestBodyObj, responseHandler); - } - - private HttpUrl.Builder urlBuilder( - final ValidatorApiMethod apiMethod, final Map urlParams) { - return baseEndpoint.resolve(apiMethod.getPath(urlParams)).newBuilder(); - } - private Optional executeCall( final Request request, final ResponseHandler responseHandler) { try (final Response response = httpClient.newCall(request).execute()) { 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 4662c4e6edf..80eee0c461d 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 @@ -16,11 +16,9 @@ import java.util.List; import java.util.Optional; import tech.pegasys.teku.api.response.v1.beacon.PostDataFailureResponse; -import tech.pegasys.teku.api.response.v1.validator.PostValidatorLivenessResponse; import tech.pegasys.teku.api.schema.Attestation; import tech.pegasys.teku.api.schema.SignedAggregateAndProof; import tech.pegasys.teku.api.schema.altair.SyncCommitteeMessage; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; public interface ValidatorRestApiClient { @@ -31,9 +29,4 @@ Optional sendAggregateAndProofs( Optional sendSyncCommitteeMessages( List syncCommitteeMessages); - - // void prepareBeaconProposer(final List beaconPreparableProposers); - - Optional sendValidatorsLiveness( - UInt64 epoch, List validatorsIndices); } 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 78b12802346..7dee3e02ba0 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 @@ -23,6 +23,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; import tech.pegasys.teku.ethereum.json.types.node.PeerCount; @@ -46,6 +47,7 @@ import tech.pegasys.teku.spec.datastructures.validator.BeaconPreparableProposer; import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.datastructures.validator.SubnetSubscription; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; import tech.pegasys.teku.validator.api.CommitteeSubscriptionRequest; import tech.pegasys.teku.validator.api.SendSignedBlockResult; import tech.pegasys.teku.validator.api.required.SyncingStatus; @@ -66,6 +68,7 @@ import tech.pegasys.teku.validator.remote.typedef.handlers.SendContributionAndProofsRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.SendSignedBlockRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.SendSubscribeToSyncCommitteeSubnetsRequest; +import tech.pegasys.teku.validator.remote.typedef.handlers.SendValidatorLivenessRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.SubscribeToBeaconCommitteeRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.SubscribeToPersistentSubnetsRequest; import tech.pegasys.teku.validator.remote.typedef.handlers.SyncCommitteeSelectionsRequest; @@ -76,23 +79,7 @@ public class OkHttpValidatorTypeDefClient extends OkHttpValidatorMinimalTypeDefC private final Spec spec; private final boolean preferSszBlockEncoding; - private final GetSyncingStatusRequest getSyncingStatusRequest; - private final GetProposerDutiesRequest getProposerDutiesRequest; - private final GetPeerCountRequest getPeerCountRequest; - private final GetStateValidatorsRequest getStateValidatorsRequest; - private final PostAttesterDutiesRequest postAttesterDutiesRequest; - private final PostSyncDutiesRequest postSyncDutiesRequest; - private final SendSignedBlockRequest sendSignedBlockRequest; - private final RegisterValidatorsRequest registerValidatorsRequest; - private final CreateAttestationDataRequest createAttestationDataRequest; - private final BeaconCommitteeSelectionsRequest beaconCommitteeSelectionsRequest; - private final SyncCommitteeSelectionsRequest syncCommitteeSelectionsRequest; - private final SendSubscribeToSyncCommitteeSubnetsRequest subscribeToSyncCommitteeSubnetsRequest; - private final CreateSyncCommitteeContributionRequest createSyncCommitteeContributionRequest; - private final SubscribeToPersistentSubnetsRequest subscribeToPersistentSubnetsRequest; - private final SendContributionAndProofsRequest sendContributionAndProofsRequest; - private final SubscribeToBeaconCommitteeRequest subscribeToBeaconCommitteeRequest; - private final PrepareBeaconProposersRequest prepareBeaconProposersRequest; + private final SchemaDefinitionCache schemaDefinitionCache; public OkHttpValidatorTypeDefClient( final OkHttpClient okHttpClient, @@ -101,50 +88,31 @@ public OkHttpValidatorTypeDefClient( final boolean preferSszBlockEncoding) { super(baseEndpoint, okHttpClient); this.spec = spec; + schemaDefinitionCache = new SchemaDefinitionCache(spec); this.preferSszBlockEncoding = preferSszBlockEncoding; - this.getSyncingStatusRequest = new GetSyncingStatusRequest(okHttpClient, baseEndpoint); - this.getProposerDutiesRequest = new GetProposerDutiesRequest(baseEndpoint, okHttpClient); - this.getPeerCountRequest = new GetPeerCountRequest(baseEndpoint, okHttpClient); - this.getStateValidatorsRequest = new GetStateValidatorsRequest(baseEndpoint, okHttpClient); - this.postSyncDutiesRequest = new PostSyncDutiesRequest(baseEndpoint, okHttpClient); - this.postAttesterDutiesRequest = new PostAttesterDutiesRequest(baseEndpoint, okHttpClient); - this.sendSignedBlockRequest = - new SendSignedBlockRequest(spec, baseEndpoint, okHttpClient, preferSszBlockEncoding); - this.registerValidatorsRequest = - new RegisterValidatorsRequest(baseEndpoint, okHttpClient, false); - this.createAttestationDataRequest = - new CreateAttestationDataRequest(baseEndpoint, okHttpClient); - this.beaconCommitteeSelectionsRequest = - new BeaconCommitteeSelectionsRequest(baseEndpoint, okHttpClient); - this.syncCommitteeSelectionsRequest = - new SyncCommitteeSelectionsRequest(baseEndpoint, okHttpClient); - this.subscribeToSyncCommitteeSubnetsRequest = - new SendSubscribeToSyncCommitteeSubnetsRequest(baseEndpoint, okHttpClient); - this.createSyncCommitteeContributionRequest = - new CreateSyncCommitteeContributionRequest(baseEndpoint, okHttpClient, spec); - this.subscribeToPersistentSubnetsRequest = - new SubscribeToPersistentSubnetsRequest(baseEndpoint, okHttpClient); - this.sendContributionAndProofsRequest = - new SendContributionAndProofsRequest(baseEndpoint, okHttpClient); - this.subscribeToBeaconCommitteeRequest = - new SubscribeToBeaconCommitteeRequest(baseEndpoint, okHttpClient); - this.prepareBeaconProposersRequest = - new PrepareBeaconProposersRequest(baseEndpoint, okHttpClient); } public SyncingStatus getSyncingStatus() { + final GetSyncingStatusRequest getSyncingStatusRequest = + new GetSyncingStatusRequest(getBaseEndpoint(), getOkHttpClient()); return getSyncingStatusRequest.getSyncingStatus(); } public Optional getProposerDuties(final UInt64 epoch) { + final GetProposerDutiesRequest getProposerDutiesRequest = + new GetProposerDutiesRequest(getBaseEndpoint(), getOkHttpClient()); return getProposerDutiesRequest.getProposerDuties(epoch); } public Optional getPeerCount() { + final GetPeerCountRequest getPeerCountRequest = + new GetPeerCountRequest(getBaseEndpoint(), getOkHttpClient()); return getPeerCountRequest.getPeerCount(); } public Optional> getStateValidators(final List validatorIds) { + final GetStateValidatorsRequest getStateValidatorsRequest = + new GetStateValidatorsRequest(getBaseEndpoint(), getOkHttpClient()); return getStateValidatorsRequest .getStateValidators(validatorIds) .map(ObjectAndMetaData::getData); @@ -152,17 +120,24 @@ public Optional> getStateValidators(final List public Optional postSyncDuties( final UInt64 epoch, final Collection validatorIndices) { + final PostSyncDutiesRequest postSyncDutiesRequest = + new PostSyncDutiesRequest(getBaseEndpoint(), getOkHttpClient()); return postSyncDutiesRequest.postSyncDuties(epoch, validatorIndices); } public Optional postAttesterDuties( final UInt64 epoch, final Collection validatorIndices) { + final PostAttesterDutiesRequest postAttesterDutiesRequest = + new PostAttesterDutiesRequest(getBaseEndpoint(), getOkHttpClient()); return postAttesterDutiesRequest.postAttesterDuties(epoch, validatorIndices); } public SendSignedBlockResult sendSignedBlock( final SignedBlockContainer blockContainer, final BroadcastValidationLevel broadcastValidationLevel) { + final SendSignedBlockRequest sendSignedBlockRequest = + new SendSignedBlockRequest( + spec, getBaseEndpoint(), getOkHttpClient(), preferSszBlockEncoding); return sendSignedBlockRequest.sendSignedBlock(blockContainer, broadcastValidationLevel); } @@ -207,27 +182,41 @@ public Optional createUnsignedBlock( public void registerValidators( final SszList validatorRegistrations) { + final RegisterValidatorsRequest registerValidatorsRequest = + new RegisterValidatorsRequest(getBaseEndpoint(), getOkHttpClient(), false); registerValidatorsRequest.registerValidators(validatorRegistrations); } public Optional createAttestationData( final UInt64 slot, final int committeeIndex) { + + final CreateAttestationDataRequest createAttestationDataRequest = + new CreateAttestationDataRequest(getBaseEndpoint(), getOkHttpClient()); return createAttestationDataRequest.createAttestationData(slot, committeeIndex); } public Optional> getBeaconCommitteeSelectionProof( final List validatorsPartialProofs) { + + final BeaconCommitteeSelectionsRequest beaconCommitteeSelectionsRequest = + new BeaconCommitteeSelectionsRequest(getBaseEndpoint(), getOkHttpClient()); return beaconCommitteeSelectionsRequest.getSelectionProof(validatorsPartialProofs); } public Optional> getSyncCommitteeSelectionProof( final List validatorsPartialProofs) { + + final SyncCommitteeSelectionsRequest syncCommitteeSelectionsRequest = + new SyncCommitteeSelectionsRequest(getBaseEndpoint(), getOkHttpClient()); return syncCommitteeSelectionsRequest.getSelectionProof(validatorsPartialProofs); } public void subscribeToSyncCommitteeSubnets( final Collection subscriptions) { LOG.debug("Subscribing to sync committee subnets {}", subscriptions); + + final SendSubscribeToSyncCommitteeSubnetsRequest subscribeToSyncCommitteeSubnetsRequest = + new SendSubscribeToSyncCommitteeSubnetsRequest(getBaseEndpoint(), getOkHttpClient()); subscribeToSyncCommitteeSubnetsRequest.subscribeToSyncCommitteeSubnets(subscriptions); } @@ -238,33 +227,53 @@ public Optional createSyncCommitteeContribution( slot, subcommitteeIndex, beaconBlockRoot); + + final CreateSyncCommitteeContributionRequest createSyncCommitteeContributionRequest = + new CreateSyncCommitteeContributionRequest(getBaseEndpoint(), getOkHttpClient(), spec); return createSyncCommitteeContributionRequest.getSyncCommitteeContribution( slot, subcommitteeIndex, beaconBlockRoot); } public void subscribeToPersistentSubnets(final Set subnetSubscriptions) { + final SubscribeToPersistentSubnetsRequest subscribeToPersistentSubnetsRequest = + new SubscribeToPersistentSubnetsRequest(getBaseEndpoint(), getOkHttpClient()); subscribeToPersistentSubnetsRequest.subscribeToPersistentSubnets( new ArrayList<>(subnetSubscriptions)); } public void sendContributionAndProofs( final Collection signedContributionAndProofs) { + + final SendContributionAndProofsRequest sendContributionAndProofsRequest = + new SendContributionAndProofsRequest(getBaseEndpoint(), getOkHttpClient()); sendContributionAndProofsRequest.submit(signedContributionAndProofs); } public void subscribeToBeaconCommittee(final List subscriptions) { + final SubscribeToBeaconCommitteeRequest subscribeToBeaconCommitteeRequest = + new SubscribeToBeaconCommitteeRequest(getBaseEndpoint(), getOkHttpClient()); subscribeToBeaconCommitteeRequest.submit(subscriptions); } public void prepareBeaconProposer( final List beaconPreparableProposers) { + final PrepareBeaconProposersRequest prepareBeaconProposersRequest = + new PrepareBeaconProposersRequest(getBaseEndpoint(), getOkHttpClient()); prepareBeaconProposersRequest.submit(beaconPreparableProposers); } public Optional createAggregate( final UInt64 slot, final Bytes32 attestationHashTreeRoot) { final CreateAggregateAttestationRequest createAggregateAttestationRequest = - new CreateAggregateAttestationRequest(getBaseEndpoint(), getOkHttpClient(), slot, spec); - return createAggregateAttestationRequest.createAggregate(attestationHashTreeRoot); + new CreateAggregateAttestationRequest( + getBaseEndpoint(), getOkHttpClient(), schemaDefinitionCache); + return createAggregateAttestationRequest.createAggregate(slot, attestationHashTreeRoot); + } + + public Optional> sendValidatorsLiveness( + final UInt64 epoch, final List validatorIndices) { + final SendValidatorLivenessRequest sendValidatorLivenessRequest = + new SendValidatorLivenessRequest(getBaseEndpoint(), getOkHttpClient()); + return sendValidatorLivenessRequest.submit(epoch, validatorIndices); } } diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequest.java index a2383175cef..f915767c548 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAggregateAttestationRequest.java @@ -24,45 +24,40 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; import tech.pegasys.teku.validator.remote.typedef.ResponseHandler; public class CreateAggregateAttestationRequest extends AbstractTypeDefRequest { - private final UInt64 slot; - final AttestationSchema attestationSchema; - private final ResponseHandler responseHandler; - - private final DeserializableTypeDefinition - getAggregateAttestationTypeDef; + private final SchemaDefinitionCache schemaDefinitionCache; public CreateAggregateAttestationRequest( final HttpUrl baseEndpoint, final OkHttpClient okHttpClient, - final UInt64 slot, - final Spec spec) { + final SchemaDefinitionCache schemaDefinitionCache) { super(baseEndpoint, okHttpClient); - this.slot = slot; - this.attestationSchema = - spec.atSlot(slot) - .getSchemaDefinitions() - .getAttestationSchema() - .castTypeToAttestationSchema(); - this.getAggregateAttestationTypeDef = - DeserializableTypeDefinition.object(GetAggregateAttestationResponse.class) - .initializer(GetAggregateAttestationResponse::new) - .withField( - "data", - attestationSchema.getJsonTypeDefinition(), - GetAggregateAttestationResponse::getData, - GetAggregateAttestationResponse::setData) - .build(); - - this.responseHandler = new ResponseHandler<>(getAggregateAttestationTypeDef); + this.schemaDefinitionCache = schemaDefinitionCache; } - public Optional createAggregate(final Bytes32 attestationHashTreeRoot) { + public Optional createAggregate( + final UInt64 slot, final Bytes32 attestationHashTreeRoot) { + + final AttestationSchema attestationSchema = + schemaDefinitionCache.atSlot(slot).getAttestationSchema().castTypeToAttestationSchema(); + final DeserializableTypeDefinition + getAggregateAttestationTypeDef = + DeserializableTypeDefinition.object(GetAggregateAttestationResponse.class) + .initializer(GetAggregateAttestationResponse::new) + .withField( + "data", + attestationSchema.getJsonTypeDefinition(), + GetAggregateAttestationResponse::getData, + GetAggregateAttestationResponse::setData) + .build(); + final ResponseHandler responseHandler = + new ResponseHandler<>(getAggregateAttestationTypeDef); + final Map queryParams = Map.of(SLOT, slot.toString(), ATTESTATION_DATA_ROOT, attestationHashTreeRoot.toString()); return get(GET_AGGREGATE, queryParams, responseHandler) diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequest.java index a566fcbb20c..7d701a177cf 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetSyncingStatusRequest.java @@ -23,7 +23,7 @@ public class GetSyncingStatusRequest extends AbstractTypeDefRequest { - public GetSyncingStatusRequest(final OkHttpClient okHttpClient, final HttpUrl baseEndpoint) { + public GetSyncingStatusRequest(final HttpUrl baseEndpoint, final OkHttpClient okHttpClient) { super(baseEndpoint, okHttpClient); } diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java new file mode 100644 index 00000000000..5621b941261 --- /dev/null +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java @@ -0,0 +1,50 @@ +/* + * 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.SharedApiTypes.withDataWrapper; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.UINT64_TYPE; +import static tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition.listOf; +import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_VALIDATOR_LIVENESS; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.validator.remote.typedef.ResponseHandler; + +public class SendValidatorLivenessRequest extends AbstractTypeDefRequest { + + public SendValidatorLivenessRequest(final HttpUrl baseEndpoint, final OkHttpClient okHttpClient) { + super(baseEndpoint, okHttpClient); + } + + public Optional> submit( + final UInt64 epoch, final List validatorIndices) { + return postJson( + SEND_VALIDATOR_LIVENESS, + Map.of("epoch", epoch.toString()), + Collections.emptyMap(), + validatorIndices, + listOf(UINT64_TYPE), + new ResponseHandler<>( + withDataWrapper( + "SendValidatorLivenessResponse", + listOf(ValidatorLivenessAtEpoch.getJsonTypeDefinition())))); + } +} 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 1b577ff260d..e3fd97815e3 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 @@ -52,8 +52,6 @@ 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.ValidatorStatus; -import tech.pegasys.teku.api.response.v1.validator.PostValidatorLivenessResponse; -import tech.pegasys.teku.api.response.v1.validator.ValidatorLiveness; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; @@ -727,7 +725,7 @@ void shouldRetryAfterDelayWhenRequestRateLimited() { } @Test - public void registerValidators_InvokeApiWithCorrectRequest() { + public void registerValidators_invokeApiWithCorrectRequest() { final SszList validatorRegistrations = dataStructureUtil.randomSignedValidatorRegistrations(5); @@ -739,7 +737,7 @@ public void registerValidators_InvokeApiWithCorrectRequest() { } @Test - public void checkValidatorsDoppelganger_InvokeApiWithCorrectRequest() + public void checkValidatorsDoppelganger_invokeApiWithCorrectRequest() throws ExecutionException, InterruptedException { final List validatorIndices = List.of( @@ -753,7 +751,7 @@ public void checkValidatorsDoppelganger_InvokeApiWithCorrectRequest() assertThat(result).isCompleted(); assertThat(result.get()).isEmpty(); - verify(apiClient).sendValidatorsLiveness(epoch, validatorIndices); + verify(typeDefClient).sendValidatorsLiveness(epoch, validatorIndices); } @Test @@ -767,15 +765,13 @@ public void checkValidatorsDoppelgangerShouldReturnDoppelgangerDetectionResult() List validatorIndices = List.of(firstIndex, secondIndex, thirdIndex); - List validatorLivenesses = + List validatorLivenesses = List.of( - new ValidatorLiveness(firstIndex, false), - new ValidatorLiveness(secondIndex, true), - new ValidatorLiveness(thirdIndex, true)); - PostValidatorLivenessResponse postValidatorLivenessResponse = - new PostValidatorLivenessResponse(validatorLivenesses); - when(apiClient.sendValidatorsLiveness(any(), any())) - .thenReturn(Optional.of(postValidatorLivenessResponse)); + new ValidatorLivenessAtEpoch(firstIndex, false), + new ValidatorLivenessAtEpoch(secondIndex, true), + new ValidatorLivenessAtEpoch(thirdIndex, true)); + when(typeDefClient.sendValidatorsLiveness(any(), any())) + .thenReturn(Optional.of(validatorLivenesses)); final SafeFuture>> result = apiHandler.getValidatorsLiveness(validatorIndices, epoch); @@ -787,7 +783,7 @@ public void checkValidatorsDoppelgangerShouldReturnDoppelgangerDetectionResult() assertThat(validatorIsLive(validatorLivenessAtEpochesResult, firstIndex)).isFalse(); assertThat(validatorIsLive(validatorLivenessAtEpochesResult, secondIndex)).isTrue(); assertThat(validatorIsLive(validatorLivenessAtEpochesResult, thirdIndex)).isTrue(); - verify(apiClient).sendValidatorsLiveness(epoch, validatorIndices); + verify(typeDefClient).sendValidatorsLiveness(epoch, validatorIndices); } private boolean validatorIsLive( @@ -796,7 +792,7 @@ private boolean validatorIsLive( return validatorLivenessAtEpoches.stream() .anyMatch( validatorLivenessAtEpoch -> - validatorLivenessAtEpoch.getIndex().equals(validatorIndex) + validatorLivenessAtEpoch.index().equals(validatorIndex) && validatorLivenessAtEpoch.isLive()); }