From bf649d00822a9868d9000328541c98c615d24f7a Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Mon, 9 Dec 2024 23:26:23 +1000 Subject: [PATCH 1/2] update post attestation interface for electra (#8893) --- ...tAttestationsV2ElectraIntegrationTest.java | 79 +++++++++++++ .../PostAttestationsV2IntegrationTest.java | 104 +++++++++--------- .../_eth_v2_beacon_pool_attestations.json | 2 +- .../beacon/schema/SingleAttestation.json | 28 +++++ .../v2/beacon/PostAttestationsV2.java | 63 ++++++++--- .../postAttestationRequestBodyELECTRA.json | 28 ++--- 6 files changed, 223 insertions(+), 81 deletions(-) create mode 100644 data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2ElectraIntegrationTest.java create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SingleAttestation.json diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2ElectraIntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2ElectraIntegrationTest.java new file mode 100644 index 00000000000..687feebd189 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2ElectraIntegrationTest.java @@ -0,0 +1,79 @@ +/* + * 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.v2.beacon; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import okhttp3.Response; +import org.junit.jupiter.api.BeforeEach; +import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.PostAttestationsV2; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.SingleAttestation; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class PostAttestationsV2ElectraIntegrationTest extends PostAttestationsV2IntegrationTest { + protected SerializableTypeDefinition> attestationsListTypeDef; + + @Override + @BeforeEach + void setup() { + spec = TestSpecFactory.createMinimalElectra(); + specMilestone = SpecMilestone.ELECTRA; + startRestAPIAtGenesis(specMilestone); + dataStructureUtil = new DataStructureUtil(spec); + attestationsListTypeDef = + SerializableTypeDefinition.listOf( + SchemaDefinitionsElectra.required(spec.getGenesisSchemaDefinitions()) + .getSingleAttestationSchema() + .getJsonTypeDefinition()); + } + + @Override + protected List getAttestationList(final int listSize) { + final List attestations = new ArrayList<>(listSize); + for (int i = 0; i < listSize; i++) { + attestations.add(dataStructureUtil.randomSingleAttestation()); + } + return attestations; + } + + @Override + @SuppressWarnings("unchecked") + protected Response postAttestations(final List attestations, final String milestone) + throws IOException { + final SerializableTypeDefinition> attestationsListTypeDef = + SerializableTypeDefinition.listOf( + SchemaDefinitionsElectra.required(spec.getGenesisSchemaDefinitions()) + .getSingleAttestationSchema() + .getJsonTypeDefinition()); + if (milestone == null) { + return post( + PostAttestationsV2.ROUTE, + JsonUtil.serialize((List) attestations, attestationsListTypeDef)); + } + return post( + PostAttestationsV2.ROUTE, + JsonUtil.serialize((List) attestations, attestationsListTypeDef), + Collections.emptyMap(), + Optional.of(milestone)); + } +} diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2IntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2IntegrationTest.java index f468e7651d1..8a4d9b35036 100644 --- a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2IntegrationTest.java +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttestationsV2IntegrationTest.java @@ -20,13 +20,14 @@ import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Optional; import okhttp3.Response; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.Test; import tech.pegasys.teku.beaconrestapi.AbstractDataBackedRestAPIIntegrationTest; import tech.pegasys.teku.beaconrestapi.handlers.v2.beacon.PostAttestationsV2; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -35,75 +36,51 @@ import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.TestSpecContext; -import tech.pegasys.teku.spec.TestSpecInvocationContextProvider; +import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.util.DataStructureUtil; import tech.pegasys.teku.validator.api.SubmitDataError; -@TestSpecContext(milestone = {SpecMilestone.PHASE0, SpecMilestone.ELECTRA}) public class PostAttestationsV2IntegrationTest extends AbstractDataBackedRestAPIIntegrationTest { - private DataStructureUtil dataStructureUtil; - private SpecMilestone specMilestone; - private SerializableTypeDefinition> attestationsListTypeDef; + protected DataStructureUtil dataStructureUtil; + protected SpecMilestone specMilestone; @BeforeEach - void setup(final TestSpecInvocationContextProvider.SpecContext specContext) { - spec = specContext.getSpec(); - specMilestone = specContext.getSpecMilestone(); + void setup() { + spec = TestSpecFactory.createMinimalPhase0(); + specMilestone = SpecMilestone.PHASE0; startRestAPIAtGenesis(specMilestone); - dataStructureUtil = specContext.getDataStructureUtil(); - attestationsListTypeDef = - SerializableTypeDefinition.listOf( - spec.getGenesisSchemaDefinitions() - .getAttestationSchema() - .castTypeToAttestationSchema() - .getJsonTypeDefinition()); + dataStructureUtil = new DataStructureUtil(spec); } - @TestTemplate + @Test void shouldPostAttestations_NoErrors() throws Exception { - final List attestations = - List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + final List attestations = getAttestationList(2); when(validatorApiChannel.sendSignedAttestations(attestations)) .thenReturn(SafeFuture.completedFuture(Collections.emptyList())); - final Response response = - post( - PostAttestationsV2.ROUTE, - JsonUtil.serialize(attestations, attestationsListTypeDef), - Collections.emptyMap(), - Optional.of(specMilestone.name().toLowerCase(Locale.ROOT))); + final Response response = postAttestations(attestations, specMilestone.name()); assertThat(response.code()).isEqualTo(SC_OK); assertThat(response.body().string()).isEmpty(); } - @TestTemplate + @Test void shouldPartiallyPostAttestations_ReturnsErrors() throws Exception { final SubmitDataError firstSubmitDataError = new SubmitDataError(UInt64.ZERO, "Bad attestation"); final SubmitDataError secondSubmitDataError = new SubmitDataError(UInt64.ONE, "Very bad attestation"); - final List attestations = - List.of( - dataStructureUtil.randomAttestation(), - dataStructureUtil.randomAttestation(), - dataStructureUtil.randomAttestation()); + final List attestations = getAttestationList(3); when(validatorApiChannel.sendSignedAttestations(attestations)) .thenReturn( SafeFuture.completedFuture(List.of(firstSubmitDataError, secondSubmitDataError))); - final Response response = - post( - PostAttestationsV2.ROUTE, - JsonUtil.serialize(attestations, attestationsListTypeDef), - Collections.emptyMap(), - Optional.of(specMilestone.name().toLowerCase(Locale.ROOT))); + final Response response = postAttestations(attestations, specMilestone.name()); assertThat(response.code()).isEqualTo(SC_BAD_REQUEST); final JsonNode resultAsJsonNode = JsonTestUtil.parseAsJsonNode(response.body().string()); @@ -121,16 +98,14 @@ void shouldPartiallyPostAttestations_ReturnsErrors() throws Exception { .isEqualTo(secondSubmitDataError.message()); } - @TestTemplate + @Test void shouldFailWhenMissingConsensusHeader() throws Exception { - final List attestations = - List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + final List attestations = getAttestationList(2); when(validatorApiChannel.sendSignedAttestations(attestations)) .thenReturn(SafeFuture.completedFuture(Collections.emptyList())); - final Response response = - post(PostAttestationsV2.ROUTE, JsonUtil.serialize(attestations, attestationsListTypeDef)); + final Response response = postAttestations(attestations, null); assertThat(response.code()).isEqualTo(SC_BAD_REQUEST); @@ -139,20 +114,14 @@ void shouldFailWhenMissingConsensusHeader() throws Exception { .isEqualTo("Missing required header value for (%s)", HEADER_CONSENSUS_VERSION); } - @TestTemplate + @Test void shouldFailWhenBadConsensusHeaderValue() throws Exception { - final List attestations = - List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + final List attestations = getAttestationList(2); when(validatorApiChannel.sendSignedAttestations(attestations)) .thenReturn(SafeFuture.completedFuture(Collections.emptyList())); final String badConsensusHeaderValue = "NonExistingMileStone"; - final Response response = - post( - PostAttestationsV2.ROUTE, - JsonUtil.serialize(attestations, attestationsListTypeDef), - Collections.emptyMap(), - Optional.of(badConsensusHeaderValue)); + final Response response = postAttestations(attestations, badConsensusHeaderValue); assertThat(response.code()).isEqualTo(SC_BAD_REQUEST); @@ -163,4 +132,33 @@ void shouldFailWhenBadConsensusHeaderValue() throws Exception { "Invalid value for (%s) header: %s", HEADER_CONSENSUS_VERSION, badConsensusHeaderValue)); } + + protected List getAttestationList(final int listSize) { + final List attestations = new ArrayList<>(listSize); + for (int i = 0; i < listSize; i++) { + attestations.add(dataStructureUtil.randomAttestation()); + } + return attestations; + } + + @SuppressWarnings("unchecked") + protected Response postAttestations(final List attestations, final String milestone) + throws IOException { + final SerializableTypeDefinition> attestationsListTypeDef = + SerializableTypeDefinition.listOf( + spec.getGenesisSchemaDefinitions() + .getAttestationSchema() + .castTypeToAttestationSchema() + .getJsonTypeDefinition()); + if (milestone == null) { + return post( + PostAttestationsV2.ROUTE, + JsonUtil.serialize((List) attestations, attestationsListTypeDef)); + } + return post( + PostAttestationsV2.ROUTE, + JsonUtil.serialize((List) attestations, attestationsListTypeDef), + Collections.emptyMap(), + Optional.of(milestone)); + } } 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 index 5f652893c81..7242e8ad9ee 100644 --- 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 @@ -100,7 +100,7 @@ "oneOf" : [ { "$ref" : "#/components/schemas/AttestationPhase0" }, { - "$ref" : "#/components/schemas/AttestationElectra" + "$ref" : "#/components/schemas/SingleAttestation" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SingleAttestation.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SingleAttestation.json new file mode 100644 index 00000000000..beb8224bfdc --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SingleAttestation.json @@ -0,0 +1,28 @@ +{ + "title" : "SingleAttestation", + "type" : "object", + "required" : [ "committee_index", "attester_index", "data", "signature" ], + "properties" : { + "committee_index" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "attester_index" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "data" : { + "$ref" : "#/components/schemas/AttestationData" + }, + "signature" : { + "type" : "string", + "pattern" : "^0x[a-fA-F0-9]{2,}$", + "description" : "SSZ hexadecimal", + "format" : "bytes" + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttestationsV2.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttestationsV2.java index 2ddd488a0f0..f8e14f5edd1 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttestationsV2.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttestationsV2.java @@ -15,22 +15,25 @@ import static tech.pegasys.teku.api.ValidatorDataProvider.PARTIAL_PUBLISH_FAILURE_MESSAGE; import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.ETH_CONSENSUS_VERSION_TYPE; -import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.getSchemaDefinitionForAllSupportedMilestones; -import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.headerBasedSelector; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; 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.http.RestApiConstants.TAG_BEACON; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_EXPERIMENTAL; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.TAG_VALIDATOR_REQUIRED; import com.fasterxml.jackson.core.JsonProcessingException; import java.util.List; +import java.util.Map; import java.util.function.BiPredicate; import tech.pegasys.teku.api.DataProvider; import tech.pegasys.teku.api.ValidatorDataProvider; +import tech.pegasys.teku.api.exceptions.BadRequestException; import tech.pegasys.teku.beaconrestapi.schema.ErrorListBadRequest; 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; @@ -40,7 +43,7 @@ import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; -import tech.pegasys.teku.spec.schemas.SchemaDefinitions; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.validator.api.SubmitDataError; public class PostAttestationsV2 extends RestApiEndpoint { @@ -87,20 +90,27 @@ private static EndpointMetadata createMetadata( .milestoneAtSlot(attestation.getData().getSlot()) .equals(milestone); + final SerializableOneOfTypeDefinitionBuilder builder = + new SerializableOneOfTypeDefinitionBuilder().title("SignedAttestation"); + + builder.withType( + value -> attestationSchemaPredicate.test(value, SpecMilestone.PHASE0), + schemaDefinitionCache + .getSchemaDefinition(SpecMilestone.PHASE0) + .getAttestationSchema() + .getJsonTypeDefinition()); + builder.withType( + value -> attestationSchemaPredicate.test(value, SpecMilestone.ELECTRA), + SchemaDefinitionsElectra.required( + schemaDefinitionCache.getSchemaDefinition(SpecMilestone.ELECTRA)) + .getSingleAttestationSchema() + .getJsonTypeDefinition()); final SerializableOneOfTypeDefinition attestationSchemaDefinition = - getSchemaDefinitionForAllSupportedMilestones( - schemaDefinitionCache, - "SignedAttestation", - SchemaDefinitions::getAttestationSchema, - attestationSchemaPredicate); + builder.build(); final OneOfArrayJsonRequestContentTypeDefinition.BodyTypeSelector attestationBodySelector = - context -> - headerBasedSelector( - context.getHeaders(), - schemaDefinitionCache, - SchemaDefinitions::getAttestationSchema); + context -> headerBasedSelector(context.getHeaders(), schemaDefinitionCache); return EndpointMetadata.post(ROUTE) .operationId("submitPoolAttestationsV2") @@ -123,4 +133,31 @@ private static EndpointMetadata createMetadata( .withChainDataResponses() .build(); } + + public static DeserializableTypeDefinition headerBasedSelector( + final Map headers, final SchemaDefinitionCache schemaDefinitionCache) { + if (!headers.containsKey(HEADER_CONSENSUS_VERSION)) { + throw new BadRequestException( + String.format("Missing required header value for (%s)", HEADER_CONSENSUS_VERSION)); + } + try { + final SpecMilestone milestone = SpecMilestone.forName(headers.get(HEADER_CONSENSUS_VERSION)); + if (milestone.isLessThanOrEqualTo(SpecMilestone.DENEB)) { + return schemaDefinitionCache + .getSchemaDefinition(SpecMilestone.PHASE0) + .getAttestationSchema() + .getJsonTypeDefinition(); + } else { + return SchemaDefinitionsElectra.required( + schemaDefinitionCache.getSchemaDefinition(SpecMilestone.ELECTRA)) + .getSingleAttestationSchema() + .getJsonTypeDefinition(); + } + } catch (Exception e) { + throw new BadRequestException( + String.format( + "Invalid value for (%s) header: %s", + HEADER_CONSENSUS_VERSION, headers.get(HEADER_CONSENSUS_VERSION))); + } + } } diff --git a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/postAttestationRequestBodyELECTRA.json b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/postAttestationRequestBodyELECTRA.json index 1ec340afbd9..67ce9a3a449 100644 --- a/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/postAttestationRequestBodyELECTRA.json +++ b/data/beaconrestapi/src/test/resources/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/postAttestationRequestBodyELECTRA.json @@ -1,20 +1,20 @@ [ { - "aggregation_bits": "0x4a9278690f62e1a353f1abf2b9701e13e8cdf4d9ac6b032ba43b05b25f713540ce24f3192819e752fb091217ff34b68e934a06d316b6060696f8f24749574c4b3ac2e4ccb6914c434b09a81fff523e12acbe299fcdad715593298d72ca70e1ffc743ad7ce89587fbb4b4c57db7856b7082db70ebddcbebe264f886236df2dd51539e10d4dcd5950e6cea7c993d3e999a5589a4c71669ffb1390987e43a8c4790a70275364f96cbee34b0b5a9d1b3da4322ad12e07c81c6e6430d2528f19bb1727c3f63f414885bd97505283b0bb6773712096d5feb67c43d67f2fbf17bf796ed5080aece6b968d532d985ad2553daf31ad4022aa49d7a92ada719c9f93ab4b6f0d09f127c8d47b9ab80e95a2e72257013e933113029994778c23dfa313b689e08c58979148ac541159b8eb601eee74e985a3b5b9c2dfe0ce3145794d84647136865fabf5814e8a013e236e9b740a6c18229838f3022e1aa2afe5fe48caff6e1470e4458ebcbf152462dc300b07a3d0b102a29196b0a8d444871868408fe80e1dcecd216fe8022ea70326081e516c48bd1b8a18003322738642b189013c3ed8ad64185cf1a44cb1f6265cc40450b5caea7c29b135b5145b4f3c5f14bc76f27442d5a180909ec2e144be68711737211e8d70bda6502a88a4a6558d9c857d6028b1fdfdf2d7df9d2a415b8754d194d17b29d09444d786a0478e62141c31410eda02abcd05769473e5fa75496d49aad564d139af8efee156d8089a253f4cd49814ed34fa9346701d66738938cbc5d54ba2adeb11dbe76f828dec46b82a6ff51dcf17e49771a6d88ad61996a5552809f78746562eba9d7aa9d4525d969c662628b857133d024ead8205bd3f367f3523c6ee9ff9b1784f47de41a1c196a73b178fce869b445c9a1b872a83ba946f2ca41232cdea11c53b7652dcfe615e9b3f9f0153f706eeee404e88e8736b3712e8ab9da9c9b75e419a615c3c1d1357886f77c8eaebbf4501dc1fef854fc5cc7f2f071c0a7411eb78bc14b25307cc7e4bde334ee3df0c53d6159751e82248f280434e466712dfe33981e0171e67352cdd86838eff3acd6e05592e2d1e441ddae9450a144da5c7926ee673458a59bc98c9e4d68f8b04134cc24ffdc2034c4ac3b46d6dd98ecca28dcaa7857f0c3f73a0809ae3c5dd2205555fd7cc6444024869d4f6d7dfe043917660119433c76239b17cdc8fad6c92f3756d206d67800e4e2566a73b27bb7a51dac62bc8411cdab0c5920a821e8ae6bb7779afb69f53452ad0b33c60c41a2be2c3aa94d46e97ffb5b2ebd9adc99eab85d5a3a73d4935f7ea6867d8277040c49f3ac822a4c7c7d67aba4f45765a46fccb7f79c332ac708b58911dc000c49d54fe6137be87df7f364a94fa5642338b6eeaf4152f7410ba97b0169aad82a9c4dd2d353cc3a8f57aaa90b5335f325b3e6e01", - "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", - "committee_bits": "0x08", - "data": { - "slot": "1", - "index": "1", - "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", - "source": { - "epoch": "1", - "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + "committee_index" : "1063251753", + "attester_index" : "1640609292", + "data" : { + "slot" : "4669978815449698508", + "index" : "4668326327938047084", + "beacon_block_root" : "0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f", + "source" : { + "epoch" : "542310465", + "root" : "0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379" }, - "target": { - "epoch": "1", - "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + "target" : { + "epoch" : "542695214", + "root" : "0x1f86d83f0bf91cc0d7e07410828140e0dddbb331dc20b6743f9f79e549b50b11" } - } + }, + "signature" : "0xb3a22ab9ec46aec35a9dacfb9036375ea1528041a926cb9d2d315ab964e82be5d6990e7fef2343f2dbb4c2b7dd74687f11144beaeb5758ebe349762b4dbde5e67bbc8d89a95a803c6610631d178249917cbf0d8b11bd8740f3cb767c843aa88c" } ] \ No newline at end of file From d10a47d92af7ada8757c7b16192f47b8636d6961 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Mon, 9 Dec 2024 16:53:52 +0100 Subject: [PATCH 2/2] Fix `SingleAttestation` in `PerformanceTracker` bug (#8902) --- .../coordinator/ValidatorApiHandler.java | 13 +++-- .../DefaultPerformanceTracker.java | 2 + .../coordinator/ValidatorApiHandlerTest.java | 47 +++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index 04648dd04fb..91515842187 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -586,13 +586,20 @@ public SafeFuture> sendSignedAttestations( } private SafeFuture processAttestation(final Attestation attestation) { + final ValidatableAttestation validatableAttestation = + ValidatableAttestation.fromValidator(spec, attestation); return attestationManager - .addAttestation(ValidatableAttestation.fromValidator(spec, attestation), Optional.empty()) + .addAttestation(validatableAttestation, Optional.empty()) .thenPeek( result -> { if (!result.isReject()) { - dutyMetrics.onAttestationPublished(attestation.getData().getSlot()); - performanceTracker.saveProducedAttestation(attestation); + // When saving the attestation in performance tracker, we want to make sure we save + // the converted attestation. + // The conversion happens during processing and is saved in the validatable + // attestation. + final Attestation convertedAttestation = validatableAttestation.getAttestation(); + dutyMetrics.onAttestationPublished(convertedAttestation.getData().getSlot()); + performanceTracker.saveProducedAttestation(convertedAttestation); } else { VALIDATOR_LOGGER.producedInvalidAttestation( attestation.getData().getSlot(), diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java index 381229ffca5..8031f4f0a30 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/performance/DefaultPerformanceTracker.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.validator.coordinator.performance; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; import com.google.common.annotations.VisibleForTesting; import it.unimi.dsi.fastutil.ints.Int2IntMap; @@ -424,6 +425,7 @@ private SafeFuture>> getAttestationsIncludedInEpoc @Override public void saveProducedAttestation(final Attestation attestation) { + checkState(!attestation.isSingleAttestation(), "Single attestation is not supported"); final UInt64 epoch = spec.computeEpochAtSlot(attestation.getData().getSlot()); final Set attestationsInEpoch = producedAttestationsByEpoch.computeIfAbsent(epoch, __ -> concurrentSet()); diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index d882571c41d..770ecbad932 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -819,6 +819,53 @@ public void sendSignedAttestations_shouldAddAttestationToAttestationManager() { .addAttestation(ValidatableAttestation.from(spec, attestation), Optional.empty()); } + @Test + void sendSignedAttestations_shouldSaveConvertedAttestationFromSingleAttestation() { + spec = TestSpecFactory.createMinimalElectra(); + dataStructureUtil = new DataStructureUtil(spec); + validatorApiHandler = + new ValidatorApiHandler( + chainDataProvider, + nodeDataProvider, + networkDataProvider, + chainDataClient, + syncStateProvider, + blockFactory, + attestationPool, + attestationManager, + attestationTopicSubscriptions, + activeValidatorTracker, + dutyMetrics, + performanceTracker, + spec, + forkChoiceTrigger, + proposersDataManager, + syncCommitteeMessagePool, + syncCommitteeContributionPool, + syncCommitteeSubscriptionManager, + blockProductionPerformanceFactory, + blockPublisher); + + final Attestation attestation = dataStructureUtil.randomSingleAttestation(); + final Attestation convertedAttestation = dataStructureUtil.randomAttestation(); + doAnswer( + invocation -> { + invocation + .getArgument(0, ValidatableAttestation.class) + .convertToAggregatedFormatFromSingleAttestation(convertedAttestation); + return completedFuture(InternalValidationResult.ACCEPT); + }) + .when(attestationManager) + .addAttestation(any(ValidatableAttestation.class), any()); + + final SafeFuture> result = + validatorApiHandler.sendSignedAttestations(List.of(attestation)); + assertThat(result).isCompletedWithValue(emptyList()); + + verify(dutyMetrics).onAttestationPublished(convertedAttestation.getData().getSlot()); + verify(performanceTracker).saveProducedAttestation(convertedAttestation); + } + @Test void sendSignedAttestations_shouldAddToDutyMetricsAndPerformanceTrackerWhenNotInvalid() { final Attestation attestation = dataStructureUtil.randomAttestation();