Skip to content

Commit

Permalink
Add GetAggregateAttestationV2 API (#8464)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdi-aouadi authored Jul 24, 2024
1 parent bdff765 commit 8c36891
Show file tree
Hide file tree
Showing 11 changed files with 446 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Added a state pruner that can limit the number of finalized states stored when running an archive node.
- Updated bootnodes for Sepolia network.
- Implemented [GetBlockAttestationV2](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getBlockAttestationsV2) (adding support for Electra attestations)
- Implemented [GetAggregateAttestationV2](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Validator/getAggregatedAttestationV2) (adding support for Electra attestations)
- Updated a number of parameters to reduce issues when using `p2p-subscribe-all-subnets-enabled`. If you have adjusted queue sizes manually when using all-subnets, please refer to details below. Manual settings will still override these defaults.
- When `p2p-subscribe-all-subnets-enabled`, `p2p-peer-lower-bound` now defaults to 60 (previously 64), and `p2p-peer-upper-bound` now defaults to 80 (previously 100).
- When `p2p-subscribe-all-subnets-enabled`, (`Xnetwork-async-p2p-max-queue`, `Xnetwork-async-beaconchain-max-queue`, `Xp2p-batch-verify-signatures-queue-capacity`) now default to 40_000 (previously 10_000)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"operationId" : "getAggregatedAttestation",
"summary" : "Get aggregated attestation",
"description" : "Aggregates all attestations matching given attestation data root and slot.",
"deprecated" : true,
"parameters" : [ {
"name" : "attestation_data_root",
"required" : true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"get" : {
"tags" : [ "Validator", "Validator Required Api" ],
"operationId" : "getAggregatedAttestationV2",
"summary" : "Get aggregated attestation",
"description" : "Aggregates all attestations matching given attestation data root, slot and committee index.\nA 503 error must be returned if the block identified by the response\n`beacon_block_root` is optimistic (i.e. the aggregated attestation attests\nto a block that has not been fully verified by an execution engine).\nA 404 error must be returned if no attestation is available for the requested\n`attestation_data_root`.",
"parameters" : [ {
"name" : "attestation_data_root",
"required" : true,
"in" : "query",
"schema" : {
"type" : "string",
"description" : "`String` HashTreeRoot of AttestationData that validator wants aggregated.",
"example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"format" : "byte"
}
}, {
"name" : "slot",
"required" : true,
"in" : "query",
"schema" : {
"type" : "string",
"description" : "`uint64` value representing slot",
"example" : "1",
"format" : "uint64"
}
}, {
"name" : "committee_index",
"required" : true,
"in" : "query",
"schema" : {
"type" : "string",
"description" : "`uint64` Committee index to query.",
"example" : "1",
"format" : "uint64"
}
} ],
"responses" : {
"200" : {
"description" : "Request successful",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/GetAggregatedAttestationResponseV2"
}
}
}
},
"404" : {
"description" : "Not found",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"400" : {
"description" : "The request could not be processed, check the response for more information.",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
},
"500" : {
"description" : "Internal server error",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/HttpErrorResponse"
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title" : "GetAggregatedAttestationResponseV2",
"type" : "object",
"required" : [ "version", "data" ],
"properties" : {
"version" : {
"type" : "string",
"enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ]
},
"data" : {
"type" : "object",
"oneOf" : [ {
"$ref" : "#/components/schemas/AttestationElectra"
}, {
"$ref" : "#/components/schemas/AttestationPhase0"
} ]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@
import tech.pegasys.teku.spec.SpecMilestone;
import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader;
import tech.pegasys.teku.spec.datastructures.metadata.BlockAndMetaData;
import tech.pegasys.teku.spec.datastructures.operations.Attestation;
import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;

public class BeaconRestApiTypes {
private static final StringValueTypeDefinition<StatusParameter> STATUS_VALUE =
Expand Down Expand Up @@ -247,6 +249,26 @@ public class BeaconRestApiTypes {
public static final ParameterMetadata<SpecMilestone> ETH_CONSENSUS_VERSION_TYPE =
new ParameterMetadata<>(HEADER_CONSENSUS_VERSION, MILESTONE_TYPE);

@SuppressWarnings("unchecked")
public static DeserializableTypeDefinition<Attestation> electraAttestationTypeDef(
final SchemaDefinitionCache schemaDefinitionCache) {
return (DeserializableTypeDefinition<Attestation>)
schemaDefinitionCache
.getSchemaDefinition(SpecMilestone.ELECTRA)
.getAttestationSchema()
.getJsonTypeDefinition();
}

@SuppressWarnings("unchecked")
public static DeserializableTypeDefinition<Attestation> phase0AttestationTypeDef(
final SchemaDefinitionCache schemaDefinitionCache) {
return (DeserializableTypeDefinition<Attestation>)
schemaDefinitionCache
.getSchemaDefinition(SpecMilestone.PHASE0)
.getAttestationSchema()
.getJsonTypeDefinition();
}

@SuppressWarnings("JavaCase")
public enum BroadcastValidationParameter {
gossip,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.PostBlockV2;
import tech.pegasys.teku.beaconrestapi.handlers.v2.debug.GetChainHeadsV2;
import tech.pegasys.teku.beaconrestapi.handlers.v2.debug.GetState;
import tech.pegasys.teku.beaconrestapi.handlers.v2.validator.GetAggregateAttestationV2;
import tech.pegasys.teku.beaconrestapi.handlers.v2.validator.GetNewBlock;
import tech.pegasys.teku.beaconrestapi.handlers.v3.validator.GetNewBlockV3;
import tech.pegasys.teku.infrastructure.async.AsyncRunner;
Expand Down Expand Up @@ -273,6 +274,7 @@ private static RestApi create(
.endpoint(new GetNewBlockV3(dataProvider, schemaCache))
.endpoint(new GetAttestationData(dataProvider))
.endpoint(new GetAggregateAttestation(dataProvider, spec))
.endpoint(new GetAggregateAttestationV2(dataProvider, schemaCache))
.endpoint(new PostAggregateAndProofs(dataProvider, spec.getGenesisSchemaDefinitions()))
.endpoint(new PostSubscribeToBeaconCommitteeSubnet(dataProvider))
.endpoint(new PostSyncDuties(dataProvider))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public GetAggregateAttestation(final ValidatorDataProvider provider, final Spec
.description(
"Aggregates all attestations matching given attestation data root and slot.")
.tags(TAG_VALIDATOR, TAG_VALIDATOR_REQUIRED)
.deprecated(true)
.queryParamRequired(ATTESTATION_DATA_ROOT_PARAMETER)
.queryParamRequired(SLOT_PARAM)
.response(HttpStatusCodes.SC_OK, "Request successful", getResponseType(spec))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@
import tech.pegasys.teku.api.ChainDataProvider;
import tech.pegasys.teku.api.DataProvider;
import tech.pegasys.teku.api.schema.Version;
import tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition;
import tech.pegasys.teku.infrastructure.json.types.SerializableOneOfTypeDefinition;
import tech.pegasys.teku.infrastructure.json.types.SerializableOneOfTypeDefinitionBuilder;
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
import tech.pegasys.teku.infrastructure.restapi.endpoints.AsyncApiResponse;
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
import tech.pegasys.teku.spec.SpecMilestone;
import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData;
import tech.pegasys.teku.spec.datastructures.operations.Attestation;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;
Expand Down Expand Up @@ -92,24 +91,15 @@ public void handleRequest(final RestApiRequest request) throws JsonProcessingExc
@SuppressWarnings("unchecked")
private static SerializableTypeDefinition<ObjectAndMetaData<List<Attestation>>> getResponseType(
final SchemaDefinitionCache schemaDefinitionCache) {
final DeserializableTypeDefinition<Attestation> electraAttestationTypeDef =
(DeserializableTypeDefinition<Attestation>)
schemaDefinitionCache
.getSchemaDefinition(SpecMilestone.ELECTRA)
.getAttestationSchema()
.getJsonTypeDefinition();

final DeserializableTypeDefinition<Attestation> phase0AttestationTypeDef =
(DeserializableTypeDefinition<Attestation>)
schemaDefinitionCache
.getSchemaDefinition(SpecMilestone.PHASE0)
.getAttestationSchema()
.getJsonTypeDefinition();

final SerializableOneOfTypeDefinition<List<Attestation>> oneOfTypeDefinition =
new SerializableOneOfTypeDefinitionBuilder<List<Attestation>>()
.withType(electraAttestationsPredicate(), listOf(electraAttestationTypeDef))
.withType(phase0AttestationsPredicate(), listOf(phase0AttestationTypeDef))
.withType(
electraAttestationsPredicate(),
listOf(BeaconRestApiTypes.electraAttestationTypeDef(schemaDefinitionCache)))
.withType(
phase0AttestationsPredicate(),
listOf(BeaconRestApiTypes.phase0AttestationTypeDef(schemaDefinitionCache)))
.build();

return SerializableTypeDefinition.<ObjectAndMetaData<List<Attestation>>>object()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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.beaconrestapi.handlers.v2.validator;

import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.ATTESTATION_DATA_ROOT_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.COMMITTEE_INDEX_PARAMETER;
import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.SLOT_PARAMETER;
import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.MILESTONE_TYPE;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR;
import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR_REQUIRED;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Optional;
import java.util.function.Predicate;
import org.apache.tuweni.bytes.Bytes32;
import tech.pegasys.teku.api.DataProvider;
import tech.pegasys.teku.api.ValidatorDataProvider;
import tech.pegasys.teku.api.schema.Version;
import tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes;
import tech.pegasys.teku.infrastructure.async.SafeFuture;
import tech.pegasys.teku.infrastructure.http.HttpStatusCodes;
import tech.pegasys.teku.infrastructure.json.types.SerializableOneOfTypeDefinition;
import tech.pegasys.teku.infrastructure.json.types.SerializableOneOfTypeDefinitionBuilder;
import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition;
import tech.pegasys.teku.infrastructure.restapi.endpoints.AsyncApiResponse;
import tech.pegasys.teku.infrastructure.restapi.endpoints.EndpointMetadata;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiEndpoint;
import tech.pegasys.teku.infrastructure.restapi.endpoints.RestApiRequest;
import tech.pegasys.teku.infrastructure.unsigned.UInt64;
import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData;
import tech.pegasys.teku.spec.datastructures.operations.Attestation;
import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache;

public class GetAggregateAttestationV2 extends RestApiEndpoint {

public static final String ROUTE = "/eth/v2/validator/aggregate_attestation";
private final ValidatorDataProvider provider;

public GetAggregateAttestationV2(
final DataProvider dataProvider, final SchemaDefinitionCache schemaDefinitionCache) {
this(dataProvider.getValidatorDataProvider(), schemaDefinitionCache);
}

public GetAggregateAttestationV2(
final ValidatorDataProvider provider, final SchemaDefinitionCache schemaDefinitionCache) {
super(
EndpointMetadata.get(ROUTE)
.operationId("getAggregatedAttestationV2")
.summary("Get aggregated attestation")
.description(
"Aggregates all attestations matching given attestation data root, slot and committee index.\n"
+ "A 503 error must be returned if the block identified by the response\n"
+ "`beacon_block_root` is optimistic (i.e. the aggregated attestation attests\n"
+ "to a block that has not been fully verified by an execution engine).\n"
+ "A 404 error must be returned if no attestation is available for the requested\n"
+ "`attestation_data_root`.")
.tags(TAG_VALIDATOR, TAG_VALIDATOR_REQUIRED)
.queryParamRequired(ATTESTATION_DATA_ROOT_PARAMETER)
.queryParamRequired(SLOT_PARAMETER)
.queryParamRequired(COMMITTEE_INDEX_PARAMETER)
.response(
HttpStatusCodes.SC_OK, "Request successful", getResponseType(schemaDefinitionCache))
.withNotFoundResponse()
.build());
this.provider = provider;
}

@Override
public void handleRequest(final RestApiRequest request) throws JsonProcessingException {
final Bytes32 beaconBlockRoot = request.getQueryParameter(ATTESTATION_DATA_ROOT_PARAMETER);
final UInt64 slot = request.getQueryParameter(SLOT_PARAMETER);
final UInt64 committeeIndex = request.getQueryParameter(COMMITTEE_INDEX_PARAMETER);

final SafeFuture<Optional<ObjectAndMetaData<Attestation>>> future =
provider.createAggregateAndMetaData(slot, beaconBlockRoot, committeeIndex);

request.respondAsync(
future.thenApply(
maybeAttestation ->
maybeAttestation
.map(
attestationAndMetaData -> {
request.header(
HEADER_CONSENSUS_VERSION,
Version.fromMilestone(attestationAndMetaData.getMilestone()).name());
return AsyncApiResponse.respondOk(attestationAndMetaData);
})
.orElseGet(AsyncApiResponse::respondNotFound)));
}

@SuppressWarnings("unchecked")
private static SerializableTypeDefinition<ObjectAndMetaData<Attestation>> getResponseType(
final SchemaDefinitionCache schemaDefinitionCache) {

final SerializableOneOfTypeDefinition<Attestation> oneOfTypeDefinition =
new SerializableOneOfTypeDefinitionBuilder<Attestation>()
.withType(
electraAttestationPredicate(),
BeaconRestApiTypes.electraAttestationTypeDef(schemaDefinitionCache))
.withType(
phase0AttestationPredicate(),
BeaconRestApiTypes.phase0AttestationTypeDef(schemaDefinitionCache))
.build();

return SerializableTypeDefinition.<ObjectAndMetaData<Attestation>>object()
.name("GetAggregatedAttestationResponseV2")
.withField("version", MILESTONE_TYPE, ObjectAndMetaData::getMilestone)
.withField("data", oneOfTypeDefinition, ObjectAndMetaData::getData)
.build();
}

private static Predicate<Attestation> phase0AttestationPredicate() {
// Before Electra attestations do not require committee bits
return attestation -> !attestation.requiresCommitteeBits();
}

private static Predicate<Attestation> electraAttestationPredicate() {
// Only once we are in Electra attestations will have committee bits
return Attestation::requiresCommitteeBits;
}
}
Loading

0 comments on commit 8c36891

Please sign in to comment.