diff --git a/CHANGELOG.md b/CHANGELOG.md index d0fee413bc7..17d50d387af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 [GetAttestationsV2](https://ethereum.github.io/beacon-APIs/?urls.primaryName=dev#/Beacon/getPoolAttestationsV2) (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). diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_pool_attestations.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_pool_attestations.json index 8d7dccf937e..cb9c873b2f8 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_pool_attestations.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_pool_attestations.json @@ -4,6 +4,7 @@ "operationId" : "getPoolAttestations", "summary" : "Get Attestations from operations pool", "description" : "Retrieves attestations known by the node but not necessarily incorporated into any block.", + "deprecated" : true, "parameters" : [ { "name" : "slot", "in" : "query", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_pool_attestations.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_pool_attestations.json new file mode 100644 index 00000000000..079710fb0c4 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_pool_attestations.json @@ -0,0 +1,59 @@ +{ + "get" : { + "tags" : [ "Beacon" ], + "operationId" : "getPoolAttestationsV2", + "summary" : "Get Attestations from operations pool", + "description" : "Retrieves attestations known by the node but not necessarily incorporated into any block.", + "parameters" : [ { + "name" : "slot", + "in" : "query", + "schema" : { + "type" : "string", + "description" : "`UInt64` Slot to query in the canonical chain.", + "example" : "1", + "format" : "uint64" + } + }, { + "name" : "committee_index", + "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/GetPoolAttestationsV2Response" + } + } + } + }, + "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" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPoolAttestationsV2Response.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPoolAttestationsV2Response.json new file mode 100644 index 00000000000..80756cfe9c9 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPoolAttestationsV2Response.json @@ -0,0 +1,23 @@ +{ + "title" : "GetPoolAttestationsV2Response", + "type" : "object", + "required" : [ "version", "data" ], + "properties" : { + "version" : { + "type" : "string", + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + }, + "data" : { + "type" : "array", + "items" : { + "title" : "Attestation", + "type" : "object", + "oneOf" : [ { + "$ref" : "#/components/schemas/AttestationPhase0" + }, { + "$ref" : "#/components/schemas/AttestationElectra" + } ] + } + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java index bbf8abaf299..ab073602f7e 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/JsonTypeDefinitionBeaconRestApi.java @@ -101,6 +101,7 @@ import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncCommitteeSubscriptions; import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostSyncDuties; import tech.pegasys.teku.beaconrestapi.handlers.v1.validator.PostValidatorLiveness; +import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.GetAttestationsV2; import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.GetAttesterSlashingsV2; import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.GetBlock; import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.GetBlockAttestationsV2; @@ -236,6 +237,7 @@ private static RestApi create( .endpoint(new GetBlockAttestations(dataProvider, spec)) .endpoint(new GetBlockAttestationsV2(dataProvider, schemaCache)) .endpoint(new GetAttestations(dataProvider, spec)) + .endpoint(new GetAttestationsV2(dataProvider, schemaCache)) .endpoint(new PostAttestation(dataProvider, schemaCache)) .endpoint(new GetAttesterSlashings(dataProvider, spec)) .endpoint(new GetAttesterSlashingsV2(dataProvider, schemaCache)) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetAttestations.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetAttestations.java index 199897ea156..2d7ee137c41 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetAttestations.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetAttestations.java @@ -52,6 +52,7 @@ public GetAttestations(final NodeDataProvider nodeDataProvider, final Spec spec) .description( "Retrieves attestations known by the node but not necessarily incorporated into any block.") .tags(TAG_BEACON) + .deprecated(true) .queryParam(SLOT_PARAMETER.withDescription(SLOT_QUERY_DESCRIPTION)) .queryParam(COMMITTEE_INDEX_PARAMETER) .response(SC_OK, "Request successful", getResponseType(spec)) @@ -70,7 +71,6 @@ public void handleRequest(final RestApiRequest request) throws JsonProcessingExc request.respondOk(nodeDataProvider.getAttestations(slot, committeeIndex)); } - // TODO EIP-7549 handle Electra attestations private static SerializableTypeDefinition> getResponseType(final Spec spec) { return SerializableTypeDefinition.>object() .name("GetPoolAttestationsResponse") diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/GetAttestationsV2.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/GetAttestationsV2.java new file mode 100644 index 00000000000..a1fb01e5130 --- /dev/null +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/GetAttestationsV2.java @@ -0,0 +1,123 @@ +/* + * 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.beacon; + +import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.COMMITTEE_INDEX_PARAMETER; +import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.SLOT_PARAMETER; +import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.getMultipleSchemaDefinitionFromMilestone; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.MILESTONE_TYPE; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.CACHE_NONE; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT_QUERY_DESCRIPTION; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_BEACON; +import static tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition.listOf; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.javalin.http.Header; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import tech.pegasys.teku.api.DataProvider; +import tech.pegasys.teku.api.NodeDataProvider; +import tech.pegasys.teku.api.schema.Version; +import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil; +import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; +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.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; +import tech.pegasys.teku.spec.schemas.SchemaDefinitions; + +public class GetAttestationsV2 extends RestApiEndpoint { + + public static final String ROUTE = "/eth/v2/beacon/pool/attestations"; + + private final NodeDataProvider nodeDataProvider; + + public GetAttestationsV2( + final DataProvider dataProvider, final SchemaDefinitionCache schemaDefinitionCache) { + + this(dataProvider.getNodeDataProvider(), schemaDefinitionCache); + } + + public GetAttestationsV2( + final NodeDataProvider nodeDataProvider, final SchemaDefinitionCache schemaDefinitionCache) { + super( + EndpointMetadata.get(ROUTE) + .operationId("getPoolAttestationsV2") + .summary("Get Attestations from operations pool") + .description( + "Retrieves attestations known by the node but not necessarily incorporated into any block.") + .tags(TAG_BEACON) + .queryParam(SLOT_PARAMETER.withDescription(SLOT_QUERY_DESCRIPTION)) + .queryParam(COMMITTEE_INDEX_PARAMETER) + .response(SC_OK, "Request successful", getResponseType(schemaDefinitionCache)) + .build()); + this.nodeDataProvider = nodeDataProvider; + } + + @Override + public void handleRequest(final RestApiRequest request) throws JsonProcessingException { + request.header(Header.CACHE_CONTROL, CACHE_NONE); + final Optional slot = + request.getOptionalQueryParameter(SLOT_PARAMETER.withDescription(SLOT_QUERY_DESCRIPTION)); + final Optional committeeIndex = + request.getOptionalQueryParameter(COMMITTEE_INDEX_PARAMETER); + final ObjectAndMetaData> attestationsAndMetaData = + nodeDataProvider.getAttestationsAndMetaData(slot, committeeIndex); + + request.header( + HEADER_CONSENSUS_VERSION, + Version.fromMilestone(attestationsAndMetaData.getMilestone()).name()); + request.respondOk(attestationsAndMetaData); + } + + private static SerializableTypeDefinition>> getResponseType( + final SchemaDefinitionCache schemaDefinitionCache) { + + final List> schemaGetters = + generateAttestationSchemaGetters(schemaDefinitionCache); + + final SerializableTypeDefinition attestationType = + getMultipleSchemaDefinitionFromMilestone( + schemaDefinitionCache, "Attestation", schemaGetters); + + return SerializableTypeDefinition.>>object() + .name("GetPoolAttestationsV2Response") + .withField("version", MILESTONE_TYPE, ObjectAndMetaData::getMilestone) + .withField("data", listOf(attestationType), ObjectAndMetaData::getData) + .build(); + } + + private static List> + generateAttestationSchemaGetters(final SchemaDefinitionCache schemaDefinitionCache) { + final List> schemaGetterList = + new ArrayList<>(); + + schemaGetterList.add( + new MilestoneDependentTypesUtil.ConditionalSchemaGetter<>( + (attestation, milestone) -> + schemaDefinitionCache + .milestoneAtSlot(attestation.getData().getSlot()) + .equals(milestone), + SpecMilestone.PHASE0, + SchemaDefinitions::getAttestationSchema)); + return schemaGetterList; + } +} diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/GetAttestationsV2Test.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/GetAttestationsV2Test.java new file mode 100644 index 00000000000..3425a5667cc --- /dev/null +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/GetAttestationsV2Test.java @@ -0,0 +1,119 @@ +/* + * 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.beacon; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse; +import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.io.Resources; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.schema.Version; +import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerTest; +import tech.pegasys.teku.infrastructure.json.JsonTestUtil; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.TestSpecInvocationContextProvider; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +@TestSpecContext(milestone = {PHASE0, ELECTRA}) +public class GetAttestationsV2Test extends AbstractMigratedBeaconHandlerTest { + + private SpecMilestone specMilestone; + + @BeforeEach + void setup(final TestSpecInvocationContextProvider.SpecContext specContext) { + spec = specContext.getSpec(); + dataStructureUtil = new DataStructureUtil(spec); + specMilestone = specContext.getSpecMilestone(); + setHandler(new GetAttestationsV2(nodeDataProvider, new SchemaDefinitionCache(spec))); + } + + @TestTemplate + public void shouldReturnAttestationsWhenFound() throws JsonProcessingException { + final List attestations = + List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + final ObjectAndMetaData> attestationsAndMetaData = + new ObjectAndMetaData<>( + attestations, spec.getGenesisSpec().getMilestone(), false, false, false); + when(nodeDataProvider.getAttestationsAndMetaData(any(), any())) + .thenReturn(attestationsAndMetaData); + + handler.handleRequest(request); + assertThat(request.getResponseCode()).isEqualTo(SC_OK); + assertThat(request.getResponseBody()).isEqualTo(attestationsAndMetaData); + assertThat(request.getResponseHeaders(HEADER_CONSENSUS_VERSION)) + .isEqualTo(Version.fromMilestone(specMilestone).name()); + } + + @TestTemplate + public void shouldReturnEmptyListWhenNoAttestations() throws JsonProcessingException { + final ObjectAndMetaData> attestationsAndMetaData = + new ObjectAndMetaData<>( + Collections.emptyList(), spec.getGenesisSpec().getMilestone(), false, false, false); + when(nodeDataProvider.getAttestationsAndMetaData(any(), any())) + .thenReturn(attestationsAndMetaData); + + handler.handleRequest(request); + assertThat(request.getResponseCode()).isEqualTo(SC_OK); + assertThat(request.getResponseBody()).isEqualTo(attestationsAndMetaData); + assertThat(request.getResponseHeaders(HEADER_CONSENSUS_VERSION)) + .isEqualTo(Version.fromMilestone(specMilestone).name()); + } + + @TestTemplate + void metadata_shouldHandle400() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_BAD_REQUEST); + } + + @TestTemplate + void metadata_shouldHandle500() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR); + } + + @TestTemplate + void metadata_shouldHandle200() throws Exception { + final List attestations = + List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + final ObjectAndMetaData> responseData = withMetaData(attestations); + final String responseDataAsString = getResponseStringFromMetadata(handler, SC_OK, responseData); + final JsonNode responseDataAsJsonNode = JsonTestUtil.parseAsJsonNode(responseDataAsString); + final String expected = getExpectedResponseAsJson(specMilestone); + final JsonNode expectedAsJsonNode = JsonTestUtil.parseAsJsonNode(expected); + assertThat(responseDataAsJsonNode).isEqualTo(expectedAsJsonNode); + } + + private String getExpectedResponseAsJson(final SpecMilestone specMilestone) throws IOException { + final String fileName = String.format("getAttestations%s.json", specMilestone.name()); + return Resources.toString(Resources.getResource(GetAttestationsV2Test.class, fileName), UTF_8); + } +} diff --git a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/getAttestationsELECTRA.json b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/getAttestationsELECTRA.json new file mode 100644 index 00000000000..df4001d5efd --- /dev/null +++ b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/getAttestationsELECTRA.json @@ -0,0 +1,41 @@ +{ + "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" + }, + { + "aggregation_bits": "0x845545fabb721755d4a172b873380c0edb449ebd072a2de3b1876da1d8982be4180ef73f8ba3ae1e5591d29e266b8c81c5dc355847a2ab7cf16ea32faa82896cf0321cbbe284bea22e0d0f1d89dc2eaf1ee3ebad1a41c1b15320f7c539213a5c3b66b6cbb895b3b64f7f5c734069f90ae04794bbb4b878eecf54206948eed9a986c2cfbc6e63f6d4579b685d3d0b676b64ddfd0823e79f1b707f2ecbfc53c9d917bc69ee349db40ce9c613b9607a638788397d29c83c607606ac5b6365d67a87594f1d21ec28f28a30add7b915540909f15d30d59bb57e9983af5140e516365a41bdb22c28e71d738d4cedebc05d4c03ee3b5b12737ce986c3e6163f7d1c66d53c5b042691f5b62c2dcb47650682746b7d69ea1c24344b23f6d64e66c493ded66be1f3ad68110c921365fde983165637ac4508cf3e0c2e9d0d3ae4d0ea19837b5f6195ff65a37e3dcbcba06ee4be125ca8ba384941e40088d56d47e9f4be8235999e152bafdc5fc1a9876e6224596f101f6afd2879ffb0f413a008143ffc8e357c370a420c2ad2941429587d57858cf5c42d42a92c6b4741e78575a8d72734141913081c7f8813ea6084af86618a4c50b0427ad32842d24332757466c89b8709fbae2cf2185cf20bcbcbfe2523965a044f351c4eddd63ca44814a5e21d1385405c5e009777a1c5edd367364ac7ccfc8c4b17e3c904063b0dbb3277c42c0b455a1e4c3755c93da5d6c0018d6ca9fea294627664b646e59a352e1401ff5cb86331e2cc8c85a1c6b5ac7e3c89d6fcf6e8a94fc252f7f4a08965791f54ee8e054f403c4d335935b0e21e08d921580cd2305343eb32a4ca75a0203401efc6812f8b34d54ac10357f88a36f054b2fe53ca54aa2e52d4451399f31a4fbdf0bf4e35ca8685bbc869fd75f24563a6b74f14ea273c44109eed479daeb97d92d4a4f853d32a222d484d3e1014caccf4a9583c54cb4884de03a579b6fcc131bd13fd0ed7647b7071a19e295c019241adf483375b32ddece6a07ef9d3451da2fbbe3959d45c0158dfeedae3bed87fd8fb15b36561017d37440a071a388c96d76991acc70cea3ad10537622e358ac8800213b687e1a3bcda4fb7365ce99e727eae54191a75021f76c9bff99509adc9af01dc98dd2ad1b6e1703026439b58a2db7ae37554cbaf21f4e6fde58e492653b7c9a87cfa4b987d00ce797077326fcf6afd4b19c985b7dce7376a3f80cf09dbfcf883979accd219e6b9c0bab705a1299bc306ea85f718082a4cc901ca763b59f38224dccd9cfaa8aa9908eec3cdb9d6e4ae7e9cb0a216225d34d205b0d343e6c1be6bf311d01aafcdfd8c61284746bf9fe83cdf4f4421b5bde4060cf81ad019f06db0ace76d59dec36b01e971601d3aaef9a25f192059fa3bd734deb9ab88df6e4f5c107a900d0449b2a05ac93b1f2c5f78e36cf0ed66e801", + "data": { + "slot": "4602226973500878187", + "index": "4593964548827522954", + "beacon_block_root": "0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6", + "source": { + "epoch": "535192610", + "root": "0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1" + }, + "target": { + "epoch": "534038364", + "root": "0xcbafa33faaa1f62b763b1697f350f91515f7aa53462f2500db29d9eff71c7ef1" + } + }, + "signature": "0x9776d892cbc6c914554d0153158c9a7c72164e9f60d3fe5816bdaf08a9bf3ca67d77a9c1d6431ba3294ec9032424322b0ab9e9a58fb0b9d8de93946ff414d2bef1f58da9f4a9b1e585c57571a6c8c8c2c0aa1c574939b7acd228d3f35d7b6e6e", + "committee_bits": "0x02" + } + ] +} \ No newline at end of file diff --git a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/getAttestationsPHASE0.json b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/getAttestationsPHASE0.json new file mode 100644 index 00000000000..51f28386580 --- /dev/null +++ b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/getAttestationsPHASE0.json @@ -0,0 +1,39 @@ +{ + "version": "phase0", + "data": [ + { + "aggregation_bits": "0x4a9278690f62e1a353f1abf2b9701e13e8cdf4d9ac6b032ba43b05b25f713540ce24f3192819e752fb091217ff34b68e934a06d316b6060696f8f24749574c4b3ac2e4ccb6914c434b09a81fff523e12acbe299fcdad715593298d72ca70e1ffc743ad7ce89587fbb4b4c57db7856b7082db70ebddcbebe264f886236df2dd51539e10d4dcd5950e6cea7c993d3e999a5589a4c71669ffb1390987e43a8c4790a70275364f96cbee34b0b5a9d1b3da4322ad12e07c81c6e6430d2528f19bb1727c3f63f414885bd97505283b0bb6773712096d5feb67c43d67f2fbf17bf796ed5080aece6b968d532d985ad2553daf31ad4022aa49d7a92ada719c9f93ab4b6f01", + "data": { + "slot": "4665021361504678828", + "index": "4669978815449698508", + "beacon_block_root": "0x103ac9406cdc59b89027eb1c9e97f607dd5fdccfa8fb2da4eaeea9d25032add9", + "source": { + "epoch": "542502839", + "root": "0x8200a6402ca295554fb9562193cc71d60272d63beeaf2201fdf53e846e77f919" + }, + "target": { + "epoch": "542887588", + "root": "0x5cbeb140ec0ad7cb653388caecba483cf66bd817821ed18ca1f3b7f3b9b58a04" + } + }, + "signature": "0xae401f767ab1917f925fe299ad51a57d52f7cc80deb1cc20fa2b3aa983e4e4d23056d79f01f3c97e29c8905da17e70e30c2a3f6bdd83dbc4ddf530e02e8f4d7ba22260e12e5f5fe7875b48e79660615b275597e87b2d33e076664b3da1737852" + }, + { + "aggregation_bits": "0xd837d68714980f385c05b1c4e60e08695d100bd93ba04e50555a513c03bd99ce5c71e4a2a5a91419669bc583aab06a4e8f5d03f406f837e1a1d5bbec09cef1d8d9e33e9b65986cb7c3a274160d8245732ae9bf069db0adc8c4b9550069bfba37b8831ae257e4435f9910b4f4833226fa4830001eab94dc4fb0c094ac3876f98df571f6287b2093a52d404dc4bc3ae98f220a5127f647a3ba73545715870cc3e15051d024ea5de67f28429fd3139d978cc4aec10f360ef8e1f2207665a62887850b6ab8094cde4d7683e37ba631c8dc6e8ea826e6d12d1bf0962970755d49f8451035b8e305c6352b7de6753e459f404b760689dd4f53dd9ff6b0d221849b10aa01", + "data": { + "slot": "4603879456717562315", + "index": "4602226973500878187", + "beacon_block_root": "0x6b0ac13f8a279ad3abec11bed1a49214f6e7af79b643595df6a38706b338e93b", + "source": { + "epoch": "534615487", + "root": "0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26" + }, + "target": { + "epoch": "535000236", + "root": "0xb88ea93f0a5617e780f8ae6b1fc8e4480ff4abc18f66fc45ada895271cbcc666" + } + }, + "signature": "0xb8f4f7eb7f1ff3eb3923e6bf36b3a0865c80f47fb8e5dbe8830751f66bd8a06a3a1e06b7b2dec66556b532721018ce940c982953c8c6176125c7dd2ba1e8cb944e10e4a14905f7135a477810872518cbac085dfc69c1759d64dab5e225a5f16c" + } + ] +} \ No newline at end of file diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java index c488a5d8c8f..b9e658cc645 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPool.java @@ -273,15 +273,10 @@ public synchronized List getAttestations( final Predicate>> filterForSlot = (entry) -> maybeSlot.map(slot -> entry.getKey().equals(slot)).orElse(true); - final Predicate filterForCommitteeIndex = - (group) -> - maybeCommitteeIndex - .map(index -> group.getAttestationData().getIndex().equals(index)) - .orElse(true); final UInt64 slot = maybeSlot.orElse(recentChainData.getCurrentSlot().orElse(UInt64.ZERO)); final SchemaDefinitions schemaDefinitions = spec.atSlot(slot).getSchemaDefinitions(); - final boolean requestRequiresAttestationWithCommitteeBits = + final boolean requiresCommitteeBits = schemaDefinitions.getAttestationSchema().requiresCommitteeBits(); return dataHashBySlot.descendingMap().entrySet().stream() @@ -290,12 +285,10 @@ public synchronized List getAttestations( .flatMap(Collection::stream) .map(attestationGroupByDataHash::get) .filter(Objects::nonNull) - .filter(filterForCommitteeIndex) - .flatMap(MatchingDataAttestationGroup::stream) + .flatMap( + matchingDataAttestationGroup -> + matchingDataAttestationGroup.stream(maybeCommitteeIndex, requiresCommitteeBits)) .map(ValidatableAttestation::getAttestation) - .filter( - attestation -> - attestation.requiresCommitteeBits() == requestRequiresAttestationWithCommitteeBits) .toList(); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java index b840d82e033..27303e656bd 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/attestation/MatchingDataAttestationGroup.java @@ -147,6 +147,14 @@ public Stream stream(final Optional committeeInd return StreamSupport.stream(spliterator(committeeIndex), false); } + public Stream stream( + final Optional committeeIndex, final boolean requiresCommitteeBits) { + if (noMatchingAttestations(committeeIndex, requiresCommitteeBits)) { + return Stream.empty(); + } + return StreamSupport.stream(spliterator(committeeIndex), false); + } + public Spliterator spliterator(final Optional committeeIndex) { return Spliterators.spliteratorUnknownSize(iterator(committeeIndex), 0); } @@ -229,6 +237,18 @@ public boolean matchesCommitteeShufflingSeed(final Set validSeeds) { return committeeShufflingSeed.map(validSeeds::contains).orElse(false); } + private boolean noMatchingAttestations( + final Optional committeeIndex, final boolean requiresCommitteeBits) { + return requiresCommitteeBits != includedValidators.requiresCommitteeBits() + || noMatchingPreElectraAttestations(committeeIndex); + } + + private boolean noMatchingPreElectraAttestations(final Optional committeeIndex) { + return committeeIndex.isPresent() + && !includedValidators.requiresCommitteeBits() + && !attestationData.getIndex().equals(committeeIndex.get()); + } + private class AggregatingIterator implements Iterator { private final Optional maybeCommitteeIndex; diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java index b804792a9c9..05d82c5c265 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/attestation/AggregatingAttestationPoolTest.java @@ -522,7 +522,10 @@ public void getAttestations_shouldReturnAllAttestations() { } @TestTemplate - public void getAttestations_shouldReturnAttestationsForGivenCommitteeIndexOnly() { + public void getAttestations_shouldReturnAttestationsForGivenCommitteeIndexOnly_PreElectra() { + assumeThat(specMilestone).isLessThan(ELECTRA); + // Pre Electra the committee index filter is applied to the index set at the attestation data + // level final AttestationData attestationData1 = dataStructureUtil.randomAttestationData(); final AttestationData attestationData2 = new AttestationData( @@ -531,7 +534,7 @@ public void getAttestations_shouldReturnAttestationsForGivenCommitteeIndexOnly() attestationData1.getBeaconBlockRoot(), attestationData1.getSource(), attestationData1.getTarget()); - Attestation attestation1 = addAttestationFromValidators(attestationData1, 1, 2, 3); + final Attestation attestation1 = addAttestationFromValidators(attestationData1, 1, 2, 3); addAttestationFromValidators(attestationData2, 4, 5, 6); assertThat( aggregatingPool.getAttestations( @@ -539,6 +542,20 @@ public void getAttestations_shouldReturnAttestationsForGivenCommitteeIndexOnly() .containsExactly(attestation1); } + @TestTemplate + public void getAttestations_shouldReturnAttestationsForGivenCommitteeIndexOnly_PostElectra() { + assumeThat(specMilestone).isGreaterThanOrEqualTo(ELECTRA); + // Post Electra the committee index filter is applied to the committee bits + final AttestationData attestationData1 = dataStructureUtil.randomAttestationData(); + final AttestationData attestationData2 = dataStructureUtil.randomAttestationData(); + final Attestation attestation1 = addAttestationFromValidators(attestationData1, 1, 2, 3); + final Optional committeeIndexFilter = committeeIndex; + committeeIndex = Optional.of(committeeIndex.get().plus(1)); + addAttestationFromValidators(attestationData2, 4, 5, 6); + assertThat(aggregatingPool.getAttestations(Optional.empty(), committeeIndexFilter)) + .containsExactly(attestation1); + } + @TestTemplate public void getAttestations_shouldReturnAttestationsForGivenSlotOnly() { final AttestationData attestationData1 = dataStructureUtil.randomAttestationData();