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 GetAggregateAttestationV2 API #8464

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
mehdi-aouadi marked this conversation as resolved.
Show resolved Hide resolved
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