Skip to content

Commit

Permalink
add get aggregate attestation request (#8483)
Browse files Browse the repository at this point in the history
* add get aggregate attestation request

* fix integration test

* refactoring

* rename test

* spotless

* refactor aggregattion api

* refactoring

* refactoring

* rename aggregate attestation request

* rename request

* apply spotless

* use schema definition cache

* refactoring

* use spec when submit

* refactor create aggregate request
  • Loading branch information
mehdi-aouadi authored Aug 22, 2024
1 parent 376a874 commit fe54d38
Show file tree
Hide file tree
Showing 9 changed files with 336 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ public void createAggregate_makesExpectedRequest() throws Exception {

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT));

typeDefClient.createAggregate(slot, attestationHashTreeRoot);
typeDefClient.createAggregate(slot, attestationHashTreeRoot, Optional.empty());

RecordedRequest request = mockWebServer.takeRequest();

Expand All @@ -693,7 +693,10 @@ public void createAggregate_whenBadParameters_throwsIllegalArgumentException() {

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST));

assertThatThrownBy(() -> typeDefClient.createAggregate(UInt64.ONE, attestationHashTreeRoot))
assertThatThrownBy(
() ->
typeDefClient.createAggregate(
UInt64.ONE, attestationHashTreeRoot, Optional.empty()))
.isInstanceOf(IllegalArgumentException.class);
}

Expand All @@ -703,7 +706,8 @@ public void createAggregate_whenNotFound_returnsEmpty() {

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND));

assertThat(typeDefClient.createAggregate(UInt64.ONE, attestationHashTreeRoot)).isEmpty();
assertThat(typeDefClient.createAggregate(UInt64.ONE, attestationHashTreeRoot, Optional.empty()))
.isEmpty();
}

@TestTemplate
Expand All @@ -712,7 +716,10 @@ public void createAggregate_whenServerError_throwsRuntimeException() {

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR));

assertThatThrownBy(() -> typeDefClient.createAggregate(UInt64.ONE, attestationHashTreeRoot))
assertThatThrownBy(
() ->
typeDefClient.createAggregate(
UInt64.ONE, attestationHashTreeRoot, Optional.empty()))
.isInstanceOf(RuntimeException.class)
.hasMessageContaining("Server error from Beacon Node API");
}
Expand All @@ -731,11 +738,11 @@ public void createAggregate_WhenSuccess_ReturnsAttestation() throws JsonProcessi
mockWebServer.enqueue(
new MockResponse().setResponseCode(SC_OK).setBody("{\"data\": " + body + "}"));

final Optional<Attestation> attestation =
typeDefClient.createAggregate(UInt64.ONE, attestationHashTreeRoot);
final Optional<ObjectAndMetaData<Attestation>> attestation =
typeDefClient.createAggregate(UInt64.ONE, attestationHashTreeRoot, Optional.empty());

assertThat(attestation).isPresent();
assertThat(attestation.get()).isEqualTo(expectedAttestation);
assertThat(attestation.get().getData()).isEqualTo(expectedAttestation);
}

private AttesterDuty randomAttesterDuty() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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 java.util.Collections.emptyMap;
import static org.assertj.core.api.Assertions.assertThat;
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.http.RestApiConstants.ATTESTATION_DATA_ROOT;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.COMMITTEE_INDEX;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT;
import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA;

import java.util.Optional;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.RecordedRequest;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.TestSpecContext;
import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData;
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;

@TestSpecContext(
milestone = {ELECTRA},
network = Eth2Network.MINIMAL)
public class CreateAggregateAttestationRequestElectraTest extends AbstractTypeDefRequestTestBase {

private CreateAggregateAttestationRequest createAggregateAttestationRequest;
private final UInt64 slot = UInt64.ONE;

@BeforeEach
void setupRequest() {}

@TestTemplate
public void getAggregateAttestation_makesExpectedRequest() throws Exception {
final UInt64 committeeIndex = dataStructureUtil.randomUInt64();
final Bytes32 attestationHashTreeRoot = dataStructureUtil.randomBytes32();

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT));

