Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add get aggregate attestation request #8483

Merged
merged 16 commits into from
Aug 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,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 @@ -774,7 +774,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 @@ -784,7 +787,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 @@ -793,7 +797,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 @@ -812,11 +819,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,113 @@
/*
* 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 okio.Buffer;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.AfterEach;
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 Buffer responseBodyBuffer;
private final UInt64 slot = UInt64.ONE;

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

@AfterEach
void reset() {
responseBodyBuffer.clear();
responseBodyBuffer.close();
}

@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.submit(
slot, attestationHashTreeRoot, Optional.of(committeeIndex), spec);

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();

final Optional<ObjectAndMetaData<Attestation>> maybeAttestationAndMetaData =
createAggregateAttestationRequest.submit(
slot, attestation.hashTreeRoot(), Optional.of(committeeIndex), spec);
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 @@ -34,6 +34,7 @@
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 @@ -59,7 +60,7 @@ public void getAggregateAttestation_makesExpectedRequest() throws Exception {

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

request.submit(slot, attestationHashTreeRoot);
request.submit(slot, attestationHashTreeRoot, Optional.empty(), spec);

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

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

final Optional<Attestation> maybeAttestation = request.submit(slot, attestation.hashTreeRoot());
final Optional<ObjectAndMetaData<Attestation>> maybeAttestation =
request.submit(slot, attestation.hashTreeRoot(), Optional.empty(), spec);

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()))
() ->
request.submit(
dataStructureUtil.randomSlot(),
dataStructureUtil.randomBytes32(),
Optional.empty(),
spec))
.isInstanceOf(IllegalArgumentException.class);
}

@TestTemplate
void handle404() {
mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND));
assertThat(request.submit(dataStructureUtil.randomSlot(), dataStructureUtil.randomBytes32()))
assertThat(
request.submit(
dataStructureUtil.randomSlot(),
dataStructureUtil.randomBytes32(),
Optional.empty(),
spec))
.isEmpty();
}

@TestTemplate
void handle500() {
mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR));
assertThatThrownBy(
() -> request.submit(dataStructureUtil.randomSlot(), dataStructureUtil.randomBytes32()))
() ->
request.submit(
dataStructureUtil.randomSlot(),
dataStructureUtil.randomBytes32(),
Optional.empty(),
spec))
.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 @@ -268,7 +269,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 @@ -35,6 +35,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 @@ -263,12 +263,15 @@ 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);
return createAggregateAttestationRequest.submit(
slot, attestationHashTreeRoot, committeeIndex, spec);
}

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