From beebcc73d7a2283732564352f4995cd14a041655 Mon Sep 17 00:00:00 2001 From: Lucas Saldanha Date: Wed, 12 Feb 2025 16:32:56 +1300 Subject: [PATCH 01/15] 25.2.0 Cleanup (#9109) --- CHANGELOG.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1a99ac259a..f9bffda3d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,7 @@ ## Unreleased Changes ### Breaking Changes - - Removed `MAX_CHUNK_SIZE` from network global configurations, which will mean older clients will not get it from newer versions of Teku. ### Additions and Improvements - - Applied spec change to alter `GOSSIP_MAX_SIZE` to `MAX_PAYLOAD_SIZE`. - - `MAX_PAYLOAD_SIZE` is now used instead of `MAX_CHUNK_SIZE`. - - Updated 3rd party products to latest versions. - - Add SSZ support to validator registration via Builder API. - - Deprecated beacon-api `/eth/v1/config/deposit_contract` - will be removed after electra, in the fulu timeframe. - - Deprecated beacon-api `/teku/v1/beacon/pool/deposits` - will be removed after electra, in the fulu timeframe. - - Deprecated beacon-api `/eth/v1/builder/states/{state_id}/expected_withdrawals` - will be removed after electra, in the fulu timeframe. -### Bug Fixes \ No newline at end of file +### Bug Fixes From f0e92c0e779220bbb5f3bc04a7463195004dda97 Mon Sep 17 00:00:00 2001 From: Glory Agatevure Date: Wed, 12 Feb 2025 09:54:21 +0100 Subject: [PATCH 02/15] update electra fork epoch (#9093) * update electra epoch version Signed-off-by: gconnect * Remove MAX_REQUEST_BLOB_SIDECARS Signed-off-by: gconnect * Add MAX_REQUEST_BLOB_SIDECARS Signed-off-by: gconnect --------- Signed-off-by: gconnect Co-authored-by: Enrico Del Fante --- .../tech/pegasys/teku/spec/config/configs/ephemery.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/ephemery.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/ephemery.yaml index 8a7e02b5faa..d2e31d1949c 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/ephemery.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/ephemery.yaml @@ -38,7 +38,7 @@ DENEB_FORK_EPOCH: 0 # Electra ELECTRA_FORK_VERSION: 0x6000101b -ELECTRA_FORK_EPOCH: 18446744073709551615 +ELECTRA_FORK_EPOCH: 10 # Time parameters # --------------------------------------------------------------- From b506d4a0dcca876de2e2830ecb0837752b37df29 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Wed, 12 Feb 2025 10:21:32 +0100 Subject: [PATCH 03/15] Add `SingleAttestation` in pretty print (#9101) * Add SingleAttestation in pretty print * Add SingleAttestation in pretty print --- .../pegasys/teku/cli/subcommand/debug/SszObjectType.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/SszObjectType.java b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/SszObjectType.java index 739745dc57c..e6cd5ba89f9 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/SszObjectType.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/subcommand/debug/SszObjectType.java @@ -43,6 +43,7 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsCapella; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; @SuppressWarnings("JavaCase") public enum SszObjectType { @@ -70,6 +71,7 @@ public enum SszObjectType { Deposit(new DepositSchema()), AttestationData(new AttestationDataSchema()), BeaconState(schemas(SchemaDefinitions::getBeaconStateSchema)), + SingleAttestation(electraSchemas(SchemaDefinitionsElectra::getSingleAttestationSchema)), Attestation(schemas(SchemaDefinitions::getAttestationSchema)), SignedVoluntaryExit(new SignedVoluntaryExitSchema()), SyncCommitteeMessage(altairSchemas(SchemaDefinitionsAltair::getSyncCommitteeMessageSchema)), @@ -136,6 +138,11 @@ private static Function> denebSchemas( return spec -> getter.apply(SchemaDefinitionsDeneb.required(spec.getSchemaDefinitions())); } + private static Function> electraSchemas( + final Function> getter) { + return spec -> getter.apply(SchemaDefinitionsElectra.required(spec.getSchemaDefinitions())); + } + private static Function> config( final Function> getter) { return spec -> getter.apply(spec.getConfig()); From bb1c53cc7ee365ac4d97590879a3d3e918b8876d Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Thu, 13 Feb 2025 08:21:07 +1100 Subject: [PATCH 04/15] Added GetPendingDeposits (#9110) Partially addresses https://github.com/ethereum/beacon-APIs/pull/500 Attempting to access this data prior to electra will fail with a 400. Signed-off-by: Paul Harris --- CHANGELOG.md | 1 + .../GetPendingDepositsIntegrationTest.java | 59 +++++++++ ...on_states_{state_id}_pending_deposits.json | 101 +++++++++++++++ .../schema/GetPendingDepositsResponse.json | 23 ++++ .../JsonTypeDefinitionBeaconRestApi.java | 2 + .../v1/beacon/GetStatePendingDeposits.java | 119 ++++++++++++++++++ .../beacon/GetStatePendingDepositsTest.java | 102 +++++++++++++++ .../pegasys/teku/api/ChainDataProvider.java | 36 ++++++ .../restapi/endpoints/EndpointMetadata.java | 5 + 9 files changed, 448 insertions(+) create mode 100644 data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetPendingDepositsIntegrationTest.java create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPendingDepositsResponse.json create mode 100644 data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java create mode 100644 data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDepositsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index f9bffda3d31..1b93ab9c9da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,5 +9,6 @@ ### Breaking Changes ### Additions and Improvements + - Added beacon-api `/eth/v1/beacon/states/{state_id}/pending_deposits` endpoint for use post-electra. ### Bug Fixes diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetPendingDepositsIntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetPendingDepositsIntegrationTest.java new file mode 100644 index 00000000000..198426e08cf --- /dev/null +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetPendingDepositsIntegrationTest.java @@ -0,0 +1,59 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.v1.beacon; + +import static org.assertj.core.api.Assertions.assertThat; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; + +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.util.List; +import okhttp3.Response; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.api.schema.Version; +import tech.pegasys.teku.beaconrestapi.AbstractDataBackedRestAPIIntegrationTest; +import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStatePendingDeposits; +import tech.pegasys.teku.infrastructure.json.JsonTestUtil; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; + +public class GetPendingDepositsIntegrationTest extends AbstractDataBackedRestAPIIntegrationTest { + @Test + public void shouldGetElectraDepositsJson() throws Exception { + startRestAPIAtGenesis(SpecMilestone.ELECTRA); + final List data = createBlocksAtSlots(10); + final Response response = get("head"); + + final String responseText = response.body().string(); + final JsonNode node = JsonTestUtil.parseAsJsonNode(responseText); + final BeaconStateElectra stateElectra = + data.getLast().getState().toVersionElectra().orElseThrow(); + assertThat(node.get("version").asText()).isEqualTo("electra"); + assertThat(node.get("execution_optimistic").asBoolean()).isFalse(); + assertThat(node.get("finalized").asBoolean()).isFalse(); + assertThat(node.get("data").size()).isEqualTo(2); + assertThat(node.get("data").get(0).get("slot").asInt()).isEqualTo(10); + assertThat(node.get("data").get(0).get("pubkey").asText()) + .isEqualTo(stateElectra.getPendingDeposits().get(0).getPublicKey().toHexString()); + assertThat(node.get("data").get(1).get("slot").asInt()).isEqualTo(10); + assertThat(node.get("data").get(1).get("pubkey").asText()) + .isEqualTo(stateElectra.getPendingDeposits().get(1).getPublicKey().toHexString()); + assertThat(response.header(HEADER_CONSENSUS_VERSION)).isEqualTo(Version.electra.name()); + } + + public Response get(final String stateId) throws IOException { + return getResponse(GetStatePendingDeposits.ROUTE.replace("{state_id}", stateId)); + } +} diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json new file mode 100644 index 00000000000..e647aec1a41 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json @@ -0,0 +1,101 @@ +{ + "get" : { + "tags" : [ "Beacon", "Experimental" ], + "operationId" : "getPendingDeposits", + "summary" : "Get pending deposits from state", + "description" : "Returns pending deposits for state with given 'stateId'. Should return 400 if requested before electra.", + "parameters" : [ { + "name" : "state_id", + "required" : true, + "in" : "path", + "schema" : { + "type" : "string", + "description" : "State identifier. Can be one of: \"head\" (canonical head in node's view), \"genesis\", \"finalized\", \"justified\", <slot>, <hex encoded stateRoot with 0x prefix>.", + "example" : "head" + } + } ], + "responses" : { + "200" : { + "description" : "Request successful", + "headers" : { + "Eth-Consensus-Version" : { + "description" : "Required in response so client can deserialize returned json or ssz data more effectively.", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ], + "example" : "phase0" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GetPendingDepositsResponse" + } + }, + "application/octet-stream" : { + "schema" : { + "type" : "string", + "format" : "binary" + } + } + } + }, + "404" : { + "description" : "Not found", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/HttpErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/HttpErrorResponse" + } + } + } + }, + "503" : { + "description" : "Service unavailable", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/HttpErrorResponse" + } + } + } + }, + "204" : { + "description" : "Data is unavailable because the chain has not yet reached genesis", + "content" : { } + }, + "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/GetPendingDepositsResponse.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPendingDepositsResponse.json new file mode 100644 index 00000000000..8b4264a33be --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPendingDepositsResponse.json @@ -0,0 +1,23 @@ +{ + "title" : "GetPendingDepositsResponse", + "type" : "object", + "required" : [ "version", "execution_optimistic", "finalized", "data" ], + "properties" : { + "version" : { + "type" : "string", + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + }, + "execution_optimistic" : { + "type" : "boolean" + }, + "finalized" : { + "type" : "boolean" + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PendingDeposit" + } + } + } +} \ 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 511cb94688f..32ccce20d16 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 @@ -54,6 +54,7 @@ import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateCommittees; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateFinalityCheckpoints; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateFork; +import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStatePendingDeposits; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateRandao; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateRoot; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateSyncCommittees; @@ -251,6 +252,7 @@ private static RestApi create( .endpoint(new PostVoluntaryExit(dataProvider)) .endpoint(new PostSyncCommittees(dataProvider)) .endpoint(new PostValidatorLiveness(dataProvider)) + .endpoint(new GetStatePendingDeposits(dataProvider, schemaCache)) .endpoint(new GetDepositSnapshot(eth1DataProvider)) // Event Handler .endpoint( diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java new file mode 100644 index 00000000000..651290a3ec8 --- /dev/null +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java @@ -0,0 +1,119 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.v1.beacon; + +import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.PARAMETER_STATE_ID; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.ETH_CONSENSUS_HEADER_TYPE; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.MILESTONE_TYPE; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.sszResponseType; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EXECUTION_OPTIMISTIC; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.FINALIZED; +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.json.types.CoreTypes.BOOLEAN_TYPE; +import static tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition.listOf; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.List; +import java.util.Optional; +import tech.pegasys.teku.api.ChainDataProvider; +import tech.pegasys.teku.api.DataProvider; +import tech.pegasys.teku.api.schema.Version; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +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.ssz.SszList; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingDeposit; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; + +public class GetStatePendingDeposits extends RestApiEndpoint { + public static final String ROUTE = "/eth/v1/beacon/states/{state_id}/pending_deposits"; + + private final ChainDataProvider chainDataProvider; + + public GetStatePendingDeposits( + final DataProvider dataProvider, final SchemaDefinitionCache schemaDefinitionCache) { + this(dataProvider.getChainDataProvider(), schemaDefinitionCache); + } + + GetStatePendingDeposits( + final ChainDataProvider provider, final SchemaDefinitionCache schemaDefinitionCache) { + super( + EndpointMetadata.get(ROUTE) + .operationId("getPendingDeposits") + .summary("Get pending deposits from state") + .description( + "Returns pending deposits for state with given 'stateId'. Should return 400 if requested before electra.") + .pathParam(PARAMETER_STATE_ID) + .tags(TAG_BEACON, TAG_EXPERIMENTAL) + .response( + SC_OK, + "Request successful", + getResponseType(schemaDefinitionCache), + sszResponseType(), + ETH_CONSENSUS_HEADER_TYPE) + .withNotFoundResponse() + .withUnsupportedMediaTypeResponse() + .withChainDataResponses() + .build()); + this.chainDataProvider = provider; + } + + @Override + public void handleRequest(final RestApiRequest request) throws JsonProcessingException { + + final SafeFuture>>> future = + chainDataProvider.getStatePendingDeposits(request.getPathParameter(PARAMETER_STATE_ID)); + + request.respondAsync( + future.thenApply( + maybeData -> + maybeData + .map( + objectAndMetadata -> { + request.header( + HEADER_CONSENSUS_VERSION, + Version.fromMilestone(objectAndMetadata.getMilestone()).name()); + return AsyncApiResponse.respondOk(objectAndMetadata); + }) + .orElseGet(AsyncApiResponse::respondNotFound))); + } + + private static SerializableTypeDefinition>> + getResponseType(final SchemaDefinitionCache schemaDefinitionCache) { + + final SerializableTypeDefinition pendingDepositType = + schemaDefinitionCache + .getSchemaDefinition(SpecMilestone.ELECTRA) + .toVersionElectra() + .orElseThrow() + .getPendingDepositSchema() + .getJsonTypeDefinition(); + + return SerializableTypeDefinition.>>object() + .name("GetPendingDepositsResponse") + .withField("version", MILESTONE_TYPE, ObjectAndMetaData::getMilestone) + .withField(EXECUTION_OPTIMISTIC, BOOLEAN_TYPE, ObjectAndMetaData::isExecutionOptimistic) + .withField(FINALIZED, BOOLEAN_TYPE, ObjectAndMetaData::isFinalized) + .withField("data", listOf(pendingDepositType), ObjectAndMetaData::getData) + .build(); + } +} diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDepositsTest.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDepositsTest.java new file mode 100644 index 00000000000..ed0dd29e678 --- /dev/null +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDepositsTest.java @@ -0,0 +1,102 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.v1.beacon; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +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_NOT_FOUND; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_UNSUPPORTED_MEDIA_TYPE; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseSszFromMetadata; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerWithChainDataProviderTest; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingDeposit; + +public class GetStatePendingDepositsTest + extends AbstractMigratedBeaconHandlerWithChainDataProviderTest { + + @BeforeEach + public void setup() { + + final GetStatePendingDeposits pendingDepositsHandler = + new GetStatePendingDeposits(chainDataProvider, schemaDefinitionCache); + initialise(SpecMilestone.ELECTRA); + genesis(); + setHandler(pendingDepositsHandler); + request.setPathParameter("state_id", "head"); + } + + @Test + void metadata_shouldHandle400() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_BAD_REQUEST); + } + + @Test + void metadata_shouldHandle404() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_NOT_FOUND); + } + + @Test + void metadata_shouldHandle415() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_UNSUPPORTED_MEDIA_TYPE); + } + + @Test + void metadata_shouldHandle500() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR); + } + + @Test + void metadata_shouldHandle200() throws IOException { + final PendingDeposit deposit = dataStructureUtil.randomPendingDeposit(); + final ObjectAndMetaData> responseData = + new ObjectAndMetaData<>(List.of(deposit), SpecMilestone.ELECTRA, false, true, false); + final String data = getResponseStringFromMetadata(handler, SC_OK, responseData); + String expected = + String.format( + "{\"version\":\"electra\",\"execution_optimistic\":false,\"finalized\":false," + + "\"data\":[{\"pubkey\":\"%s\",\"withdrawal_credentials\":\"%s\",\"amount\":\"%s\",\"signature\":\"%s\",\"slot\":\"%s\"}]}", + deposit.getPublicKey(), + deposit.getWithdrawalCredentials(), + deposit.getAmount(), + deposit.getSignature(), + deposit.getSlot()); + assertThat(data).isEqualTo(expected); + } + + @Test + void metadata_shouldHandle200OctetStream() throws IOException { + final BeaconStateElectra state = + dataStructureUtil.randomBeaconState().toVersionElectra().orElseThrow(); + final PendingDeposit deposit = dataStructureUtil.randomPendingDeposit(); + final SszList deposits = state.getPendingDeposits().getSchema().of(deposit); + final ObjectAndMetaData> responseData = + new ObjectAndMetaData<>(deposits, SpecMilestone.ELECTRA, false, true, false); + final byte[] data = getResponseSszFromMetadata(handler, SC_OK, responseData); + assertThat(Bytes.of(data)).isEqualTo(deposits.sszSerialize()); + } +} diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java index 84db5f623f4..33301baf928 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java @@ -53,6 +53,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.ssz.Merkleizable; +import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; @@ -71,6 +72,7 @@ import tech.pegasys.teku.spec.datastructures.state.CommitteeAssignment; import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingDeposit; import tech.pegasys.teku.spec.logic.common.statetransition.epoch.status.ValidatorStatuses; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException; @@ -746,4 +748,38 @@ public SafeFuture> getFinalizedStateSlot(final UInt64 beforeSlo .getLatestAvailableFinalizedState(beforeSlot) .thenApply(maybeState -> maybeState.map(BeaconState::getSlot)); } + + public SafeFuture>>> getStatePendingDeposits( + final String stateIdParam) { + return stateSelectorFactory + .createSelectorForStateId(stateIdParam) + .getState() + .thenApply(this::getPendingDeposits); + } + + private Optional>> getPendingDeposits( + final Optional maybeStateAndMetadata) { + if (maybeStateAndMetadata.isPresent()) { + if (!maybeStateAndMetadata + .get() + .getMilestone() + .isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)) { + throw new BadRequestException( + "The state was successfully retrieved, but was prior to electra and does not contain pending deposits."); + } + return maybeStateAndMetadata.map( + stateAndMetaData -> { + final SszList deposits = + stateAndMetaData.getData().toVersionElectra().orElseThrow().getPendingDeposits(); + ; + return new ObjectAndMetaData<>( + deposits, + stateAndMetaData.getMilestone(), + stateAndMetaData.isExecutionOptimistic(), + stateAndMetaData.isCanonical(), + stateAndMetaData.isFinalized()); + }); + } + return Optional.empty(); + } } diff --git a/infrastructure/restapi/src/main/java/tech/pegasys/teku/infrastructure/restapi/endpoints/EndpointMetadata.java b/infrastructure/restapi/src/main/java/tech/pegasys/teku/infrastructure/restapi/endpoints/EndpointMetadata.java index c6af6eaeaac..5b904eb13a1 100644 --- a/infrastructure/restapi/src/main/java/tech/pegasys/teku/infrastructure/restapi/endpoints/EndpointMetadata.java +++ b/infrastructure/restapi/src/main/java/tech/pegasys/teku/infrastructure/restapi/endpoints/EndpointMetadata.java @@ -776,6 +776,11 @@ public EndpointMetaDataBuilder withInternalErrorResponse() { return this; } + public EndpointMetaDataBuilder withUnsupportedMediaTypeResponse() { + response(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported media type", HTTP_ERROR_RESPONSE_TYPE); + return this; + } + public EndpointMetaDataBuilder withNotImplementedResponse() { response(SC_NOT_IMPLEMENTED, "Not implemented", HTTP_ERROR_RESPONSE_TYPE); return this; From 03777f06543fe40fb073df86466d629d273e13ff Mon Sep 17 00:00:00 2001 From: Gabriel Fukushima Date: Thu, 13 Feb 2025 12:02:51 +1000 Subject: [PATCH 05/15] upgrade besu library (#9116) Signed-off-by: Gabriel Fukushima --- gradle/versions.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 5dfef8211e1..5c74c8116aa 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -140,12 +140,12 @@ dependencyManagement { dependency 'io.prometheus:prometheus-metrics-bom:1.3.5' - dependencySet(group: 'org.hyperledger.besu.internal', version: '25.1.0') { + dependencySet(group: 'org.hyperledger.besu.internal', version: '25.2.0') { entry('metrics-core') entry('core') entry('config') } - dependencySet(group: 'org.hyperledger.besu', version: '25.1.0') { + dependencySet(group: 'org.hyperledger.besu', version: '25.2.0') { entry('besu-datatypes') entry('evm') entry('plugin-api') From eff5983efeea9733016e3bb2b34540dd8125d06e Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Fri, 14 Feb 2025 09:54:28 +1100 Subject: [PATCH 06/15] added getPendingPartialWithdrawals api for electra (#9113) Partially addresses ethereum/beacon-APIs#500 Attempting to access this data prior to electra will fail with a 400. Signed-off-by: Paul Harris --- CHANGELOG.md | 1 + ...dingPartialWithdrawalsIntegrationTest.java | 49 ++++ ...on_states_{state_id}_pending_deposits.json | 2 +- ...state_id}_pending_partial_withdrawals.json | 101 +++++++ .../GetPendingPartialWithdrawalsResponse.json | 23 ++ .../JsonTypeDefinitionBeaconRestApi.java | 2 + .../v1/beacon/GetStatePendingDeposits.java | 3 +- .../GetStatePendingPartialWithdrawals.java | 120 ++++++++ ...GetStatePendingPartialWithdrawalsTest.java | 102 +++++++ .../pegasys/teku/api/ChainDataProvider.java | 75 +++-- .../tech/pegasys/teku/spec/SpecMilestone.java | 4 + .../pegasys/teku/spec/SpecMilestoneTest.java | 274 +++++++++++------- .../teku/spec/util/DataStructureUtil.java | 2 +- 13 files changed, 627 insertions(+), 131 deletions(-) create mode 100644 data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetStatePendingPartialWithdrawalsIntegrationTest.java create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_partial_withdrawals.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPendingPartialWithdrawalsResponse.json create mode 100644 data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawals.java create mode 100644 data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawalsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b93ab9c9da..a894484ffe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Breaking Changes ### Additions and Improvements + - Added beacon-api `/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals` endpoint for use post-electra. - Added beacon-api `/eth/v1/beacon/states/{state_id}/pending_deposits` endpoint for use post-electra. ### Bug Fixes diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetStatePendingPartialWithdrawalsIntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetStatePendingPartialWithdrawalsIntegrationTest.java new file mode 100644 index 00000000000..610d6ff9172 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/GetStatePendingPartialWithdrawalsIntegrationTest.java @@ -0,0 +1,49 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.v1.beacon; + +import static org.assertj.core.api.Assertions.assertThat; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; + +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import okhttp3.Response; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.api.schema.Version; +import tech.pegasys.teku.beaconrestapi.AbstractDataBackedRestAPIIntegrationTest; +import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStatePendingPartialWithdrawals; +import tech.pegasys.teku.infrastructure.json.JsonTestUtil; +import tech.pegasys.teku.spec.SpecMilestone; + +public class GetStatePendingPartialWithdrawalsIntegrationTest + extends AbstractDataBackedRestAPIIntegrationTest { + @Test + public void shouldGetElectraDepositsJson() throws Exception { + startRestAPIAtGenesis(SpecMilestone.ELECTRA); + createBlocksAtSlots(10); + final Response response = get("head"); + + final String responseText = response.body().string(); + final JsonNode node = JsonTestUtil.parseAsJsonNode(responseText); + assertThat(node.get("version").asText()).isEqualTo("electra"); + assertThat(node.get("execution_optimistic").asBoolean()).isFalse(); + assertThat(node.get("finalized").asBoolean()).isFalse(); + assertThat(node.get("data").size()).isEqualTo(0); + assertThat(response.header(HEADER_CONSENSUS_VERSION)).isEqualTo(Version.electra.name()); + } + + public Response get(final String stateId) throws IOException { + return getResponse(GetStatePendingPartialWithdrawals.ROUTE.replace("{state_id}", stateId)); + } +} diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json index e647aec1a41..4daacc4478c 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_deposits.json @@ -1,6 +1,6 @@ { "get" : { - "tags" : [ "Beacon", "Experimental" ], + "tags" : [ "Beacon" ], "operationId" : "getPendingDeposits", "summary" : "Get pending deposits from state", "description" : "Returns pending deposits for state with given 'stateId'. Should return 400 if requested before electra.", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_partial_withdrawals.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_partial_withdrawals.json new file mode 100644 index 00000000000..46e5bbe88f9 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_states_{state_id}_pending_partial_withdrawals.json @@ -0,0 +1,101 @@ +{ + "get" : { + "tags" : [ "Beacon" ], + "operationId" : "getPendingPartialWithdrawals", + "summary" : "Get pending partial withdrawals from state", + "description" : "Returns pending partial withdrawals for state with given 'stateId'. Should return 400 if requested before electra.", + "parameters" : [ { + "name" : "state_id", + "required" : true, + "in" : "path", + "schema" : { + "type" : "string", + "description" : "State identifier. Can be one of: \"head\" (canonical head in node's view), \"genesis\", \"finalized\", \"justified\", <slot>, <hex encoded stateRoot with 0x prefix>.", + "example" : "head" + } + } ], + "responses" : { + "200" : { + "description" : "Request successful", + "headers" : { + "Eth-Consensus-Version" : { + "description" : "Required in response so client can deserialize returned json or ssz data more effectively.", + "required" : true, + "schema" : { + "type" : "string", + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ], + "example" : "phase0" + } + } + }, + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GetPendingPartialWithdrawalsResponse" + } + }, + "application/octet-stream" : { + "schema" : { + "type" : "string", + "format" : "binary" + } + } + } + }, + "404" : { + "description" : "Not found", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/HttpErrorResponse" + } + } + } + }, + "415" : { + "description" : "Unsupported media type", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/HttpErrorResponse" + } + } + } + }, + "503" : { + "description" : "Service unavailable", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/HttpErrorResponse" + } + } + } + }, + "204" : { + "description" : "Data is unavailable because the chain has not yet reached genesis", + "content" : { } + }, + "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/GetPendingPartialWithdrawalsResponse.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPendingPartialWithdrawalsResponse.json new file mode 100644 index 00000000000..cf0f5c2f217 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetPendingPartialWithdrawalsResponse.json @@ -0,0 +1,23 @@ +{ + "title" : "GetPendingPartialWithdrawalsResponse", + "type" : "object", + "required" : [ "version", "execution_optimistic", "finalized", "data" ], + "properties" : { + "version" : { + "type" : "string", + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + }, + "execution_optimistic" : { + "type" : "boolean" + }, + "finalized" : { + "type" : "boolean" + }, + "data" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PendingPartialWithdrawal" + } + } + } +} \ 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 32ccce20d16..ba50aef0054 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 @@ -55,6 +55,7 @@ import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateFinalityCheckpoints; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateFork; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStatePendingDeposits; +import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStatePendingPartialWithdrawals; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateRandao; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateRoot; import tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.GetStateSyncCommittees; @@ -253,6 +254,7 @@ private static RestApi create( .endpoint(new PostSyncCommittees(dataProvider)) .endpoint(new PostValidatorLiveness(dataProvider)) .endpoint(new GetStatePendingDeposits(dataProvider, schemaCache)) + .endpoint(new GetStatePendingPartialWithdrawals(dataProvider, schemaCache)) .endpoint(new GetDepositSnapshot(eth1DataProvider)) // Event Handler .endpoint( diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java index 651290a3ec8..ab4159cdfb4 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingDeposits.java @@ -22,7 +22,6 @@ import static tech.pegasys.teku.infrastructure.http.RestApiConstants.FINALIZED; 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.json.types.CoreTypes.BOOLEAN_TYPE; import static tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition.listOf; @@ -63,7 +62,7 @@ public GetStatePendingDeposits( .description( "Returns pending deposits for state with given 'stateId'. Should return 400 if requested before electra.") .pathParam(PARAMETER_STATE_ID) - .tags(TAG_BEACON, TAG_EXPERIMENTAL) + .tags(TAG_BEACON) .response( SC_OK, "Request successful", diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawals.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawals.java new file mode 100644 index 00000000000..1bb69beb651 --- /dev/null +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawals.java @@ -0,0 +1,120 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.v1.beacon; + +import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.PARAMETER_STATE_ID; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.ETH_CONSENSUS_HEADER_TYPE; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.MILESTONE_TYPE; +import static tech.pegasys.teku.ethereum.json.types.EthereumTypes.sszResponseType; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EXECUTION_OPTIMISTIC; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.FINALIZED; +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.json.types.CoreTypes.BOOLEAN_TYPE; +import static tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition.listOf; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.List; +import java.util.Optional; +import tech.pegasys.teku.api.ChainDataProvider; +import tech.pegasys.teku.api.DataProvider; +import tech.pegasys.teku.api.schema.Version; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +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.ssz.SszList; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; + +public class GetStatePendingPartialWithdrawals extends RestApiEndpoint { + public static final String ROUTE = "/eth/v1/beacon/states/{state_id}/pending_partial_withdrawals"; + + private final ChainDataProvider chainDataProvider; + + public GetStatePendingPartialWithdrawals( + final DataProvider dataProvider, final SchemaDefinitionCache schemaDefinitionCache) { + this(dataProvider.getChainDataProvider(), schemaDefinitionCache); + } + + GetStatePendingPartialWithdrawals( + final ChainDataProvider provider, final SchemaDefinitionCache schemaDefinitionCache) { + super( + EndpointMetadata.get(ROUTE) + .operationId("getPendingPartialWithdrawals") + .summary("Get pending partial withdrawals from state") + .description( + "Returns pending partial withdrawals for state with given 'stateId'. Should return 400 if requested before electra.") + .pathParam(PARAMETER_STATE_ID) + .tags(TAG_BEACON) + .response( + SC_OK, + "Request successful", + getResponseType(schemaDefinitionCache), + sszResponseType(), + ETH_CONSENSUS_HEADER_TYPE) + .withNotFoundResponse() + .withUnsupportedMediaTypeResponse() + .withChainDataResponses() + .build()); + this.chainDataProvider = provider; + } + + @Override + public void handleRequest(final RestApiRequest request) throws JsonProcessingException { + + final SafeFuture>>> future = + chainDataProvider.getPendingPartialWithdrawals( + request.getPathParameter(PARAMETER_STATE_ID)); + + request.respondAsync( + future.thenApply( + maybeData -> + maybeData + .map( + objectAndMetadata -> { + request.header( + HEADER_CONSENSUS_VERSION, + Version.fromMilestone(objectAndMetadata.getMilestone()).name()); + return AsyncApiResponse.respondOk(objectAndMetadata); + }) + .orElseGet(AsyncApiResponse::respondNotFound))); + } + + private static SerializableTypeDefinition>> + getResponseType(final SchemaDefinitionCache schemaDefinitionCache) { + final SchemaDefinitionsElectra schemaDefinitionsElectra = + schemaDefinitionCache + .getSchemaDefinition(SpecMilestone.ELECTRA) + .toVersionElectra() + .orElseThrow(); + + final SerializableTypeDefinition pendingPartialWithdrawalType = + schemaDefinitionsElectra.getPendingPartialWithdrawalSchema().getJsonTypeDefinition(); + + return SerializableTypeDefinition.>>object() + .name("GetPendingPartialWithdrawalsResponse") + .withField("version", MILESTONE_TYPE, ObjectAndMetaData::getMilestone) + .withField(EXECUTION_OPTIMISTIC, BOOLEAN_TYPE, ObjectAndMetaData::isExecutionOptimistic) + .withField(FINALIZED, BOOLEAN_TYPE, ObjectAndMetaData::isFinalized) + .withField("data", listOf(pendingPartialWithdrawalType), ObjectAndMetaData::getData) + .build(); + } +} diff --git a/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawalsTest.java b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawalsTest.java new file mode 100644 index 00000000000..d5dacc50caf --- /dev/null +++ b/data/beaconrestapi/src/test/java/tech/pegasys/teku/beaconrestapi/handlers/v1/beacon/GetStatePendingPartialWithdrawalsTest.java @@ -0,0 +1,102 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.v1.beacon; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +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_NOT_FOUND; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_UNSUPPORTED_MEDIA_TYPE; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseSszFromMetadata; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.getResponseStringFromMetadata; +import static tech.pegasys.teku.infrastructure.restapi.MetadataTestUtil.verifyMetadataErrorResponse; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.beaconrestapi.AbstractMigratedBeaconHandlerWithChainDataProviderTest; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; + +public class GetStatePendingPartialWithdrawalsTest + extends AbstractMigratedBeaconHandlerWithChainDataProviderTest { + + @BeforeEach + public void setup() { + + final GetStatePendingPartialWithdrawals pendingDepositsHandler = + new GetStatePendingPartialWithdrawals(chainDataProvider, schemaDefinitionCache); + initialise(SpecMilestone.ELECTRA); + genesis(); + setHandler(pendingDepositsHandler); + request.setPathParameter("state_id", "head"); + } + + @Test + void metadata_shouldHandle400() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_BAD_REQUEST); + } + + @Test + void metadata_shouldHandle404() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_NOT_FOUND); + } + + @Test + void metadata_shouldHandle415() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_UNSUPPORTED_MEDIA_TYPE); + } + + @Test + void metadata_shouldHandle500() throws JsonProcessingException { + verifyMetadataErrorResponse(handler, SC_INTERNAL_SERVER_ERROR); + } + + @Test + void metadata_shouldHandle200() throws IOException { + final PendingPartialWithdrawal withdrawal = dataStructureUtil.randomPendingPartialWithdrawal(); + final ObjectAndMetaData> responseData = + new ObjectAndMetaData<>(List.of(withdrawal), SpecMilestone.ELECTRA, false, true, false); + final String data = getResponseStringFromMetadata(handler, SC_OK, responseData); + final String expected = + String.format( + "{\"version\":\"electra\",\"execution_optimistic\":false,\"finalized\":false," + + "\"data\":[{\"validator_index\":\"%s\",\"amount\":\"%s\",\"withdrawable_epoch\":\"%s\"}]}", + withdrawal.getValidatorIndex(), + withdrawal.getAmount(), + withdrawal.getWithdrawableEpoch()); + assertThat(data).isEqualTo(expected); + } + + @Test + void metadata_shouldHandle200OctetStream() throws IOException { + final BeaconStateElectra state = + dataStructureUtil.randomBeaconState().toVersionElectra().orElseThrow(); + final PendingPartialWithdrawal pendingPartialWithdrawal = + dataStructureUtil.randomPendingPartialWithdrawal(); + final SszList deposits = + state.getPendingPartialWithdrawals().getSchema().of(pendingPartialWithdrawal); + final ObjectAndMetaData> responseData = + new ObjectAndMetaData<>(deposits, SpecMilestone.ELECTRA, false, true, false); + final byte[] data = getResponseSszFromMetadata(handler, SC_OK, responseData); + assertThat(Bytes.of(data)).isEqualTo(deposits.sszSerialize()); + } +} diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java index 33301baf928..695715d8112 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ChainDataProvider.java @@ -73,6 +73,7 @@ import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingDeposit; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; import tech.pegasys.teku.spec.logic.common.statetransition.epoch.status.ValidatorStatuses; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException; @@ -759,27 +760,59 @@ public SafeFuture>>> getState private Optional>> getPendingDeposits( final Optional maybeStateAndMetadata) { - if (maybeStateAndMetadata.isPresent()) { - if (!maybeStateAndMetadata - .get() - .getMilestone() - .isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)) { - throw new BadRequestException( - "The state was successfully retrieved, but was prior to electra and does not contain pending deposits."); - } - return maybeStateAndMetadata.map( - stateAndMetaData -> { - final SszList deposits = - stateAndMetaData.getData().toVersionElectra().orElseThrow().getPendingDeposits(); - ; - return new ObjectAndMetaData<>( - deposits, - stateAndMetaData.getMilestone(), - stateAndMetaData.isExecutionOptimistic(), - stateAndMetaData.isCanonical(), - stateAndMetaData.isFinalized()); - }); + + checkMinimumMilestone(maybeStateAndMetadata, SpecMilestone.ELECTRA); + return maybeStateAndMetadata.map( + stateAndMetaData -> { + final SszList deposits = + stateAndMetaData.getData().toVersionElectra().orElseThrow().getPendingDeposits(); + return new ObjectAndMetaData<>( + deposits, + stateAndMetaData.getMilestone(), + stateAndMetaData.isExecutionOptimistic(), + stateAndMetaData.isCanonical(), + stateAndMetaData.isFinalized()); + }); + } + + public SafeFuture>>> + getPendingPartialWithdrawals(final String stateIdParam) { + return stateSelectorFactory + .createSelectorForStateId(stateIdParam) + .getState() + .thenApply(this::getPendingPartialWithdrawals); + } + + private Optional>> + getPendingPartialWithdrawals(final Optional maybeStateAndMetadata) { + checkMinimumMilestone(maybeStateAndMetadata, SpecMilestone.ELECTRA); + + return maybeStateAndMetadata.map( + stateAndMetaData -> { + final SszList withdrawals = + stateAndMetaData + .getData() + .toVersionElectra() + .orElseThrow() + .getPendingPartialWithdrawals(); + return new ObjectAndMetaData<>( + withdrawals, + stateAndMetaData.getMilestone(), + stateAndMetaData.isExecutionOptimistic(), + stateAndMetaData.isCanonical(), + stateAndMetaData.isFinalized()); + }); + } + + private void checkMinimumMilestone( + final Optional maybeStateAndMetadata, + final SpecMilestone minimumMilestone) { + if (maybeStateAndMetadata.isPresent() + && maybeStateAndMetadata.get().getMilestone().isLessThan(minimumMilestone)) { + throw new BadRequestException( + String.format( + "The state was successfully retrieved, but was prior to %s and does not contain pending deposits.", + minimumMilestone.name())); } - return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java index 301b589b512..e1956cd7919 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java @@ -55,6 +55,10 @@ public boolean isLessThanOrEqualTo(final SpecMilestone other) { return compareTo(other) <= 0; } + public boolean isLessThan(final SpecMilestone other) { + return compareTo(other) < 0; + } + /** Returns the milestone prior to this milestone */ @SuppressWarnings("EnumOrdinal") public SpecMilestone getPreviousMilestone() { diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java index 004944845ff..5b675c9edc7 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java @@ -15,8 +15,19 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; - +import static tech.pegasys.teku.spec.SpecMilestone.ALTAIR; +import static tech.pegasys.teku.spec.SpecMilestone.BELLATRIX; +import static tech.pegasys.teku.spec.SpecMilestone.CAPELLA; +import static tech.pegasys.teku.spec.SpecMilestone.DENEB; +import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; + +import java.util.stream.Stream; +import org.assertj.core.api.AssertionsForClassTypes; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfig; @@ -57,170 +68,227 @@ public class SpecMilestoneTest { private final SpecConfig phase0SpecConfig = SpecConfigLoader.loadConfig(Eth2Network.MINIMAL.configName()).specConfig(); - @Test - public void isGreaterThanOrEqualTo() { - assertThat(SpecMilestone.PHASE0.isGreaterThanOrEqualTo(SpecMilestone.PHASE0)).isTrue(); - assertThat(SpecMilestone.PHASE0.isGreaterThanOrEqualTo(SpecMilestone.ALTAIR)).isFalse(); - - assertThat(SpecMilestone.ALTAIR.isGreaterThanOrEqualTo(SpecMilestone.PHASE0)).isTrue(); - assertThat(SpecMilestone.ALTAIR.isGreaterThanOrEqualTo(SpecMilestone.ALTAIR)).isTrue(); - assertThat(SpecMilestone.ALTAIR.isGreaterThanOrEqualTo(SpecMilestone.BELLATRIX)).isFalse(); - - assertThat(SpecMilestone.BELLATRIX.isGreaterThanOrEqualTo(SpecMilestone.ALTAIR)).isTrue(); - assertThat(SpecMilestone.BELLATRIX.isGreaterThanOrEqualTo(SpecMilestone.BELLATRIX)).isTrue(); - - assertThat(SpecMilestone.CAPELLA.isGreaterThanOrEqualTo(SpecMilestone.BELLATRIX)).isTrue(); - assertThat(SpecMilestone.CAPELLA.isGreaterThanOrEqualTo(SpecMilestone.CAPELLA)).isTrue(); - assertThat(SpecMilestone.CAPELLA.isGreaterThanOrEqualTo(SpecMilestone.DENEB)).isFalse(); - - assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.BELLATRIX)).isTrue(); - assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.CAPELLA)).isTrue(); - assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.DENEB)).isTrue(); - assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)).isFalse(); - - assertThat(SpecMilestone.ELECTRA.isGreaterThanOrEqualTo(SpecMilestone.CAPELLA)).isTrue(); - assertThat(SpecMilestone.ELECTRA.isGreaterThanOrEqualTo(SpecMilestone.DENEB)).isTrue(); - assertThat(SpecMilestone.ELECTRA.isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)).isTrue(); - } - - @Test - public void getPreviousMilestone() { - assertThrows(IllegalArgumentException.class, SpecMilestone.PHASE0::getPreviousMilestone); - assertThat(SpecMilestone.ALTAIR.getPreviousMilestone()).isEqualTo(SpecMilestone.PHASE0); - assertThat(SpecMilestone.BELLATRIX.getPreviousMilestone()).isEqualTo(SpecMilestone.ALTAIR); - assertThat(SpecMilestone.CAPELLA.getPreviousMilestone()).isEqualTo(SpecMilestone.BELLATRIX); - assertThat(SpecMilestone.DENEB.getPreviousMilestone()).isEqualTo(SpecMilestone.CAPELLA); - assertThat(SpecMilestone.ELECTRA.getPreviousMilestone()).isEqualTo(SpecMilestone.DENEB); + public static Stream isLessThanPermutations() { + return Stream.of( + Arguments.of(PHASE0, PHASE0, false), + Arguments.of(PHASE0, ALTAIR, true), + Arguments.of(PHASE0, BELLATRIX, true), + Arguments.of(PHASE0, CAPELLA, true), + Arguments.of(PHASE0, DENEB, true), + Arguments.of(PHASE0, ELECTRA, true), + Arguments.of(ALTAIR, PHASE0, false), + Arguments.of(ALTAIR, ALTAIR, false), + Arguments.of(ALTAIR, BELLATRIX, true), + Arguments.of(ALTAIR, CAPELLA, true), + Arguments.of(ALTAIR, DENEB, true), + Arguments.of(ALTAIR, ELECTRA, true), + Arguments.of(BELLATRIX, PHASE0, false), + Arguments.of(BELLATRIX, ALTAIR, false), + Arguments.of(BELLATRIX, BELLATRIX, false), + Arguments.of(BELLATRIX, CAPELLA, true), + Arguments.of(BELLATRIX, DENEB, true), + Arguments.of(BELLATRIX, ELECTRA, true), + Arguments.of(CAPELLA, PHASE0, false), + Arguments.of(CAPELLA, ALTAIR, false), + Arguments.of(CAPELLA, BELLATRIX, false), + Arguments.of(CAPELLA, CAPELLA, false), + Arguments.of(CAPELLA, DENEB, true), + Arguments.of(CAPELLA, ELECTRA, true), + Arguments.of(DENEB, PHASE0, false), + Arguments.of(DENEB, ALTAIR, false), + Arguments.of(DENEB, BELLATRIX, false), + Arguments.of(DENEB, CAPELLA, false), + Arguments.of(DENEB, DENEB, false), + Arguments.of(DENEB, ELECTRA, true), + Arguments.of(ELECTRA, PHASE0, false), + Arguments.of(ELECTRA, ALTAIR, false), + Arguments.of(ELECTRA, BELLATRIX, false), + Arguments.of(ELECTRA, CAPELLA, false), + Arguments.of(ELECTRA, DENEB, false), + Arguments.of(ELECTRA, ELECTRA, false)); + } + + public static Stream isGreaterThanOrEqualToPermutations() { + return Stream.of( + Arguments.of(PHASE0, PHASE0, true), + Arguments.of(PHASE0, ALTAIR, false), + Arguments.of(PHASE0, BELLATRIX, false), + Arguments.of(PHASE0, CAPELLA, false), + Arguments.of(PHASE0, DENEB, false), + Arguments.of(PHASE0, ELECTRA, false), + Arguments.of(ALTAIR, PHASE0, true), + Arguments.of(ALTAIR, ALTAIR, true), + Arguments.of(ALTAIR, BELLATRIX, false), + Arguments.of(ALTAIR, CAPELLA, false), + Arguments.of(ALTAIR, DENEB, false), + Arguments.of(ALTAIR, ELECTRA, false), + Arguments.of(BELLATRIX, PHASE0, true), + Arguments.of(BELLATRIX, ALTAIR, true), + Arguments.of(BELLATRIX, BELLATRIX, true), + Arguments.of(BELLATRIX, CAPELLA, false), + Arguments.of(BELLATRIX, DENEB, false), + Arguments.of(BELLATRIX, ELECTRA, false), + Arguments.of(CAPELLA, PHASE0, true), + Arguments.of(CAPELLA, ALTAIR, true), + Arguments.of(CAPELLA, BELLATRIX, true), + Arguments.of(CAPELLA, CAPELLA, true), + Arguments.of(CAPELLA, DENEB, false), + Arguments.of(CAPELLA, ELECTRA, false), + Arguments.of(DENEB, PHASE0, true), + Arguments.of(DENEB, ALTAIR, true), + Arguments.of(DENEB, BELLATRIX, true), + Arguments.of(DENEB, CAPELLA, true), + Arguments.of(DENEB, DENEB, true), + Arguments.of(DENEB, ELECTRA, false), + Arguments.of(ELECTRA, PHASE0, true), + Arguments.of(ELECTRA, ALTAIR, true), + Arguments.of(ELECTRA, BELLATRIX, true), + Arguments.of(ELECTRA, CAPELLA, true), + Arguments.of(ELECTRA, DENEB, true), + Arguments.of(ELECTRA, ELECTRA, true)); + } + + public static Stream getPreviousPermutations() { + return Stream.of( + Arguments.of(ALTAIR, PHASE0), + Arguments.of(BELLATRIX, ALTAIR), + Arguments.of(CAPELLA, BELLATRIX), + Arguments.of(DENEB, CAPELLA), + Arguments.of(ELECTRA, DENEB)); + } + + @ParameterizedTest + @MethodSource("isGreaterThanOrEqualToPermutations") + public void isGreaterThanOrEqualTo( + final SpecMilestone a, final SpecMilestone b, final boolean comparisonResult) { + assertThat(a.isGreaterThanOrEqualTo(b)).isEqualTo(comparisonResult); + } + + @ParameterizedTest + @MethodSource("isLessThanPermutations") + public void isLessThan( + final SpecMilestone a, final SpecMilestone b, final boolean comparisonResult) { + AssertionsForClassTypes.assertThat(a.isLessThan(b)).isEqualTo(comparisonResult); + } + + @ParameterizedTest + @MethodSource("getPreviousPermutations") + public void getPreviousMilestone(final SpecMilestone current, final SpecMilestone previous) { + assertThat(current.getPreviousMilestone()).isEqualTo(previous); + } + + @Test + void getPreviousMilestone_throws() { + assertThrows(IllegalArgumentException.class, PHASE0::getPreviousMilestone); + } + + @Test + void getPreviousMilestoneIfPresent() { + assertThat(PHASE0.getPreviousMilestoneIfExists()).isEmpty(); + assertThat(ALTAIR.getPreviousMilestoneIfExists()).contains(PHASE0); + assertThat(BELLATRIX.getPreviousMilestoneIfExists()).contains(ALTAIR); + assertThat(CAPELLA.getPreviousMilestoneIfExists()).contains(BELLATRIX); } @Test public void getAllPriorMilestones_phase0() { - assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.PHASE0)).isEmpty(); + assertThat(SpecMilestone.getAllPriorMilestones(PHASE0)).isEmpty(); } @Test public void getAllPriorMilestones_altair() { - assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.ALTAIR)) - .contains(SpecMilestone.PHASE0); + assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.ALTAIR)).contains(PHASE0); } @Test public void getAllPriorMilestones_bellatrix() { assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.BELLATRIX)) - .contains(SpecMilestone.PHASE0, SpecMilestone.ALTAIR); + .contains(PHASE0, SpecMilestone.ALTAIR); } @Test public void getAllPriorMilestones_capella() { - assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.CAPELLA)) - .contains(SpecMilestone.PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX); + assertThat(SpecMilestone.getAllPriorMilestones(CAPELLA)) + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX); } @Test public void getAllPriorMilestones_deneb() { - assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.DENEB)) - .contains( - SpecMilestone.PHASE0, - SpecMilestone.ALTAIR, - SpecMilestone.BELLATRIX, - SpecMilestone.CAPELLA); + assertThat(SpecMilestone.getAllPriorMilestones(DENEB)) + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX, CAPELLA); } @Test public void getAllPriorMilestones_electra() { assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.ELECTRA)) - .contains( - SpecMilestone.PHASE0, - SpecMilestone.ALTAIR, - SpecMilestone.BELLATRIX, - SpecMilestone.CAPELLA, - SpecMilestone.DENEB); + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX, CAPELLA, DENEB); } @Test public void getMilestonesUpTo_phase0() { - assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.PHASE0)) - .contains(SpecMilestone.PHASE0); + assertThat(SpecMilestone.getMilestonesUpTo(PHASE0)).contains(PHASE0); } @Test public void getMilestonesUpTo_altair() { assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.ALTAIR)) - .contains(SpecMilestone.PHASE0, SpecMilestone.ALTAIR); + .contains(PHASE0, SpecMilestone.ALTAIR); } @Test public void getMilestonesUpTo_bellatrix() { assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.BELLATRIX)) - .contains(SpecMilestone.PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX); + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX); } @Test public void getMilestonesUpTo_capella() { - assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.CAPELLA)) - .contains(SpecMilestone.PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX); + assertThat(SpecMilestone.getMilestonesUpTo(CAPELLA)) + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX); } @Test public void getMilestonesUpTo_deneb() { - assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.DENEB)) - .contains( - SpecMilestone.PHASE0, - SpecMilestone.ALTAIR, - SpecMilestone.BELLATRIX, - SpecMilestone.CAPELLA); + assertThat(SpecMilestone.getMilestonesUpTo(DENEB)) + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX, CAPELLA); } @Test public void getMilestonesUpTo_electra() { assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.ELECTRA)) - .contains( - SpecMilestone.PHASE0, - SpecMilestone.ALTAIR, - SpecMilestone.BELLATRIX, - SpecMilestone.CAPELLA, - SpecMilestone.DENEB); + .contains(PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX, CAPELLA, DENEB); } @Test public void areMilestonesInOrder() { - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.PHASE0, SpecMilestone.ALTAIR)) - .isTrue(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.PHASE0)).isTrue(); + assertThat(SpecMilestone.areMilestonesInOrder(PHASE0, SpecMilestone.ALTAIR)).isTrue(); + assertThat(SpecMilestone.areMilestonesInOrder(PHASE0)).isTrue(); assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.ALTAIR)).isTrue(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.ALTAIR, SpecMilestone.PHASE0)) - .isFalse(); + assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.ALTAIR, PHASE0)).isFalse(); assertThat( SpecMilestone.areMilestonesInOrder( - SpecMilestone.PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX)) + PHASE0, SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX)) .isTrue(); assertThat( SpecMilestone.areMilestonesInOrder( - SpecMilestone.ALTAIR, SpecMilestone.PHASE0, SpecMilestone.BELLATRIX)) + SpecMilestone.ALTAIR, PHASE0, SpecMilestone.BELLATRIX)) .isFalse(); assertThat( SpecMilestone.areMilestonesInOrder( - SpecMilestone.PHASE0, SpecMilestone.BELLATRIX, SpecMilestone.ALTAIR)) - .isFalse(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.BELLATRIX, SpecMilestone.CAPELLA)) - .isTrue(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.CAPELLA, SpecMilestone.BELLATRIX)) - .isFalse(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.CAPELLA, SpecMilestone.DENEB)) - .isTrue(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.DENEB, SpecMilestone.CAPELLA)) - .isFalse(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.DENEB, SpecMilestone.ELECTRA)) - .isTrue(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.ELECTRA, SpecMilestone.DENEB)) + PHASE0, SpecMilestone.BELLATRIX, SpecMilestone.ALTAIR)) .isFalse(); + assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.BELLATRIX, CAPELLA)).isTrue(); + assertThat(SpecMilestone.areMilestonesInOrder(CAPELLA, SpecMilestone.BELLATRIX)).isFalse(); + assertThat(SpecMilestone.areMilestonesInOrder(CAPELLA, DENEB)).isTrue(); + assertThat(SpecMilestone.areMilestonesInOrder(DENEB, CAPELLA)).isFalse(); + assertThat(SpecMilestone.areMilestonesInOrder(DENEB, SpecMilestone.ELECTRA)).isTrue(); + assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.ELECTRA, DENEB)).isFalse(); } @Test public void getForkVersion_phase0() { final Bytes4 expected = altairSpecConfig.getGenesisForkVersion(); - assertThat(SpecMilestone.getForkVersion(altairSpecConfig, SpecMilestone.PHASE0)) - .contains(expected); + assertThat(SpecMilestone.getForkVersion(altairSpecConfig, PHASE0)).contains(expected); } @Test @@ -240,15 +308,13 @@ public void getForkVersion_bellatrix() { @Test public void getForkVersion_capella() { final Bytes4 expected = capellaSpecConfig.getCapellaForkVersion(); - assertThat(SpecMilestone.getForkVersion(capellaSpecConfig, SpecMilestone.CAPELLA)) - .contains(expected); + assertThat(SpecMilestone.getForkVersion(capellaSpecConfig, CAPELLA)).contains(expected); } @Test public void getForkVersion_deneb() { final Bytes4 expected = denebSpecConfig.getDenebForkVersion(); - assertThat(SpecMilestone.getForkVersion(denebSpecConfig, SpecMilestone.DENEB)) - .contains(expected); + assertThat(SpecMilestone.getForkVersion(denebSpecConfig, DENEB)).contains(expected); } @Test @@ -261,8 +327,7 @@ public void getForkVersion_electra() { @Test public void getForkEpoch_phase0() { final UInt64 expected = UInt64.ZERO; - assertThat(SpecMilestone.getForkEpoch(altairSpecConfig, SpecMilestone.PHASE0)) - .contains(expected); + assertThat(SpecMilestone.getForkEpoch(altairSpecConfig, PHASE0)).contains(expected); } @Test @@ -282,14 +347,13 @@ public void getForkEpoch_bellatrix() { @Test public void getForkEpoch_capella() { final UInt64 expected = capellaSpecConfig.getCapellaForkEpoch(); - assertThat(SpecMilestone.getForkEpoch(capellaSpecConfig, SpecMilestone.CAPELLA)) - .contains(expected); + assertThat(SpecMilestone.getForkEpoch(capellaSpecConfig, CAPELLA)).contains(expected); } @Test public void getForkEpoch_deneb() { final UInt64 expected = denebSpecConfig.getDenebForkEpoch(); - assertThat(SpecMilestone.getForkEpoch(denebSpecConfig, SpecMilestone.DENEB)).contains(expected); + assertThat(SpecMilestone.getForkEpoch(denebSpecConfig, DENEB)).contains(expected); } @Test @@ -313,14 +377,12 @@ public void getForkSlot_bellatrixNotScheduled() { @Test public void getForkEpoch_capellaNotScheduled() { - assertThat(SpecMilestone.getForkEpoch(bellatrixSpecConfig, SpecMilestone.CAPELLA)) - .contains(UInt64.MAX_VALUE); + assertThat(SpecMilestone.getForkEpoch(bellatrixSpecConfig, CAPELLA)).contains(UInt64.MAX_VALUE); } @Test public void getForkEpoch_denebNotScheduled() { - assertThat(SpecMilestone.getForkEpoch(capellaSpecConfig, SpecMilestone.DENEB)) - .contains(UInt64.MAX_VALUE); + assertThat(SpecMilestone.getForkEpoch(capellaSpecConfig, DENEB)).contains(UInt64.MAX_VALUE); } @Test diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index c64ed60f946..a1c86803364 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -2642,7 +2642,7 @@ public PendingPartialWithdrawal randomPendingPartialWithdrawal() { return getElectraSchemaDefinitions(randomSlot()) .getPendingPartialWithdrawalSchema() .create( - SszUInt64.of(randomUInt64()), + SszUInt64.of(randomValidatorIndex()), SszUInt64.of(randomUInt64()), SszUInt64.of(randomUInt64())); } From 193da1c6218a21fe0d9c0b73cd5bdc98bf9a6709 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Tue, 18 Feb 2025 09:25:54 +0100 Subject: [PATCH 07/15] Improve attestation selection (#9114) * improve attestation selection * add test * improvements * no need for entrySet * rename var --------- Co-authored-by: Paul Harris --- .../AggregatingAttestationPool.java | 44 ++++++++++--- .../AggregatingAttestationPoolTest.java | 62 ++++++++++++++++--- 2 files changed, 88 insertions(+), 18 deletions(-) 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 b9e658cc645..5030b4cc6ce 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 @@ -15,6 +15,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntMap; import java.util.Collection; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -26,6 +27,7 @@ import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -57,6 +59,11 @@ public class AggregatingAttestationPool implements SlotEventsChannel { /** The valid attestation retention period is 64 slots in deneb */ static final long ATTESTATION_RETENTION_SLOTS = 64; + static final Comparator ATTESTATION_INCLUSION_COMPARATOR = + Comparator.comparingInt( + attestation -> attestation.getAggregationBits().getBitCount()) + .reversed(); + /** * Default maximum number of attestations to store in the pool. * @@ -238,22 +245,20 @@ public synchronized SszList getAttestationsForBlock( schemaDefinitions.getAttestationSchema().requiresCommitteeBits(); final AtomicInteger prevEpochCount = new AtomicInteger(0); + return dataHashBySlot // We can immediately skip any attestations from the block slot or later .headMap(stateAtBlockSlot.getSlot(), false) .descendingMap() .values() .stream() - .flatMap(Collection::stream) - .map(attestationGroupByDataHash::get) - .filter(Objects::nonNull) - .filter(group -> isValid(stateAtBlockSlot, group.getAttestationData())) - .filter(forkChecker::areAttestationsFromCorrectFork) - .flatMap(MatchingDataAttestationGroup::stream) - .map(ValidatableAttestation::getAttestation) - .filter( - attestation -> - attestation.requiresCommitteeBits() == blockRequiresAttestationsWithCommitteeBits) + .flatMap( + dataHashSetForSlot -> + streamAggregatesForDataHashesBySlot( + dataHashSetForSlot, + stateAtBlockSlot, + forkChecker, + blockRequiresAttestationsWithCommitteeBits)) .limit(attestationsSchema.getMaxLength()) .filter( attestation -> { @@ -267,6 +272,25 @@ public synchronized SszList getAttestationsForBlock( .collect(attestationsSchema.collector()); } + private Stream streamAggregatesForDataHashesBySlot( + final Set dataHashSetForSlot, + final BeaconState stateAtBlockSlot, + final AttestationForkChecker forkChecker, + final boolean blockRequiresAttestationsWithCommitteeBits) { + + return dataHashSetForSlot.stream() + .map(attestationGroupByDataHash::get) + .filter(Objects::nonNull) + .filter(group -> isValid(stateAtBlockSlot, group.getAttestationData())) + .filter(forkChecker::areAttestationsFromCorrectFork) + .flatMap(MatchingDataAttestationGroup::stream) + .map(ValidatableAttestation::getAttestation) + .filter( + attestation -> + attestation.requiresCommitteeBits() == blockRequiresAttestationsWithCommitteeBits) + .sorted(ATTESTATION_INCLUSION_COMPARATOR); + } + public synchronized List getAttestations( final Optional maybeSlot, 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 22d2277ff45..40588f52c70 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 @@ -63,7 +63,7 @@ class AggregatingAttestationPoolTest { public static final UInt64 SLOT = UInt64.valueOf(1234); - private static final int COMMITTEE_SIZE = 20; + private static final int COMMITTEE_SIZE = 130; private Spec spec; private SpecMilestone specMilestone; @@ -260,15 +260,61 @@ void getAttestationsForBlock_shouldIncludeMoreRecentAttestationsFirst() { @TestTemplate public void getAttestationsForBlock_shouldNotAddMoreAttestationsThanAllowedInBlock() { - final BeaconState state = dataStructureUtil.randomBeaconState(ONE); + final int allowed = + Math.toIntExact( + spec.atSlot(ONE) + .getSchemaDefinitions() + .getBeaconBlockBodySchema() + .getAttestationsSchema() + .getMaxLength()); + + final int validatorCount = allowed + 1; + final BeaconState state = dataStructureUtil.randomBeaconState(validatorCount, 100, ONE); final AttestationData attestationData = dataStructureUtil.randomAttestationData(ZERO); - final Attestation attestation1 = addAttestationFromValidators(attestationData, 1, 2, 3, 4); - final Attestation attestation2 = addAttestationFromValidators(attestationData, 2, 5); - // Won't be included because of the 2 attestation limit. - addAttestationFromValidators(attestationData, 2); - assertThat(aggregatingPool.getAttestationsForBlock(state, forkChecker)) - .containsExactly(attestation1, attestation2); + final int lastValidatorIndex = validatorCount - 1; + + // add non aggregatable attestations, more than allowed in block + for (int i = 0; i < validatorCount; i++) { + addAttestationFromValidators(attestationData, i, lastValidatorIndex); + } + + assertThat(aggregatingPool.getAttestationsForBlock(state, forkChecker)).hasSize(allowed); + } + + @TestTemplate + public void getAttestationsForBlock_shouldGivePriorityToBestAggregationForEachSlot() { + // let's test this on electra only, which has only 8 attestations for block + assumeThat(specMilestone).isGreaterThanOrEqualTo(ELECTRA); + assertThat( + spec.atSlot(ONE) + .getSchemaDefinitions() + .getBeaconBlockBodySchema() + .getAttestationsSchema() + .getMaxLength()) + .isEqualTo(8); + + final BeaconState state = dataStructureUtil.randomBeaconState(ONE); + + // let's prepare 2 different attestationData for the same slot + final AttestationData attestationData0 = dataStructureUtil.randomAttestationData(ZERO); + final AttestationData attestationData1 = dataStructureUtil.randomAttestationData(ZERO); + + // let's fill up the pool with non-aggregatable attestationsData0 + addAttestationFromValidators(attestationData0, 1, 2); + addAttestationFromValidators(attestationData0, 1, 3); + addAttestationFromValidators(attestationData0, 1, 4); + addAttestationFromValidators(attestationData0, 1, 5); + addAttestationFromValidators(attestationData0, 1, 6); + addAttestationFromValidators(attestationData0, 1, 7); + addAttestationFromValidators(attestationData0, 1, 8); + addAttestationFromValidators(attestationData0, 1, 9); + + // let's add a better aggregation for attestationData1 + final Attestation bestAttestation = addAttestationFromValidators(attestationData1, 11, 14, 15); + + assertThat(aggregatingPool.getAttestationsForBlock(state, forkChecker).get(0)) + .isEqualTo(bestAttestation); } @TestTemplate From db2cbf0295056c3dbe2d46b710d690429dff2d01 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Tue, 18 Feb 2025 11:05:21 +0100 Subject: [PATCH 08/15] fix mev gaslimit warning (#9136) --- .../teku/ethereum/executionlayer/BuilderBidValidatorImpl.java | 2 +- .../teku/ethereum/executionlayer/BuilderBidValidatorTest.java | 4 ++-- .../tech/pegasys/teku/infrastructure/logging/EventLogger.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorImpl.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorImpl.java index 026f428ed4e..ddd26ad67b5 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorImpl.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorImpl.java @@ -123,7 +123,7 @@ public void validateBuilderBid( final UInt64 proposedGasLimit = executionPayloadHeader.getGasLimit(); final UInt64 expectedGasLimit = expectedGasLimit(parentGasLimit, preferredGasLimit); - if (!expectedGasLimit.equals(preferredGasLimit)) { + if (!expectedGasLimit.equals(proposedGasLimit)) { eventLogger.builderBidNotHonouringGasLimit( parentGasLimit, proposedGasLimit, expectedGasLimit, preferredGasLimit); } diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorTest.java index 19bb80c5fd0..a71fba0554f 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/BuilderBidValidatorTest.java @@ -167,7 +167,7 @@ void expectedGasLimitTestCases( @Test void shouldNotLogEventIfGasLimitDecreases() throws BuilderBidValidationException { // 1023001 is as high as it can move in 1 shot - prepareGasLimit(UInt64.valueOf(1024_000), UInt64.valueOf(1022_000), UInt64.valueOf(1023_001)); + prepareGasLimit(UInt64.valueOf(1024_000), UInt64.valueOf(1023_001), UInt64.valueOf(1022_000)); builderBidValidatorWithMockSpec.validateBuilderBid( signedBuilderBid, validatorRegistration, state, Optional.empty()); @@ -178,7 +178,7 @@ void shouldNotLogEventIfGasLimitDecreases() throws BuilderBidValidationException @Test void shouldNotLogEventIfGasLimitIncreases() throws BuilderBidValidationException { // 1024999 is as high as it can move in 1 shot - prepareGasLimit(UInt64.valueOf(1024_000), UInt64.valueOf(1025_000), UInt64.valueOf(1024_999)); + prepareGasLimit(UInt64.valueOf(1024_000), UInt64.valueOf(1024_999), UInt64.valueOf(1025_000)); builderBidValidatorWithMockSpec.validateBuilderBid( signedBuilderBid, validatorRegistration, state, Optional.empty()); diff --git a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java index a7cd7228153..18cffdf00c7 100644 --- a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java +++ b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java @@ -336,12 +336,12 @@ public void depositContractLogsSyncingDisabled() { public void builderBidNotHonouringGasLimit( final UInt64 parentGasLimit, final UInt64 proposedGasLimit, - final UInt64 targetGasLimit, + final UInt64 expectedGasLimit, final UInt64 preferredGasLimit) { String reorgEventLog = String.format( "Builder proposed a bid not honouring the validator gas limit preference. Parent: %s - Proposed: %s - Expected %s - Target: %s", - parentGasLimit, proposedGasLimit, targetGasLimit, preferredGasLimit); + parentGasLimit, proposedGasLimit, expectedGasLimit, preferredGasLimit); warn(reorgEventLog, Color.YELLOW); } From 23cc8c332b28cc6e1e3c497c4da61f10f91eaf72 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Tue, 18 Feb 2025 11:31:37 +0100 Subject: [PATCH 09/15] builder-json-parsing-fix (#9135) --- .../teku/ethereum/executionclient/rest/ResponseHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/ResponseHandler.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/ResponseHandler.java index 9f3773c1c29..65a08ff621d 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/ResponseHandler.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/ResponseHandler.java @@ -85,7 +85,8 @@ void handleResponse(final Request request, final okhttp3.Response response) { return; } - if (!responseMediaType.is(MediaType.JSON_UTF_8)) { + if (!responseMediaType.type().equals(MediaType.JSON_UTF_8.type()) + || !responseMediaType.subtype().equals(MediaType.JSON_UTF_8.subtype())) { LOG.warn( "Response contains an incorrect Content-Type header: {}, attempting to parse as {} [{}]", responseMediaType, From edf44caa4297bdbbda79f5b17adebfaf271c8cf5 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Tue, 18 Feb 2025 11:54:06 +0100 Subject: [PATCH 10/15] set Accept header in getPayload (#9137) --- .../executionclient/rest/RestBuilderClientTest.java | 9 ++++++--- .../ethereum/executionclient/rest/RestBuilderClient.java | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClientTest.java b/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClientTest.java index 1e2f467a8a9..f2adbca4c58 100644 --- a/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClientTest.java +++ b/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClientTest.java @@ -673,9 +673,12 @@ private void runGetPayloadSuccessAsJson() { verifyBuilderPayloadResponse(builderPayload); }); final Consumer containsConsensusVersionHeader = - req -> - assertThat(req.getHeader("Eth-Consensus-Version")) - .isEqualTo(milestone.name().toLowerCase(Locale.ROOT)); + req -> { + assertThat(req.getHeader("Eth-Consensus-Version")) + .isEqualTo(milestone.name().toLowerCase(Locale.ROOT)); + assertThat(req.getHeader("Accept")) + .isEqualTo("application/octet-stream;q=1.0,application/json;q=0.9"); + }; verifyRequest( "POST", "/eth/v1/builder/blinded_blocks", diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClient.java index 2768534e6db..1ce10a5c806 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/RestBuilderClient.java @@ -212,7 +212,9 @@ public SafeFuture> getPayload( return restClient .postAsync( BuilderApiMethod.GET_PAYLOAD.getPath(), - Map.of(HEADER_CONSENSUS_VERSION, milestone.name().toLowerCase(Locale.ROOT)), + Map.ofEntries( + Map.entry(HEADER_CONSENSUS_VERSION, milestone.name().toLowerCase(Locale.ROOT)), + ACCEPT_HEADER), signedBlindedBeaconBlock, LAST_RECEIVED_HEADER_WAS_IN_SSZ.get(), responseTypeDefinition) From 9fd147b6baebf9315977cb222f6ec5d8082a47e3 Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Wed, 19 Feb 2025 08:58:29 +1100 Subject: [PATCH 11/15] Updating contribution guidelines for first time contributors. (#9145) Signed-off-by: Paul Harris --- CONTRIBUTING.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ce2a650e03..2d4268fec7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,19 @@ Start by looking through the 'good first issue' and 'help wanted' issues: * [Good First Issue][search-label-good-first-issue] - issues which should only require a few lines of code, and a test or two. * [Help wanted issues][search-label-help-wanted] - issues that are a bit more involved than `good first issue` issues. -Please keep in mind that we do not accept non-code contributions like fixing comments, typos or some other trivial fixes. Although we appreciate the extra help, managing lots of these small contributions is unfeasible, and puts extra pressure in our continuous delivery systems (running all tests, etc). Feel free to open an issue pointing any of those errors and we will batch them into a single change. +Please reach out in discord if you're looking to help out, and we can assist you in finding a good candidate ticket to work on, or discuss the idea you have. + +We have a [Teku](https://discord.com/channels/697535391594446898/697539289042649190) channel, and also a [Teku Contributors](https://discord.com/channels/697535391594446898/1050616638497640548) channel. + +Due to the prevalence of 'airdrop farming' type practices, this unfortunately puts heightened scrutiny on first time contributors, but if you're genuinely looking to help out, we'd really love to assist you in any way we can. +This does mean however that we will generally reject 'random' fixes such as 'TODO' fixes, typos, and generally things that add no value that we haven't identified as something we need. These are likely to be rejected with 'due to contribution guidelines' type responses. +This includes but is not limited to +* code replacement of TODO's that are not well tested or justified by performance and regression tests to prove their worth. +* typos, even if valid, will be worked into other PRs or just ignored completely if they're from first time contributors with no substantative value. +* things like replacing RuntimeException with a new exception type that's not well tested and adding value. +* rewording of comments + +Minimal discussion will be given in PR's due to the volume we're needing to deal with currently of this type of PR, which is taking away from actual development time, so please don't be offended if you're genuinely trying to help out; and we say 'see contribution guidelines'. ### Local Development The codebase is maintained using the "*contributor workflow*" where everyone without exception contributes patch proposals using "*pull-requests*". This facilitates social contribution, easy testing and peer review. From fb9f670d65b4972f47e5f62d6fef8de2eaece2ea Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Wed, 19 Feb 2025 16:31:40 +1100 Subject: [PATCH 12/15] rate-limit executor queue warning (#9128) fixes #9126 Rate limiting this message as it can come out very quickly, especially while starting. Signed-off-by: Paul Harris --- .../topics/topichandlers/Eth2TopicHandler.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/topichandlers/Eth2TopicHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/topichandlers/Eth2TopicHandler.java index a3c3e11ce38..e9c0d9987ae 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/topichandlers/Eth2TopicHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/topichandlers/Eth2TopicHandler.java @@ -27,6 +27,8 @@ import tech.pegasys.teku.infrastructure.exceptions.ExceptionUtil; import tech.pegasys.teku.infrastructure.ssz.SszData; import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; +import tech.pegasys.teku.infrastructure.time.Throttler; +import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.gossip.encoding.DecodingException; import tech.pegasys.teku.networking.eth2.gossip.encoding.Eth2PreparedGossipMessageFactory; @@ -56,6 +58,10 @@ public class Eth2TopicHandler implements TopicHandler private final NetworkingSpecConfig networkingConfig; private final DebugDataDumper debugDataDumper; private final String topic; + final TimeProvider timeProvider; + + // every slot of mainnet config + private final Throttler loggerThrottler = new Throttler<>(LOG, UInt64.valueOf(12)); public Eth2TopicHandler( final RecentChainData recentChainData, @@ -79,6 +85,7 @@ public Eth2TopicHandler( gossipEncoding.createPreparedGossipMessageFactory( recentChainData::getMilestoneByForkDigest); this.debugDataDumper = debugDataDumper; + this.timeProvider = recentChainData.getStore(); this.topic = GossipTopics.getTopic(forkDigest, topicName, gossipEncoding); } @@ -173,8 +180,12 @@ protected ValidationResult handleMessageProcessingError( P2P_LOG.onGossipMessageDecodingError(getTopic(), message.getOriginalMessage(), err); response = ValidationResult.Invalid; } else if (ExceptionUtil.hasCause(err, RejectedExecutionException.class)) { - LOG.warn( - "Discarding gossip message for topic {} because the executor queue is full", getTopic()); + loggerThrottler.invoke( + timeProvider.getTimeInSeconds(), + (log) -> + LOG.warn( + "Discarding gossip message for topic {} because the executor queue is full", + getTopic())); response = ValidationResult.Ignore; } else if (ExceptionUtil.hasCause(err, ServiceCapacityExceededException.class)) { LOG.warn( From 41577e971f9d6bfe7a6eea1eac27cf1bae878541 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Wed, 19 Feb 2025 15:48:42 +0100 Subject: [PATCH 13/15] Builder client - handle exception in request (#9148) * Builder client - handle exception in request * spotlessly --- .../ethereum/executionclient/rest/OkHttpRestClient.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/OkHttpRestClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/OkHttpRestClient.java index b64c17651aa..1c6277b7a05 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/OkHttpRestClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/rest/OkHttpRestClient.java @@ -16,6 +16,7 @@ import static java.util.Objects.requireNonNull; import java.io.IOException; +import java.io.UncheckedIOException; import java.net.URL; import java.util.Map; import java.util.Optional; @@ -120,8 +121,12 @@ private RequestBody createOctetStreamRequestBody(final S req return new RequestBody() { @Override - public void writeTo(final BufferedSink bufferedSink) { - requestBodyObject.sszSerialize(bufferedSink.outputStream()); + public void writeTo(final BufferedSink bufferedSink) throws IOException { + try { + requestBodyObject.sszSerialize(bufferedSink.outputStream()); + } catch (final UncheckedIOException e) { + throw e.getCause(); + } } @Override From c90d90b43d0b9eb13e39de76187ad4eea5e20879 Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Thu, 20 Feb 2025 07:32:00 +1100 Subject: [PATCH 14/15] Put stack trace to debug log (#9146) fixes #9042 Signed-off-by: Paul Harris --- .../teku/beacon/sync/forward/multipeer/SyncController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/SyncController.java b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/SyncController.java index c236b757c1d..bdec36e9cab 100644 --- a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/SyncController.java +++ b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/SyncController.java @@ -156,7 +156,8 @@ private InProgressSync startSync(final SyncTarget syncTarget) { syncResult.finishAsync( this::onSyncComplete, error -> { - LOG.error("Error encountered during sync", error); + LOG.error("Sync process failed to complete"); + LOG.debug("Error encountered during sync", error); onSyncComplete(SyncResult.FAILED); }, eventThread); From 52a7aa59aba998a159c516c6b0afb7501c63171e Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Thu, 20 Feb 2025 11:41:49 +0100 Subject: [PATCH 15/15] Attestations pool benchmark (#9140) --- .../teku/spec/util/BeaconBlockBuilder.java | 12 +- ethereum/statetransition/build.gradle | 2 + .../AggregatingAttestationPoolBenchmark.java | 175 ++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconBlockBuilder.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconBlockBuilder.java index 0e107d61c64..600d3ea8f7a 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconBlockBuilder.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconBlockBuilder.java @@ -15,6 +15,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; @@ -30,6 +31,7 @@ public class BeaconBlockBuilder { private final SpecVersion spec; private final DataStructureUtil dataStructureUtil; + private UInt64 slot; private SszList proposerSlashings; private SyncAggregate syncAggregate; private ExecutionPayload executionPayload; @@ -45,6 +47,11 @@ public BeaconBlockBuilder(final SpecVersion spec, final DataStructureUtil dataSt this.syncAggregate = dataStructureUtil.randomSyncAggregate(); } + public BeaconBlockBuilder slot(final UInt64 slot) { + this.slot = slot; + return this; + } + public BeaconBlockBuilder syncAggregate(final SyncAggregate syncAggregate) { this.syncAggregate = syncAggregate; return this; @@ -127,6 +134,9 @@ public SafeFuture build() { if (builder.supportsBlsToExecutionChanges()) { builder.blsToExecutionChanges(blsToExecutionChanges); } + if (builder.supportsKzgCommitments()) { + builder.blobKzgCommitments(dataStructureUtil.randomBlobKzgCommitments()); + } return SafeFuture.COMPLETE; }) .thenApply( @@ -134,7 +144,7 @@ public SafeFuture build() { spec.getSchemaDefinitions() .getBeaconBlockSchema() .create( - dataStructureUtil.randomUInt64(), + slot != null ? slot : dataStructureUtil.randomUInt64(), dataStructureUtil.randomUInt64(), dataStructureUtil.randomBytes32(), dataStructureUtil.randomBytes32(), diff --git a/ethereum/statetransition/build.gradle b/ethereum/statetransition/build.gradle index b1007fb0aa0..e5ed5a77a53 100644 --- a/ethereum/statetransition/build.gradle +++ b/ethereum/statetransition/build.gradle @@ -52,4 +52,6 @@ dependencies { testImplementation testFixtures(project(':infrastructure:logging')) jmhImplementation testFixtures(project(':infrastructure:bls')) + jmhImplementation testFixtures(project(':ethereum:spec')) + jmhImplementation 'org.mockito:mockito-core' } \ No newline at end of file diff --git a/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java b/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java new file mode 100644 index 00000000000..ba2398f0bb6 --- /dev/null +++ b/ethereum/statetransition/src/jmh/java/tech/pegasys/teku/statetransition.validation.signatures/AggregatingAttestationPoolBenchmark.java @@ -0,0 +1,175 @@ +/* + * Copyright Consensys Software Inc., 2025 + * + * 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.statetransition.validation.signatures; + +import static org.mockito.Mockito.mock; +import static tech.pegasys.teku.infrastructure.logging.Converter.gweiToEth; +import static tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool.DEFAULT_MAXIMUM_ATTESTATION_COUNT; + +import java.io.FileInputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.logic.common.util.BlockRewardCalculatorUtil; +import tech.pegasys.teku.spec.logic.common.util.BlockRewardCalculatorUtil.BlockRewardData; +import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; +import tech.pegasys.teku.statetransition.attestation.AttestationForkChecker; +import tech.pegasys.teku.storage.client.RecentChainData; + +@Warmup(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 2000, timeUnit = TimeUnit.MILLISECONDS) +@BenchmarkMode(Mode.AverageTime) +@Fork(1) +@State(Scope.Thread) +public class AggregatingAttestationPoolBenchmark { + private static final Spec SPEC = TestSpecFactory.createMainnetDeneb(); + + // pool dump can be created via something similar to + // https://github.com/tbenr/teku/commit/bd37ec8f5c6ce02edb3e375a1561e1d934b7d191 + // state and actual block can be obtained the usual ways + + // a reference file can be obtained here + // https://drive.google.com/file/d/139bA7r88riFODZ7S0FpvtO7hmWmdC_XC/view?usp=drive_link + private static final String STATE_PATH = + "BeaconStateDeneb_3630479_03664f196162fb81a4406c508674dd1ede09b883d37d0f3d0f076897f68741d2.ssz"; + + // a reference file can be obtained here + // https://drive.google.com/file/d/1I5vXK-x8ZH9wh40wNf1oACXeF_U3to8J/view?usp=drive_link + private static final String POOL_DUMP_PATH = "attestations_3630479.multi_ssz"; + + // a reference file can be obtained here + // https://drive.google.com/file/d/1PN0OToyNOV0SyjeQaS7oF3J4cKbmy1nX/view?usp=drive_link + private static final String ACTUAL_BLOCK_PATH = + "block-3630480-e652bd51c7e4e528fea0728a3ad96f86ceb92e9daa227f315e96a9884ceb187b.ssz"; + + private BeaconState state; + private BeaconState newBlockState; + private List attestations; + private AggregatingAttestationPool pool; + private RecentChainData recentChainData; + private AttestationForkChecker attestationForkChecker; + + @Setup(Level.Trial) + public void init() throws Exception { + + this.pool = + new AggregatingAttestationPool( + SPEC, recentChainData, new NoOpMetricsSystem(), DEFAULT_MAXIMUM_ATTESTATION_COUNT); + this.recentChainData = mock(RecentChainData.class); + + try (final FileInputStream fileInputStream = new FileInputStream(STATE_PATH)) { + this.state = + SPEC.getGenesisSpec() + .getSchemaDefinitions() + .getBeaconStateSchema() + .sszDeserialize(Bytes.wrap(fileInputStream.readAllBytes())); + } + + this.attestationForkChecker = new AttestationForkChecker(SPEC, state); + + var attestationSchema = SPEC.getGenesisSpec().getSchemaDefinitions().getAttestationSchema(); + + try (final Stream attestationLinesStream = Files.lines(Paths.get(POOL_DUMP_PATH))) { + attestationLinesStream + .map(line -> attestationSchema.sszDeserialize(Bytes.fromHexString(line))) + .map(attestation -> ValidatableAttestation.from(SPEC, attestation)) + .forEach( + attestation -> { + attestation.saveCommitteeShufflingSeedAndCommitteesSize(state); + pool.add(attestation); + }); + } + + this.newBlockState = SPEC.processSlots(state, state.getSlot().increment()); + + System.out.println("init done. Pool size: " + pool.getSize()); + } + + @Benchmark + public void getAttestationsForBlock(final Blackhole bh) { + var attestationsForBlock = pool.getAttestationsForBlock(newBlockState, attestationForkChecker); + bh.consume(attestationsForBlock); + } + + public void printBlockRewardData() throws Exception { + final BlockRewardCalculatorUtil blockRewardCalculatorUtil = new BlockRewardCalculatorUtil(SPEC); + final DataStructureUtil dataStructureUtil = new DataStructureUtil(SPEC); + final UInt64 blockSlot = state.getSlot().increment(); + + var block = + dataStructureUtil + .blockBuilder(blockSlot.longValue()) + .slot(blockSlot) + .attestations(pool.getAttestationsForBlock(newBlockState, attestationForkChecker)) + .build() + .getImmediately(); + + BlockRewardData blockRewardData = blockRewardCalculatorUtil.getBlockRewardData(block, state); + System.out.println( + "Block attestation rewards: " + + gweiToEth(UInt64.valueOf(blockRewardData.attestations())) + + " ETH"); + + final SignedBeaconBlock actualBlock; + try (final FileInputStream fileInputStream = new FileInputStream(ACTUAL_BLOCK_PATH)) { + actualBlock = + SPEC.getGenesisSpec() + .getSchemaDefinitions() + .getSignedBeaconBlockSchema() + .sszDeserialize(Bytes.wrap(fileInputStream.readAllBytes())); + } + + blockRewardData = blockRewardCalculatorUtil.getBlockRewardData(actualBlock.getMessage(), state); + System.out.println( + "Block attestation rewards: " + + gweiToEth(UInt64.valueOf(blockRewardData.attestations())) + + " ETH (actual block)"); + } + + public static void main(String[] args) throws Exception { + AggregatingAttestationPoolBenchmark benchmark = new AggregatingAttestationPoolBenchmark(); + benchmark.init(); + benchmark.printBlockRewardData(); + + var bh = + new Blackhole( + "Today's password is swordfish. I understand instantiating Blackholes directly is dangerous."); + + for (int i = 0; i < 100; i++) { + benchmark.getAttestationsForBlock(bh); + } + } +}