createAggregateAttestationRequest =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
slot,
attestationHashTreeRoot,
Optional.of(committeeIndex),
spec);

createAggregateAttestationRequest.submit();

final RecordedRequest request = mockWebServer.takeRequest();

assertThat(request.getMethod()).isEqualTo("GET");
assertThat(request.getPath()).contains(ValidatorApiMethod.GET_AGGREGATE_V2.getPath(emptyMap()));
assertThat(request.getRequestUrl().queryParameter(SLOT)).isEqualTo(slot.toString());
assertThat(request.getRequestUrl().queryParameter(ATTESTATION_DATA_ROOT))
.isEqualTo(attestationHashTreeRoot.toHexString());
assertThat(request.getRequestUrl().queryParameter(COMMITTEE_INDEX))
.isEqualTo(committeeIndex.toString());
}

@TestTemplate
public void shouldGetAggregateAttestation() {
final Attestation attestation = dataStructureUtil.randomAttestation();
final CreateAggregateAttestationRequest.GetAggregateAttestationResponseV2
getAggregateAttestationResponse =
new CreateAggregateAttestationRequest.GetAggregateAttestationResponseV2(attestation);

final String mockResponse =
readResource(
"responses/create_aggregate_attestation_responses/createAggregateAttestationResponseElectra.json");

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(mockResponse));

final UInt64 committeeIndex =
specMilestone.isGreaterThanOrEqualTo(ELECTRA)
? UInt64.valueOf(
attestation.getCommitteeBitsRequired().streamAllSetBits().findFirst().orElseThrow())
: attestation.getData().getIndex();

createAggregateAttestationRequest =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
slot,
attestation.hashTreeRoot(),
Optional.of(committeeIndex),
spec);

final Optional<ObjectAndMetaData<Attestation>> maybeAttestationAndMetaData =
createAggregateAttestationRequest.submit();
assertThat(maybeAttestationAndMetaData).isPresent();
assertThat(maybeAttestationAndMetaData.get().getData())
.isEqualTo(getAggregateAttestationResponse.getData());
assertThat(maybeAttestationAndMetaData.get().getMilestone()).isEqualTo(specMilestone);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.RecordedRequest;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.SpecMilestone;
import tech.pegasys.teku.spec.TestSpecContext;
import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData;
import tech.pegasys.teku.spec.datastructures.operations.Attestation;
import tech.pegasys.teku.spec.networks.Eth2Network;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;
Expand All @@ -46,20 +46,23 @@ public class CreateAggregateAttestationRequestTest extends AbstractTypeDefReques
private CreateAggregateAttestationRequest request;
private final UInt64 slot = UInt64.ONE;

@BeforeEach
void setupRequest() {
request =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"), okHttpClient, new SchemaDefinitionCache(spec));
}

@TestTemplate
public void getAggregateAttestation_makesExpectedRequest() throws Exception {
final Bytes32 attestationHashTreeRoot = dataStructureUtil.randomBytes32();

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT));

request.submit(slot, attestationHashTreeRoot);
request =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
slot,
attestationHashTreeRoot,
Optional.empty(),
spec);

request.submit();

final RecordedRequest request = mockWebServer.takeRequest();
assertThat(request.getMethod()).isEqualTo("GET");
Expand All @@ -82,32 +85,69 @@ public void shouldGetAggregateAttestation() {

mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(mockResponse));

final Optional<Attestation> maybeAttestation = request.submit(slot, attestation.hashTreeRoot());
request =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
slot,
attestation.hashTreeRoot(),
Optional.empty(),
spec);

final Optional<ObjectAndMetaData<Attestation>> maybeAttestation = request.submit();

assertThat(maybeAttestation).isPresent();
assertThat(maybeAttestation.get()).isEqualTo(getAggregateAttestationResponse.getData());
assertThat(maybeAttestation.get().getData())
.isEqualTo(getAggregateAttestationResponse.getData());
}

@TestTemplate
void handle400() {
mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST));
assertThatThrownBy(
() -> request.submit(dataStructureUtil.randomSlot(), dataStructureUtil.randomBytes32()))
.isInstanceOf(IllegalArgumentException.class);
request =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
dataStructureUtil.randomSlot(),
dataStructureUtil.randomBytes32(),
Optional.empty(),
spec);

assertThatThrownBy(() -> request.submit()).isInstanceOf(IllegalArgumentException.class);
}

@TestTemplate
void handle404() {
mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND));
assertThat(request.submit(dataStructureUtil.randomSlot(), dataStructureUtil.randomBytes32()))
.isEmpty();
request =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
dataStructureUtil.randomSlot(),
dataStructureUtil.randomBytes32(),
Optional.empty(),
spec);

assertThat(request.submit()).isEmpty();
}

@TestTemplate
void handle500() {
mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR));
assertThatThrownBy(
() -> request.submit(dataStructureUtil.randomSlot(), dataStructureUtil.randomBytes32()))
request =
new CreateAggregateAttestationRequest(
mockWebServer.url("/"),
okHttpClient,
new SchemaDefinitionCache(spec),
dataStructureUtil.randomSlot(),
dataStructureUtil.randomBytes32(),
Optional.empty(),
spec);

assertThatThrownBy(() -> request.submit())
.isInstanceOf(RemoteServiceNotAvailableException.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"version": "electra",
"data": {
"aggregation_bits": "0x4a9278690f62e1a353f1abf2b9701e13e8cdf4d9ac6b032ba43b05b25f713540ce24f3192819e752fb091217ff34b68e934a06d316b6060696f8f24749574c4b3ac2e4ccb6914c434b09a81fff523e12acbe299fcdad715593298d72ca70e1ffc743ad7ce89587fbb4b4c57db7856b7082db70ebddcbebe264f886236df2dd51539e10d4dcd5950e6cea7c993d3e999a5589a4c71669ffb1390987e43a8c4790a70275364f96cbee34b0b5a9d1b3da4322ad12e07c81c6e6430d2528f19bb1727c3f63f414885bd97505283b0bb6773712096d5feb67c43d67f2fbf17bf796ed5080aece6b968d532d985ad2553daf31ad4022aa49d7a92ada719c9f93ab4b6f0d09f127c8d47b9ab80e95a2e72257013e933113029994778c23dfa313b689e08c58979148ac541159b8eb601eee74e985a3b5b9c2dfe0ce3145794d84647136865fabf5814e8a013e236e9b740a6c18229838f3022e1aa2afe5fe48caff6e1470e4458ebcbf152462dc300b07a3d0b102a29196b0a8d444871868408fe80e1dcecd216fe8022ea70326081e516c48bd1b8a18003322738642b189013c3ed8ad64185cf1a44cb1f6265cc40450b5caea7c29b135b5145b4f3c5f14bc76f27442d5a180909ec2e144be68711737211e8d70bda6502a88a4a6558d9c857d6028b1fdfdf2d7df9d2a415b8754d194d17b29d09444d786a0478e62141c31410eda02abcd05769473e5fa75496d49aad564d139af8efee156d8089a253f4cd49814ed34fa9346701d66738938cbc5d54ba2adeb11dbe76f828dec46b82a6ff51dcf17e49771a6d88ad61996a5552809f78746562eba9d7aa9d4525d969c662628b857133d024ead8205bd3f367f3523c6ee9ff9b1784f47de41a1c196a73b178fce869b445c9a1b872a83ba946f2ca41232cdea11c53b7652dcfe615e9b3f9f0153f706eeee404e88e8736b3712e8ab9da9c9b75e419a615c3c1d1357886f77c8eaebbf4501dc1fef854fc5cc7f2f071c0a7411eb78bc14b25307cc7e4bde334ee3df0c53d6159751e82248f280434e466712dfe33981e0171e67352cdd86838eff3acd6e05592e2d1e441ddae9450a144da5c7926ee673458a59bc98c9e4d68f8b04134cc24ffdc2034c4ac3b46d6dd98ecca28dcaa7857f0c3f73a0809ae3c5dd2205555fd7cc6444024869d4f6d7dfe043917660119433c76239b17cdc8fad6c92f3756d206d67800e4e2566a73b27bb7a51dac62bc8411cdab0c5920a821e8ae6bb7779afb69f53452ad0b33c60c41a2be2c3aa94d46e97ffb5b2ebd9adc99eab85d5a3a73d4935f7ea6867d8277040c49f3ac822a4c7c7d67aba4f45765a46fccb7f79c332ac708b58911dc000c49d54fe6137be87df7f364a94fa5642338b6eeaf4152f7410ba97b0169aad82a9c4dd2d353cc3a8f57aaa90b5335f325b3e6e01",
"data": {
"slot": "4665021361504678828",
"index": "4669978815449698508",
"beacon_block_root": "0x103ac9406cdc59b89027eb1c9e97f607dd5fdccfa8fb2da4eaeea9d25032add9",
"source": {
"epoch": "542502839",
"root": "0x8200a6402ca295554fb9562193cc71d60272d63beeaf2201fdf53e846e77f919"
},
"target": {
"epoch": "542887588",
"root": "0x5cbeb140ec0ad7cb653388caecba483cf66bd817821ed18ca1f3b7f3b9b58a04"
}
},
"signature": "0xae401f767ab1917f925fe299ad51a57d52f7cc80deb1cc20fa2b3aa983e4e4d23056d79f01f3c97e29c8905da17e70e30c2a3f6bdd83dbc4ddf530e02e8f4d7ba22260e12e5f5fe7875b48e79660615b275597e87b2d33e076664b3da1737852",
"committee_bits": "0x08"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration;
import tech.pegasys.teku.spec.datastructures.genesis.GenesisData;
import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData;
import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData;
import tech.pegasys.teku.spec.datastructures.operations.Attestation;
import tech.pegasys.teku.spec.datastructures.operations.AttestationData;
import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof;
Expand Down Expand Up @@ -260,7 +261,11 @@ public SafeFuture<Optional<Attestation>> createAggregate(
final UInt64 slot,
final Bytes32 attestationHashTreeRoot,
final Optional<UInt64> committeeIndex) {
return sendRequest(() -> typeDefClient.createAggregate(slot, attestationHashTreeRoot));
return sendRequest(
() ->
typeDefClient
.createAggregate(slot, attestationHashTreeRoot, committeeIndex)
.map(ObjectAndMetaData::getData));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public enum ValidatorApiMethod {
SEND_SIGNED_VOLUNTARY_EXIT("eth/v1/beacon/pool/voluntary_exits"),
SEND_SYNC_COMMITTEE_MESSAGES("eth/v1/beacon/pool/sync_committees"),
GET_AGGREGATE("eth/v1/validator/aggregate_attestation"),
GET_AGGREGATE_V2("eth/v2/validator/aggregate_attestation"),
SEND_SIGNED_AGGREGATE_AND_PROOF("/eth/v1/validator/aggregate_and_proofs"),
SEND_CONTRIBUTION_AND_PROOF("eth/v1/validator/contribution_and_proofs"),
SUBSCRIBE_TO_BEACON_COMMITTEE_SUBNET("eth/v1/validator/beacon_committee_subscriptions"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,20 @@ public void prepareBeaconProposer(
prepareBeaconProposersRequest.submit(beaconPreparableProposers);
}

public Optional<Attestation> createAggregate(
final UInt64 slot, final Bytes32 attestationHashTreeRoot) {
public Optional<ObjectAndMetaData<Attestation>> createAggregate(
final UInt64 slot,
final Bytes32 attestationHashTreeRoot,
final Optional<UInt64> committeeIndex) {
final CreateAggregateAttestationRequest createAggregateAttestationRequest =
new CreateAggregateAttestationRequest(
getBaseEndpoint(), getOkHttpClient(), schemaDefinitionCache);
return createAggregateAttestationRequest.submit(slot, attestationHashTreeRoot);
getBaseEndpoint(),
getOkHttpClient(),
schemaDefinitionCache,
slot,
attestationHashTreeRoot,
committeeIndex,
spec);
return createAggregateAttestationRequest.submit();
}

public Optional<List<ValidatorLivenessAtEpoch>> sendValidatorsLiveness(
Expand Down
Loading

0 comments on commit fe54d38

Please sign in to comment.