diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e20bc9d74..79bfa293d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,5 @@ - Clean up old beacon states when switching from ARCHIVE to PRUNE or MINIMAL data storage mode ### Bug Fixes + - Fixed a block production issue for Validator Client (24.10.0 to 24.10.2 teku VC), where required headers were not provided for JSON payloads. Default SSZ block production was unaffected. + - Block production now uses json data (more like 24.8.0 did than 24.10) if the Eth-Consensus-version header is absent. diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java index 11bddd0fdfc..762a10959b8 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java @@ -21,7 +21,8 @@ import java.util.Arrays; import java.util.Locale; import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.test.acceptance.dsl.AcceptanceTestBase; @@ -34,8 +35,9 @@ public class BlockProposalAcceptanceTest extends AcceptanceTestBase { private static final URL JWT_FILE = Resources.getResource("auth/ee-jwt-secret.hex"); - @Test - void shouldHaveCorrectFeeRecipientAndGraffiti() throws Exception { + @ParameterizedTest(name = "ssz_encode={0}") + @ValueSource(booleans = {true, false}) + void shouldHaveCorrectFeeRecipientAndGraffiti(final boolean useSszBlocks) throws Exception { final String networkName = "swift"; final ValidatorKeystores validatorKeystores = @@ -69,6 +71,7 @@ void shouldHaveCorrectFeeRecipientAndGraffiti() throws Exception { .withValidatorProposerDefaultFeeRecipient(defaultFeeRecipient) .withInteropModeDisabled() .withBeaconNodes(beaconNode) + .withBeaconNodeSszBlocksEnabled(useSszBlocks) .withGraffiti(userGraffiti) .withNetwork("auto") .build()); diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java index 40cc58845aa..ca23f2793ce 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuBeaconNode.java @@ -284,7 +284,7 @@ private IndexedAttestation randomIndexedAttestation( secretKey, signingRootUtil.signingRootForSignAttestationData(attestationData, forkInfo)); - final IndexedAttestationSchema schema = + final IndexedAttestationSchema schema = spec.getGenesisSchemaDefinitions().getIndexedAttestationSchema(); return schema.create( Stream.of(index).collect(schema.getAttestingIndicesSchema().collectorUnboxed()), diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java index b931a600a78..5ad12f151e5 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java @@ -530,6 +530,12 @@ public TekuNodeConfigBuilder withValidatorProposerDefaultFeeRecipient( return this; } + public TekuNodeConfigBuilder withBeaconNodeSszBlocksEnabled(final boolean enabled) { + LOG.debug("beacon-node-ssz-blocks-enabled={}", enabled); + configMap.put("beacon-node-ssz-blocks-enabled", enabled); + return this; + } + public TekuNodeConfigBuilder withStartupTargetPeerCount(final int startupTargetPeerCount) { mustBe(NodeType.BEACON_NODE); LOG.debug("Xstartup-target-peer-count={}", startupTargetPeerCount); diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/PostAttesterSlashingIntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/PostAttesterSlashingIntegrationTest.java index 92d670a017d..142d14ee5b4 100644 --- a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/PostAttesterSlashingIntegrationTest.java +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/beacon/PostAttesterSlashingIntegrationTest.java @@ -62,9 +62,7 @@ public void shouldReturnServerErrorWhenUnexpectedErrorHappens() throws Exception final Response response = post( PostAttesterSlashing.ROUTE, - JsonUtil.serialize( - slashing, - slashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition())); + JsonUtil.serialize(slashing, slashing.getSchema().getJsonTypeDefinition())); assertThat(response.code()).isEqualTo(SC_INTERNAL_SERVER_ERROR); } @@ -78,9 +76,7 @@ public void shouldReturnSuccessWhenRequestBodyIsValid() throws Exception { final Response response = post( PostAttesterSlashing.ROUTE, - JsonUtil.serialize( - slashing, - slashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition())); + JsonUtil.serialize(slashing, slashing.getSchema().getJsonTypeDefinition())); verify(attesterSlashingPool).addLocal(slashing); diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/validator/PostBlindedAndUnblindedBlockTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/validator/PostBlindedAndUnblindedBlockTest.java index 6b85496b015..27a1fdc00ff 100644 --- a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/validator/PostBlindedAndUnblindedBlockTest.java +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v1/validator/PostBlindedAndUnblindedBlockTest.java @@ -17,7 +17,6 @@ import static org.mockito.Mockito.when; import static tech.pegasys.teku.beaconrestapi.v1.validator.PostBlindedAndUnblindedBlockTest.Version.V1; import static tech.pegasys.teku.beaconrestapi.v1.validator.PostBlindedAndUnblindedBlockTest.Version.V2; -import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; import java.io.IOException; @@ -204,14 +203,7 @@ private void postRequestAndAssert( JsonUtil.serialize(request, signedBlockContainerSchema.getJsonTypeDefinition()), params, versionHeader)) { - if (version == V2 && versionHeader.isEmpty()) { - // the version header is required in V2 APIs - assertThat(response.code()).isEqualTo(SC_BAD_REQUEST); - } else { - // the version header is not required for V1 APIs, the header selector should fall back to - // the slot selector - assertThat(response.code()).isEqualTo(SC_OK); - } + assertThat(response.code()).isEqualTo(SC_OK); } } } diff --git a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttesterSlashingV2IntegrationTest.java b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttesterSlashingV2IntegrationTest.java index a9905fbaec2..ec8a79a9c54 100644 --- a/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttesterSlashingV2IntegrationTest.java +++ b/data/beaconrestapi/src/integration-test/java/tech/pegasys/teku/beaconrestapi/v2/beacon/PostAttesterSlashingV2IntegrationTest.java @@ -81,9 +81,7 @@ public void shouldReturnServerErrorWhenUnexpectedErrorHappens() throws Exception final Response response = post( PostAttesterSlashingV2.ROUTE, - JsonUtil.serialize( - slashing, - slashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition()), + JsonUtil.serialize(slashing, slashing.getSchema().getJsonTypeDefinition()), Collections.emptyMap(), Optional.of(specMilestone.name().toLowerCase(Locale.ROOT))); assertThat(response.code()).isEqualTo(500); @@ -99,9 +97,7 @@ public void shouldReturnSuccessWhenRequestBodyIsValid() throws Exception { final Response response = post( PostAttesterSlashingV2.ROUTE, - JsonUtil.serialize( - slashing, - slashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition()), + JsonUtil.serialize(slashing, slashing.getSchema().getJsonTypeDefinition()), Collections.emptyMap(), Optional.of(specMilestone.name().toLowerCase(Locale.ROOT))); @@ -117,9 +113,7 @@ void shouldFailWhenMissingConsensusHeader() throws Exception { final Response response = post( PostAttesterSlashingV2.ROUTE, - JsonUtil.serialize( - slashing, - slashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition())); + JsonUtil.serialize(slashing, slashing.getSchema().getJsonTypeDefinition())); assertThat(response.code()).isEqualTo(SC_BAD_REQUEST); @@ -139,9 +133,7 @@ void shouldFailWhenBadConsensusHeaderValue() throws Exception { final Response response = post( PostAttesterSlashingV2.ROUTE, - JsonUtil.serialize( - slashing, - slashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition()), + JsonUtil.serialize(slashing, slashing.getSchema().getJsonTypeDefinition()), Collections.emptyMap(), Optional.of(badConsensusHeaderValue)); diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/AttesterSlashingEvent.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/AttesterSlashingEvent.java index 3b8790695a2..47cd86f5114 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/AttesterSlashingEvent.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v1/events/AttesterSlashingEvent.java @@ -17,8 +17,6 @@ public class AttesterSlashingEvent extends Event { AttesterSlashingEvent(final AttesterSlashing attesterSlashing) { - super( - attesterSlashing.getSchema().castTypeToAttesterSlashingSchema().getJsonTypeDefinition(), - attesterSlashing); + super(attesterSlashing.getSchema().getJsonTypeDefinition(), attesterSlashing); } } diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttesterSlashingV2.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttesterSlashingV2.java index 776cc7dd343..4217eb91480 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttesterSlashingV2.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostAttesterSlashingV2.java @@ -61,7 +61,6 @@ private static EndpointMetadata createMetadata( schemaDefinitionCache .getSchemaDefinition(SpecMilestone.PHASE0) .getAttesterSlashingSchema() - .castTypeToAttesterSlashingSchema() .getJsonTypeDefinition(); final DeserializableTypeDefinition attesterSlashingElectraSchema = diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlindedBlockV2.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlindedBlockV2.java index 6c3422a37f8..77c5de04450 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlindedBlockV2.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlindedBlockV2.java @@ -16,7 +16,7 @@ import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.ETH_CONSENSUS_VERSION_TYPE; import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.PARAMETER_BROADCAST_VALIDATION; import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.getSchemaDefinitionForAllSupportedMilestones; -import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.headerBasedSelector; +import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.headerBasedSelectorWithSlotFallback; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_ACCEPTED; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NO_CONTENT; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; @@ -114,8 +114,9 @@ broadcast but a different status code is returned (202). Pre-Bellatrix, this end .milestoneAtSlot(blockContainer.getSlot()) .equals(milestone)), context -> - headerBasedSelector( + headerBasedSelectorWithSlotFallback( context.getHeaders(), + context.getBody(), schemaDefinitionCache, SchemaDefinitions::getSignedBlindedBlockContainerSchema), spec::deserializeSignedBlindedBlockContainer) diff --git a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlockV2.java b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlockV2.java index 9878d8f5759..3dec9966d6c 100644 --- a/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlockV2.java +++ b/data/beaconrestapi/src/main/java/tech/pegasys/teku/beaconrestapi/handlers/v2/beacon/PostBlockV2.java @@ -16,7 +16,7 @@ import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.ETH_CONSENSUS_VERSION_TYPE; import static tech.pegasys.teku.beaconrestapi.BeaconRestApiTypes.PARAMETER_BROADCAST_VALIDATION; import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.getSchemaDefinitionForAllSupportedMilestones; -import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.headerBasedSelector; +import static tech.pegasys.teku.beaconrestapi.handlers.v1.beacon.MilestoneDependentTypesUtil.headerBasedSelectorWithSlotFallback; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_ACCEPTED; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NO_CONTENT; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; @@ -114,8 +114,9 @@ validation, a separate success response code (202) is used to indicate that the .milestoneAtSlot(blockContainer.getSlot()) .equals(milestone)), context -> - headerBasedSelector( + headerBasedSelectorWithSlotFallback( context.getHeaders(), + context.getBody(), schemaDefinitionCache, SchemaDefinitions::getSignedBlockContainerSchema), spec::deserializeSignedBlockContainer) diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/AttesterSlashing.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/AttesterSlashing.java index 6ce8c3f3287..77e11250a18 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/AttesterSlashing.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/AttesterSlashing.java @@ -46,7 +46,7 @@ public AttesterSlashing( public tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing asInternalAttesterSlashing(final SpecVersion spec) { - final AttesterSlashingSchema attesterSlashingSchema = + final AttesterSlashingSchema attesterSlashingSchema = spec.getSchemaDefinitions().getAttesterSlashingSchema(); return attesterSlashingSchema.create( attestation_1.asInternalIndexedAttestation(spec), @@ -58,10 +58,9 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (!(o instanceof AttesterSlashing)) { + if (!(o instanceof AttesterSlashing that)) { return false; } - AttesterSlashing that = (AttesterSlashing) o; return Objects.equals(attestation_1, that.attestation_1) && Objects.equals(attestation_2, that.attestation_2); } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/IndexedAttestation.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/IndexedAttestation.java index 084c9fd4eec..0adcad6b9c5 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/IndexedAttestation.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/IndexedAttestation.java @@ -61,7 +61,7 @@ public IndexedAttestation( public tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation asInternalIndexedAttestation(final SpecVersion spec) { - final IndexedAttestationSchema indexedAttestationSchema = + final IndexedAttestationSchema indexedAttestationSchema = spec.getSchemaDefinitions().getIndexedAttestationSchema(); return indexedAttestationSchema.create( indexedAttestationSchema.getAttestingIndicesSchema().of(attesting_indices), @@ -74,10 +74,9 @@ public boolean equals(final Object o) { if (this == o) { return true; } - if (!(o instanceof IndexedAttestation)) { + if (!(o instanceof IndexedAttestation that)) { return false; } - IndexedAttestation that = (IndexedAttestation) o; return Objects.equals(attesting_indices, that.attesting_indices) && Objects.equals(data, that.data) && Objects.equals(signature, that.signature); diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlindedBlock.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlindedBlock.java index 639ae6bb926..3429bdb17a7 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlindedBlock.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlindedBlock.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.api.schema.bellatrix.SignedBlindedBeaconBlockBellatrix; import tech.pegasys.teku.api.schema.capella.SignedBlindedBeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.SignedBlindedBeaconBlockDeneb; +import tech.pegasys.teku.api.schema.electra.SignedBlindedBeaconBlockElectra; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; @Schema( @@ -26,6 +27,7 @@ SignedBeaconBlockAltair.class, SignedBlindedBeaconBlockBellatrix.class, SignedBlindedBeaconBlockCapella.class, - SignedBlindedBeaconBlockDeneb.class + SignedBlindedBeaconBlockDeneb.class, + SignedBlindedBeaconBlockElectra.class }) public interface SignedBlindedBlock {} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlock.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlock.java index 1a73c683bdd..692e51d5efd 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlock.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/SignedBlock.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.api.schema.bellatrix.SignedBeaconBlockBellatrix; import tech.pegasys.teku.api.schema.capella.SignedBeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.SignedBeaconBlockDeneb; +import tech.pegasys.teku.api.schema.electra.SignedBeaconBlockElectra; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; @Schema( @@ -26,6 +27,7 @@ SignedBeaconBlockAltair.class, SignedBeaconBlockBellatrix.class, SignedBeaconBlockCapella.class, - SignedBeaconBlockDeneb.class + SignedBeaconBlockDeneb.class, + SignedBeaconBlockElectra.class }) public interface SignedBlock {} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/State.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/State.java index 648208a0edd..dcde87b11bf 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/State.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/State.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.api.schema.bellatrix.BeaconStateBellatrix; import tech.pegasys.teku.api.schema.capella.BeaconStateCapella; import tech.pegasys.teku.api.schema.deneb.BeaconStateDeneb; +import tech.pegasys.teku.api.schema.electra.BeaconStateElectra; import tech.pegasys.teku.api.schema.phase0.BeaconStatePhase0; @Schema( @@ -26,6 +27,7 @@ BeaconStateAltair.class, BeaconStateBellatrix.class, BeaconStateCapella.class, - BeaconStateDeneb.class + BeaconStateDeneb.class, + BeaconStateElectra.class }) public interface State {} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlindedBlock.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlindedBlock.java index a302f6c76e2..3ca117efc16 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlindedBlock.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlindedBlock.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.api.schema.bellatrix.BlindedBlockBellatrix; import tech.pegasys.teku.api.schema.capella.BlindedBlockCapella; import tech.pegasys.teku.api.schema.deneb.BlindedBlockDeneb; +import tech.pegasys.teku.api.schema.electra.BlindedBlockElectra; import tech.pegasys.teku.api.schema.phase0.BeaconBlockPhase0; @Schema( @@ -26,6 +27,7 @@ BeaconBlockAltair.class, BlindedBlockBellatrix.class, BlindedBlockCapella.class, - BlindedBlockDeneb.class + BlindedBlockDeneb.class, + BlindedBlockElectra.class }) public interface UnsignedBlindedBlock {} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlock.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlock.java index 764534b411a..df8b98b1761 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlock.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/interfaces/UnsignedBlock.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.api.schema.bellatrix.BeaconBlockBellatrix; import tech.pegasys.teku.api.schema.capella.BeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.BeaconBlockDeneb; +import tech.pegasys.teku.api.schema.electra.BeaconBlockElectra; import tech.pegasys.teku.api.schema.phase0.BeaconBlockPhase0; @Schema( @@ -26,6 +27,7 @@ BeaconBlockAltair.class, BeaconBlockBellatrix.class, BeaconBlockCapella.class, - BeaconBlockDeneb.class + BeaconBlockDeneb.class, + BeaconBlockElectra.class }) public interface UnsignedBlock {} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java index 19d8d8a3d97..0277f3de9c6 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java @@ -30,6 +30,7 @@ import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV4; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; import tech.pegasys.teku.ethereum.executionclient.schema.Response; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -76,6 +77,9 @@ SafeFuture> forkChoiceUpdatedV2( SafeFuture> forkChoiceUpdatedV3( ForkChoiceStateV1 forkChoiceState, Optional payloadAttributes); + SafeFuture> forkChoiceUpdatedV4( + ForkChoiceStateV1 forkChoiceState, Optional payloadAttributes); + SafeFuture>> exchangeCapabilities(List capabilities); SafeFuture>> getClientVersionV1(ClientVersionV1 clientVersion); diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java index 646a513c8dc..50e1a969a2e 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java @@ -31,6 +31,7 @@ import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV4; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; import tech.pegasys.teku.ethereum.executionclient.schema.Response; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -144,6 +145,14 @@ public SafeFuture> forkChoiceUpdatedV3( () -> delegate.forkChoiceUpdatedV3(forkChoiceState, payloadAttributes)); } + @Override + public SafeFuture> forkChoiceUpdatedV4( + final ForkChoiceStateV1 forkChoiceState, + final Optional payloadAttributes) { + return taskQueue.queueTask( + () -> delegate.forkChoiceUpdatedV4(forkChoiceState, payloadAttributes)); + } + @Override public SafeFuture>> exchangeCapabilities(final List capabilities) { return taskQueue.queueTask(() -> delegate.exchangeCapabilities(capabilities)); diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineForkChoiceUpdatedV4.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineForkChoiceUpdatedV4.java new file mode 100644 index 00000000000..d00676e03dd --- /dev/null +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineForkChoiceUpdatedV4.java @@ -0,0 +1,81 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * 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.ethereum.executionclient.methods; + +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient; +import tech.pegasys.teku.ethereum.executionclient.response.ResponseUnwrapper; +import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; +import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV4; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.executionlayer.ForkChoiceState; +import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes; + +public class EngineForkChoiceUpdatedV4 + extends AbstractEngineJsonRpcMethod< + tech.pegasys.teku.spec.executionlayer.ForkChoiceUpdatedResult> { + + private static final Logger LOG = LogManager.getLogger(); + + public EngineForkChoiceUpdatedV4(final ExecutionEngineClient executionEngineClient) { + super(executionEngineClient); + } + + @Override + public String getName() { + return EngineApiMethod.ENGINE_FORK_CHOICE_UPDATED.getName(); + } + + @Override + public int getVersion() { + return 4; + } + + @Override + public SafeFuture execute( + final JsonRpcRequestParams params) { + final ForkChoiceState forkChoiceState = params.getRequiredParameter(0, ForkChoiceState.class); + final Optional payloadBuildingAttributes = + params.getOptionalParameter(1, PayloadBuildingAttributes.class); + + LOG.trace( + "Calling {}(forkChoiceState={}, payloadAttributes={})", + getVersionedName(), + forkChoiceState, + payloadBuildingAttributes); + + final Optional maybePayloadAttributes = + payloadBuildingAttributes.flatMap( + attributes -> + PayloadAttributesV4.fromInternalPayloadBuildingAttributesV4( + payloadBuildingAttributes)); + + return executionEngineClient + .forkChoiceUpdatedV4( + ForkChoiceStateV1.fromInternalForkChoiceState(forkChoiceState), maybePayloadAttributes) + .thenApply(ResponseUnwrapper::unwrapExecutionClientResponseOrThrow) + .thenApply(ForkChoiceUpdatedResult::asInternalExecutionPayload) + .thenPeek( + forkChoiceUpdatedResult -> + LOG.trace( + "Response {}(forkChoiceState={}, payloadAttributes={}) -> {}", + getVersionedName(), + forkChoiceState, + payloadBuildingAttributes, + forkChoiceUpdatedResult)); + } +} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java index 62d3a4def02..ec4b9249a08 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java @@ -33,6 +33,7 @@ import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV4; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; import tech.pegasys.teku.ethereum.executionclient.schema.Response; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -59,8 +60,11 @@ public class MetricRecordingExecutionEngineClient extends MetricRecordingAbstrac public static final String FORKCHOICE_UPDATED_WITH_ATTRIBUTES_V2_METHOD = "forkchoice_updated_with_attributesV2"; public static final String FORKCHOICE_UPDATED_V3_METHOD = "forkchoice_updatedV3"; + public static final String FORKCHOICE_UPDATED_V4_METHOD = "forkchoice_updatedV4"; public static final String FORKCHOICE_UPDATED_WITH_ATTRIBUTES_V3_METHOD = "forkchoice_updated_with_attributesV3"; + public static final String FORKCHOICE_UPDATED_WITH_ATTRIBUTES_V4_METHOD = + "forkchoice_updated_with_attributesV4"; public static final String GET_PAYLOAD_V3_METHOD = "get_payloadV3"; public static final String GET_PAYLOAD_V4_METHOD = "get_payloadV4"; public static final String NEW_PAYLOAD_V3_METHOD = "new_payloadV3"; @@ -185,6 +189,17 @@ public SafeFuture> forkChoiceUpdatedV3( : FORKCHOICE_UPDATED_V3_METHOD); } + @Override + public SafeFuture> forkChoiceUpdatedV4( + final ForkChoiceStateV1 forkChoiceState, + final Optional payloadAttributes) { + return countRequest( + () -> delegate.forkChoiceUpdatedV4(forkChoiceState, payloadAttributes), + payloadAttributes.isPresent() + ? FORKCHOICE_UPDATED_WITH_ATTRIBUTES_V4_METHOD + : FORKCHOICE_UPDATED_V4_METHOD); + } + @Override public SafeFuture>> exchangeCapabilities(final List capabilities) { return countRequest( 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 94b6ce672f0..d87f563b9e0 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 @@ -39,13 +39,13 @@ import tech.pegasys.teku.infrastructure.version.VersionProvider; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.builder.BuilderPayload; import tech.pegasys.teku.spec.datastructures.builder.BuilderPayloadSchema; import tech.pegasys.teku.spec.datastructures.builder.SignedBuilderBid; import tech.pegasys.teku.spec.datastructures.builder.SignedBuilderBidSchema; import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionCache; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; public class RestBuilderClient implements BuilderClient { @@ -65,13 +65,13 @@ public class RestBuilderClient implements BuilderClient { cachedBuilderApiSignedBuilderBidResponseType = new ConcurrentHashMap<>(); private final RestClient restClient; - private final SchemaDefinitionCache schemaDefinitionCache; + private final Spec spec; private final boolean setUserAgentHeader; public RestBuilderClient( final RestClient restClient, final Spec spec, final boolean setUserAgentHeader) { this.restClient = restClient; - this.schemaDefinitionCache = new SchemaDefinitionCache(spec); + this.spec = spec; this.setUserAgentHeader = setUserAgentHeader; } @@ -109,7 +109,8 @@ public SafeFuture>> getHeader( urlParams.put("parent_hash", parentHash.toHexString()); urlParams.put("pubkey", pubKey.toBytesCompressed().toHexString()); - final SpecMilestone milestone = schemaDefinitionCache.milestoneAtSlot(slot); + final SpecVersion specVersion = spec.atSlot(slot); + final SpecMilestone milestone = specVersion.getMilestone(); final DeserializableTypeDefinition> responseTypeDefinition = @@ -117,7 +118,7 @@ public SafeFuture>> getHeader( milestone, __ -> { final SchemaDefinitionsBellatrix schemaDefinitionsBellatrix = - getSchemaDefinitionsBellatrix(milestone); + getSchemaDefinitionsBellatrix(specVersion); final SignedBuilderBidSchema signedBuilderBidSchema = schemaDefinitionsBellatrix.getSignedBuilderBidSchema(); return BuilderApiResponse.createTypeDefinition( @@ -146,10 +147,10 @@ public SafeFuture> getPayload( final SignedBeaconBlock signedBlindedBeaconBlock) { final UInt64 blockSlot = signedBlindedBeaconBlock.getSlot(); - final SpecMilestone milestone = schemaDefinitionCache.milestoneAtSlot(blockSlot); - + final SpecVersion specVersion = spec.atSlot(blockSlot); + final SpecMilestone milestone = specVersion.getMilestone(); final SchemaDefinitionsBellatrix schemaDefinitionsBellatrix = - getSchemaDefinitionsBellatrix(milestone); + getSchemaDefinitionsBellatrix(specVersion); final DeserializableTypeDefinition requestTypeDefinition = schemaDefinitionsBellatrix.getSignedBlindedBeaconBlockSchema().getJsonTypeDefinition(); @@ -195,15 +196,14 @@ private BuilderPayload extractBuilderPayload( return builderApiResponse.getData(); } - private SchemaDefinitionsBellatrix getSchemaDefinitionsBellatrix( - final SpecMilestone specMilestone) { - return schemaDefinitionCache - .getSchemaDefinition(specMilestone) + private SchemaDefinitionsBellatrix getSchemaDefinitionsBellatrix(final SpecVersion specVersion) { + return specVersion + .getSchemaDefinitions() .toVersionBellatrix() .orElseThrow( () -> new IllegalArgumentException( - specMilestone + specVersion.getMilestone() + " is not a supported milestone for the builder rest api. Milestones >= Bellatrix are supported.")); } } diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/PayloadAttributesV4.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/PayloadAttributesV4.java new file mode 100644 index 00000000000..47e98eaad44 --- /dev/null +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/PayloadAttributesV4.java @@ -0,0 +1,115 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * 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.ethereum.executionclient.schema; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.common.base.MoreObjects; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.ethereum.executionclient.serialization.UInt64AsHexDeserializer; +import tech.pegasys.teku.ethereum.executionclient.serialization.UInt64AsHexSerializer; +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes; + +public class PayloadAttributesV4 extends PayloadAttributesV3 { + + @JsonSerialize(using = UInt64AsHexSerializer.class) + @JsonDeserialize(using = UInt64AsHexDeserializer.class) + public final UInt64 targetBlockCount; + + @JsonSerialize(using = UInt64AsHexSerializer.class) + @JsonDeserialize(using = UInt64AsHexDeserializer.class) + public final UInt64 maximumBlobCount; + + public PayloadAttributesV4( + final @JsonProperty("timestamp") UInt64 timestamp, + final @JsonProperty("prevRandao") Bytes32 prevRandao, + final @JsonProperty("suggestedFeeRecipient") Bytes20 suggestedFeeRecipient, + final @JsonProperty("withdrawals") List withdrawals, + final @JsonProperty("parentBeaconBlockRoot") Bytes32 parentBeaconBlockRoot, + final @JsonProperty("targetBlobCount") UInt64 targetBlockCount, + final @JsonProperty("maximumBlobCount") UInt64 maximumBlobCount) { + super(timestamp, prevRandao, suggestedFeeRecipient, withdrawals, parentBeaconBlockRoot); + + checkNotNull(targetBlockCount, "targetBlockCount"); + checkNotNull(maximumBlobCount, "maximumBlobCount"); + this.targetBlockCount = targetBlockCount; + this.maximumBlobCount = maximumBlobCount; + } + + public static Optional fromInternalPayloadBuildingAttributesV4( + final Optional payloadBuildingAttributes) { + return payloadBuildingAttributes.map( + payloadAttributes -> + new PayloadAttributesV4( + payloadAttributes.getTimestamp(), + payloadAttributes.getPrevRandao(), + payloadAttributes.getFeeRecipient(), + getWithdrawals(payloadAttributes), + payloadAttributes.getParentBeaconBlockRoot(), + payloadAttributes + .getTargetBlobCount() + .orElseThrow( + () -> + new IllegalArgumentException( + "targetBlobCount is required for PayloadAttributesV4")), + payloadAttributes + .getMaximumBlobCount() + .orElseThrow( + () -> + new IllegalArgumentException( + "maximumBlobCount is required for PayloadAttributesV4")))); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + final PayloadAttributesV4 that = (PayloadAttributesV4) o; + return Objects.equals(targetBlockCount, that.targetBlockCount) + && Objects.equals(maximumBlobCount, that.maximumBlobCount); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), targetBlockCount, maximumBlobCount); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("timestamp", timestamp) + .add("prevRandao", prevRandao) + .add("suggestedFeeRecipient", suggestedFeeRecipient) + .add("withdrawals", withdrawals) + .add("parentBeaconBlockRoot", parentBeaconBlockRoot) + .add("targetBlockCount", targetBlockCount) + .add("maximumBlobCount", maximumBlobCount) + .toString(); + } +} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java index 6eed83dd565..d029d8e4dfc 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java @@ -41,6 +41,7 @@ import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV4; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; import tech.pegasys.teku.ethereum.executionclient.schema.Response; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -238,6 +239,19 @@ public SafeFuture> forkChoiceUpdatedV3( return web3JClient.doRequest(web3jRequest, EL_ENGINE_BLOCK_EXECUTION_TIMEOUT); } + @Override + public SafeFuture> forkChoiceUpdatedV4( + final ForkChoiceStateV1 forkChoiceState, + final Optional payloadAttributes) { + final Request web3jRequest = + new Request<>( + "engine_forkchoiceUpdatedV4", + list(forkChoiceState, payloadAttributes.orElse(null)), + web3JClient.getWeb3jService(), + ForkChoiceUpdatedResultWeb3jResponse.class); + return web3JClient.doRequest(web3jRequest, EL_ENGINE_BLOCK_EXECUTION_TIMEOUT); + } + @Override public SafeFuture>> exchangeCapabilities(final List capabilities) { final Request web3jRequest = diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineForkChoiceUpdatedV4Test.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineForkChoiceUpdatedV4Test.java new file mode 100644 index 00000000000..e666b69bb31 --- /dev/null +++ b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineForkChoiceUpdatedV4Test.java @@ -0,0 +1,149 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * 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.ethereum.executionclient.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient; +import tech.pegasys.teku.ethereum.executionclient.response.InvalidRemoteResponseException; +import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; +import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV4; +import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; +import tech.pegasys.teku.ethereum.executionclient.schema.Response; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.executionlayer.ExecutionPayloadStatus; +import tech.pegasys.teku.spec.executionlayer.ForkChoiceState; +import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +class EngineForkChoiceUpdatedV4Test { + + private final Spec spec = TestSpecFactory.createMinimalDeneb(); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + private final ExecutionEngineClient executionEngineClient = mock(ExecutionEngineClient.class); + private EngineForkChoiceUpdatedV4 jsonRpcMethod; + + @BeforeEach + public void setUp() { + jsonRpcMethod = new EngineForkChoiceUpdatedV4(executionEngineClient); + } + + @Test + public void shouldReturnExpectedNameAndVersion() { + assertThat(jsonRpcMethod.getName()).isEqualTo("engine_forkchoiceUpdated"); + assertThat(jsonRpcMethod.getVersion()).isEqualTo(4); + assertThat(jsonRpcMethod.getVersionedName()).isEqualTo("engine_forkchoiceUpdatedV4"); + } + + @Test + public void forkChoiceStateParamIsRequired() { + final JsonRpcRequestParams params = new JsonRpcRequestParams.Builder().build(); + + assertThatThrownBy(() -> jsonRpcMethod.execute(params)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Missing required parameter at index 0"); + + verifyNoInteractions(executionEngineClient); + } + + @Test + public void payloadBuildingAttributesParamIsOptional() { + final ForkChoiceState forkChoiceState = dataStructureUtil.randomForkChoiceState(false); + + when(executionEngineClient.forkChoiceUpdatedV4(any(), eq(Optional.empty()))) + .thenReturn(dummySuccessfulResponse()); + + final JsonRpcRequestParams params = + new JsonRpcRequestParams.Builder().add(forkChoiceState).build(); + + assertThat(jsonRpcMethod.execute(params)).isCompleted(); + + verify(executionEngineClient).forkChoiceUpdatedV4(any(), eq(Optional.empty())); + } + + @Test + public void shouldReturnFailedFutureWithMessageWhenEngineClientRequestFails() { + final ForkChoiceState forkChoiceState = dataStructureUtil.randomForkChoiceState(false); + final String errorResponseFromClient = "error!"; + + when(executionEngineClient.forkChoiceUpdatedV4(any(), any())) + .thenReturn(dummyFailedResponse(errorResponseFromClient)); + + final JsonRpcRequestParams params = + new JsonRpcRequestParams.Builder().add(forkChoiceState).build(); + + assertThat(jsonRpcMethod.execute(params)) + .failsWithin(1, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) + .withRootCauseInstanceOf(InvalidRemoteResponseException.class) + .withMessageContaining( + "Invalid remote response from the execution client: %s", errorResponseFromClient); + } + + @Test + public void shouldCallForkChoiceUpdateV4WithPayloadAttributesV4WhenInElectra() { + final ForkChoiceState forkChoiceState = dataStructureUtil.randomForkChoiceState(false); + final PayloadBuildingAttributes payloadBuildingAttributes = + dataStructureUtil.randomPayloadBuildingAttributes(false); + final ForkChoiceStateV1 forkChoiceStateV1 = + ForkChoiceStateV1.fromInternalForkChoiceState(forkChoiceState); + final Optional payloadAttributesV4 = + PayloadAttributesV4.fromInternalPayloadBuildingAttributesV4( + Optional.of(payloadBuildingAttributes)); + + jsonRpcMethod = new EngineForkChoiceUpdatedV4(executionEngineClient); + + when(executionEngineClient.forkChoiceUpdatedV4(forkChoiceStateV1, payloadAttributesV4)) + .thenReturn(dummySuccessfulResponse()); + + final JsonRpcRequestParams params = + new JsonRpcRequestParams.Builder() + .add(forkChoiceState) + .add(payloadBuildingAttributes) + .build(); + + assertThat(jsonRpcMethod.execute(params)).isCompleted(); + + verify(executionEngineClient).forkChoiceUpdatedV4(forkChoiceStateV1, payloadAttributesV4); + } + + private SafeFuture> dummySuccessfulResponse() { + return SafeFuture.completedFuture( + new Response<>( + new ForkChoiceUpdatedResult( + new PayloadStatusV1( + ExecutionPayloadStatus.ACCEPTED, dataStructureUtil.randomBytes32(), ""), + dataStructureUtil.randomBytes8()))); + } + + private SafeFuture> dummyFailedResponse( + final String errorMessage) { + return SafeFuture.completedFuture(Response.withErrorMessage(errorMessage)); + } +} diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/schema/PayloadAttributesV4Test.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/schema/PayloadAttributesV4Test.java new file mode 100644 index 00000000000..f9eca48edab --- /dev/null +++ b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/schema/PayloadAttributesV4Test.java @@ -0,0 +1,101 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.ethereum.executionclient.schema; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +class PayloadAttributesV4Test { + + private final Spec spec = TestSpecFactory.createMinimalElectra(); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + + @Test + public void buildFromInternalPayload_RequiresTargetBlobCount() { + final PayloadBuildingAttributes pbaMissingTargetBlobCount = + new PayloadBuildingAttributes( + dataStructureUtil.randomUInt64(), + dataStructureUtil.randomUInt64(), + dataStructureUtil.randomUInt64(), + dataStructureUtil.randomBytes32(), + dataStructureUtil.randomEth1Address(), + Optional.empty(), + Optional.empty(), + dataStructureUtil.randomBytes32(), + Optional.empty(), + Optional.of(dataStructureUtil.randomUInt64())); + + assertThrows( + IllegalArgumentException.class, + () -> + PayloadAttributesV4.fromInternalPayloadBuildingAttributesV4( + Optional.of(pbaMissingTargetBlobCount))); + } + + @Test + public void buildFromInternalPayload_RequiresMaximumBlobCount() { + final PayloadBuildingAttributes pbaMissingMaximumBlobCount = + new PayloadBuildingAttributes( + dataStructureUtil.randomUInt64(), + dataStructureUtil.randomUInt64(), + dataStructureUtil.randomUInt64(), + dataStructureUtil.randomBytes32(), + dataStructureUtil.randomEth1Address(), + Optional.empty(), + Optional.empty(), + dataStructureUtil.randomBytes32(), + Optional.of(dataStructureUtil.randomUInt64()), + Optional.empty()); + + assertThrows( + IllegalArgumentException.class, + () -> + PayloadAttributesV4.fromInternalPayloadBuildingAttributesV4( + Optional.of(pbaMissingMaximumBlobCount))); + } + + @Test + public void buildFromInternalPayload_HasCorrectValues() { + final PayloadBuildingAttributes payloadBuildingAttributes = + dataStructureUtil.randomPayloadBuildingAttributes(false); + + final PayloadAttributesV4 payloadAttributesV4 = + PayloadAttributesV4.fromInternalPayloadBuildingAttributesV4( + Optional.of(payloadBuildingAttributes)) + .orElseThrow(); + + assertThat(payloadBuildingAttributes.getTimestamp()).isEqualTo(payloadAttributesV4.timestamp); + assertThat(payloadBuildingAttributes.getPrevRandao()).isEqualTo(payloadAttributesV4.prevRandao); + assertThat(payloadBuildingAttributes.getFeeRecipient()) + .isEqualTo(payloadAttributesV4.suggestedFeeRecipient); + assertThat(payloadBuildingAttributes.getWithdrawals()) + .hasValueSatisfying( + withdrawals -> + assertEquals(withdrawals.size(), payloadAttributesV4.withdrawals.size())); + assertThat(payloadBuildingAttributes.getParentBeaconBlockRoot()) + .isEqualTo(payloadAttributesV4.parentBeaconBlockRoot); + assertThat(payloadBuildingAttributes.getTargetBlobCount()) + .hasValue(payloadAttributesV4.targetBlockCount); + assertThat(payloadBuildingAttributes.getMaximumBlobCount()) + .hasValue(payloadAttributesV4.maximumBlobCount); + } +} diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java index 0651980f143..1635326d0f2 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java @@ -119,6 +119,8 @@ private Map> electraSupportedMethods() { methods.put(ENGINE_NEW_PAYLOAD, new EngineNewPayloadV4(executionEngineClient)); methods.put(ENGINE_GET_PAYLOAD, new EngineGetPayloadV4(executionEngineClient, spec)); + // TODO EIP-7742 Replace with EngineForkChoiceUpdatedV4 + // (https://github.com/Consensys/teku/issues/8745) methods.put(ENGINE_FORK_CHOICE_UPDATED, new EngineForkChoiceUpdatedV3(executionEngineClient)); methods.put(ENGINE_GET_BLOBS, new EngineGetBlobsV1(executionEngineClient, spec)); diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java index 1665419dfba..35a311f56f0 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java @@ -128,6 +128,7 @@ void engineNewPayload_shouldCallNewPayloadV4() { @Test void engineForkChoiceUpdated_shouldCallEngineForkChoiceUpdatedV3() { + // TODO EIP-7742 should call FcUV4 (https://github.com/Consensys/teku/issues/8745) final ExecutionClientHandler handler = getHandler(); final ForkChoiceState forkChoiceState = dataStructureUtil.randomForkChoiceState(false); final ForkChoiceStateV1 forkChoiceStateV1 = diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index 62140a31259..5b65f071933 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -27,16 +27,16 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final UInt64 minActivationBalance; private final UInt64 maxEffectiveBalanceElectra; + private final int minSlashingPenaltyQuotientElectra; + private final int whistleblowerRewardQuotientElectra; private final int pendingDepositsLimit; private final int pendingPartialWithdrawalsLimit; private final int pendingConsolidationsLimit; - private final int minSlashingPenaltyQuotientElectra; - private final int whistleblowerRewardQuotientElectra; private final int maxAttesterSlashingsElectra; private final int maxAttestationsElectra; - private final int maxConsolidationRequestsPerPayload; private final int maxDepositRequestsPerPayload; private final int maxWithdrawalRequestsPerPayload; + private final int maxConsolidationRequestsPerPayload; private final int maxPendingPartialsPerWithdrawalsSweep; private final int maxPendingDepositsPerEpoch; @@ -47,16 +47,16 @@ public SpecConfigElectraImpl( final UInt64 minPerEpochChurnLimitElectra, final UInt64 minActivationBalance, final UInt64 maxEffectiveBalanceElectra, + final int minSlashingPenaltyQuotientElectra, + final int whistleblowerRewardQuotientElectra, final int pendingDepositsLimit, final int pendingPartialWithdrawalsLimit, final int pendingConsolidationsLimit, - final int minSlashingPenaltyQuotientElectra, - final int whistleblowerRewardQuotientElectra, final int maxAttesterSlashingsElectra, final int maxAttestationsElectra, - final int maxConsolidationRequestsPerPayload, final int maxDepositRequestsPerPayload, final int maxWithdrawalRequestsPerPayload, + final int maxConsolidationRequestsPerPayload, final int maxPendingPartialsPerWithdrawalsSweep, final int maxPendingDepositsPerEpoch) { super(specConfig); @@ -65,16 +65,16 @@ public SpecConfigElectraImpl( this.minPerEpochChurnLimitElectra = minPerEpochChurnLimitElectra; this.minActivationBalance = minActivationBalance; this.maxEffectiveBalanceElectra = maxEffectiveBalanceElectra; + this.minSlashingPenaltyQuotientElectra = minSlashingPenaltyQuotientElectra; + this.whistleblowerRewardQuotientElectra = whistleblowerRewardQuotientElectra; this.pendingDepositsLimit = pendingDepositsLimit; this.pendingPartialWithdrawalsLimit = pendingPartialWithdrawalsLimit; this.pendingConsolidationsLimit = pendingConsolidationsLimit; - this.minSlashingPenaltyQuotientElectra = minSlashingPenaltyQuotientElectra; - this.whistleblowerRewardQuotientElectra = whistleblowerRewardQuotientElectra; this.maxAttesterSlashingsElectra = maxAttesterSlashingsElectra; this.maxAttestationsElectra = maxAttestationsElectra; - this.maxConsolidationRequestsPerPayload = maxConsolidationRequestsPerPayload; this.maxDepositRequestsPerPayload = maxDepositRequestsPerPayload; this.maxWithdrawalRequestsPerPayload = maxWithdrawalRequestsPerPayload; + this.maxConsolidationRequestsPerPayload = maxConsolidationRequestsPerPayload; this.maxPendingPartialsPerWithdrawalsSweep = maxPendingPartialsPerWithdrawalsSweep; this.maxPendingDepositsPerEpoch = maxPendingDepositsPerEpoch; } @@ -105,28 +105,28 @@ public UInt64 getMaxEffectiveBalanceElectra() { } @Override - public int getPendingDepositsLimit() { - return pendingDepositsLimit; + public int getMinSlashingPenaltyQuotientElectra() { + return minSlashingPenaltyQuotientElectra; } @Override - public int getPendingPartialWithdrawalsLimit() { - return pendingPartialWithdrawalsLimit; + public int getWhistleblowerRewardQuotientElectra() { + return whistleblowerRewardQuotientElectra; } @Override - public int getPendingConsolidationsLimit() { - return pendingConsolidationsLimit; + public int getPendingDepositsLimit() { + return pendingDepositsLimit; } @Override - public int getMinSlashingPenaltyQuotientElectra() { - return minSlashingPenaltyQuotientElectra; + public int getPendingPartialWithdrawalsLimit() { + return pendingPartialWithdrawalsLimit; } @Override - public int getWhistleblowerRewardQuotientElectra() { - return whistleblowerRewardQuotientElectra; + public int getPendingConsolidationsLimit() { + return pendingConsolidationsLimit; } @Override @@ -139,11 +139,6 @@ public int getMaxAttestationsElectra() { return maxAttestationsElectra; } - @Override - public int getMaxConsolidationRequestsPerPayload() { - return maxConsolidationRequestsPerPayload; - } - @Override public int getMaxDepositRequestsPerPayload() { return maxDepositRequestsPerPayload; @@ -154,6 +149,11 @@ public int getMaxWithdrawalRequestsPerPayload() { return maxWithdrawalRequestsPerPayload; } + @Override + public int getMaxConsolidationRequestsPerPayload() { + return maxConsolidationRequestsPerPayload; + } + @Override public int getMaxPendingPartialsPerWithdrawalsSweep() { return maxPendingPartialsPerWithdrawalsSweep; @@ -184,16 +184,16 @@ public boolean equals(final Object o) { && Objects.equals(minPerEpochChurnLimitElectra, that.minPerEpochChurnLimitElectra) && Objects.equals(minActivationBalance, that.minActivationBalance) && Objects.equals(maxEffectiveBalanceElectra, that.maxEffectiveBalanceElectra) + && minSlashingPenaltyQuotientElectra == that.minSlashingPenaltyQuotientElectra + && whistleblowerRewardQuotientElectra == that.whistleblowerRewardQuotientElectra && pendingDepositsLimit == that.pendingDepositsLimit && pendingPartialWithdrawalsLimit == that.pendingPartialWithdrawalsLimit && pendingConsolidationsLimit == that.pendingConsolidationsLimit - && minSlashingPenaltyQuotientElectra == that.minSlashingPenaltyQuotientElectra - && whistleblowerRewardQuotientElectra == that.whistleblowerRewardQuotientElectra && maxAttesterSlashingsElectra == that.maxAttesterSlashingsElectra && maxAttestationsElectra == that.maxAttestationsElectra - && maxConsolidationRequestsPerPayload == that.maxConsolidationRequestsPerPayload && maxDepositRequestsPerPayload == that.maxDepositRequestsPerPayload && maxWithdrawalRequestsPerPayload == that.maxWithdrawalRequestsPerPayload + && maxConsolidationRequestsPerPayload == that.maxConsolidationRequestsPerPayload && maxPendingPartialsPerWithdrawalsSweep == that.maxPendingPartialsPerWithdrawalsSweep && maxPendingDepositsPerEpoch == that.maxPendingDepositsPerEpoch; } @@ -207,16 +207,16 @@ public int hashCode() { minPerEpochChurnLimitElectra, minActivationBalance, maxEffectiveBalanceElectra, + minSlashingPenaltyQuotientElectra, + whistleblowerRewardQuotientElectra, pendingDepositsLimit, pendingPartialWithdrawalsLimit, pendingConsolidationsLimit, - minSlashingPenaltyQuotientElectra, - whistleblowerRewardQuotientElectra, maxAttesterSlashingsElectra, maxAttestationsElectra, - maxConsolidationRequestsPerPayload, maxDepositRequestsPerPayload, maxWithdrawalRequestsPerPayload, + maxConsolidationRequestsPerPayload, maxPendingPartialsPerWithdrawalsSweep, maxPendingDepositsPerEpoch); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index 830d3b8eebc..f920fb56cb7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -32,18 +32,19 @@ public class ElectraBuilder implements ForkConfigBuilder attesterSlashingSchema, final SignedBlsToExecutionChangeSchema blsToExecutionChangeSchema, final BlobKzgCommitmentsSchema blobKzgCommitmentsSchema, final ExecutionRequestsSchema executionRequestsSchema, final long maxValidatorsPerAttestation, - final String containerName) { + final String containerName, + final SchemaRegistry schemaRegistry) { return new BeaconBlockBodySchemaElectraImpl( containerName, namedSchema(BlockBodyFields.RANDAO_REVEAL, SszSignatureSchema.INSTANCE), @@ -119,7 +120,7 @@ public static BeaconBlockBodySchemaElectraImpl create( namedSchema( BlockBodyFields.ATTESTER_SLASHINGS, SszListSchema.create( - attesterSlashingSchema.castTypeToAttesterSlashingSchema(), + schemaRegistry.get(SchemaTypes.ATTESTER_SLASHING_SCHEMA), specConfig.getMaxAttesterSlashingsElectra())), namedSchema( BlockBodyFields.ATTESTATIONS, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java index a213df0b520..76c96f447cc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java @@ -37,7 +37,6 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; import tech.pegasys.teku.spec.datastructures.operations.Deposit; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; @@ -47,6 +46,8 @@ import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.datastructures.type.SszSignature; import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; +import tech.pegasys.teku.spec.schemas.registry.SchemaRegistry; +import tech.pegasys.teku.spec.schemas.registry.SchemaTypes; public class BlindedBeaconBlockBodySchemaElectraImpl extends ContainerSchema13< @@ -100,12 +101,12 @@ private BlindedBeaconBlockBodySchemaElectraImpl( public static BlindedBeaconBlockBodySchemaElectraImpl create( final SpecConfigElectra specConfig, - final AttesterSlashingSchema attesterSlashingSchema, final SignedBlsToExecutionChangeSchema signedBlsToExecutionChangeSchema, final BlobKzgCommitmentsSchema blobKzgCommitmentsSchema, final ExecutionRequestsSchema executionRequestsSchema, final long maxValidatorsPerAttestation, - final String containerName) { + final String containerName, + final SchemaRegistry schemaRegistry) { return new BlindedBeaconBlockBodySchemaElectraImpl( containerName, namedSchema(BlockBodyFields.RANDAO_REVEAL, SszSignatureSchema.INSTANCE), @@ -118,7 +119,7 @@ public static BlindedBeaconBlockBodySchemaElectraImpl create( namedSchema( BlockBodyFields.ATTESTER_SLASHINGS, SszListSchema.create( - attesterSlashingSchema.castTypeToAttesterSlashingSchema(), + schemaRegistry.get(SchemaTypes.ATTESTER_SLASHING_SCHEMA), specConfig.getMaxAttesterSlashingsElectra())), namedSchema( BlockBodyFields.ATTESTATIONS, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0.java index b65493d80c1..3f9fa086eb6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0.java @@ -30,13 +30,14 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.common.BlockBodyFields; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; import tech.pegasys.teku.spec.datastructures.operations.Deposit; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttestationPhase0Schema; import tech.pegasys.teku.spec.datastructures.type.SszSignature; import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; +import tech.pegasys.teku.spec.schemas.registry.SchemaRegistry; +import tech.pegasys.teku.spec.schemas.registry.SchemaTypes; public class BeaconBlockBodySchemaPhase0 extends ContainerSchema8< @@ -75,9 +76,9 @@ private BeaconBlockBodySchemaPhase0( public static BeaconBlockBodySchemaPhase0 create( final SpecConfig specConfig, - final AttesterSlashingSchema attesterSlashingSchema, final long maxValidatorsPerAttestation, - final String containerName) { + final String containerName, + final SchemaRegistry schemaRegistry) { return new BeaconBlockBodySchemaPhase0( containerName, namedSchema(BlockBodyFields.RANDAO_REVEAL, SszSignatureSchema.INSTANCE), @@ -90,7 +91,7 @@ public static BeaconBlockBodySchemaPhase0 create( namedSchema( BlockBodyFields.ATTESTER_SLASHINGS, SszListSchema.create( - attesterSlashingSchema.castTypeToAttesterSlashingSchema(), + schemaRegistry.get(SchemaTypes.ATTESTER_SLASHING_SCHEMA), specConfig.getMaxAttesterSlashings())), namedSchema( BlockBodyFields.ATTESTATIONS, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExpectedWithdrawals.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExpectedWithdrawals.java index 9d290a8ded7..d839ba747de 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExpectedWithdrawals.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExpectedWithdrawals.java @@ -248,7 +248,7 @@ private static List getExpectedWithdrawals( withdrawalIndex, UInt64.valueOf(validatorIndex), new Bytes20(validator.getWithdrawalCredentials().slice(12)), - balance.minusMinZero(predicates.getValidatorMaxEffectiveBalance(validator)))); + balance.minusMinZero(miscHelpers.getMaxEffectiveBalance(validator)))); withdrawalIndex = withdrawalIndex.increment(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashing.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashing.java index 34cde303840..7d8467a8e55 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashing.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashing.java @@ -14,16 +14,41 @@ package tech.pegasys.teku.spec.datastructures.operations; import java.util.Set; -import tech.pegasys.teku.infrastructure.ssz.SszContainer; +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -public interface AttesterSlashing extends SszContainer { +public class AttesterSlashing + extends Container2 { + private final IntersectingIndicesCalculator intersectingIndicesCalculator; + + AttesterSlashing(final AttesterSlashingSchema type, final TreeNode backingNode) { + super(type, backingNode); + this.intersectingIndicesCalculator = new IntersectingIndicesCalculator(this); + } + + AttesterSlashing( + final AttesterSlashingSchema schema, + final IndexedAttestation attestation1, + final IndexedAttestation attestation2) { + super(schema, attestation1, attestation2); + this.intersectingIndicesCalculator = new IntersectingIndicesCalculator(this); + } + @Override - AttesterSlashingSchema getSchema(); + public AttesterSlashingSchema getSchema() { + return (AttesterSlashingSchema) super.getSchema(); + } - Set getIntersectingValidatorIndices(); + public Set getIntersectingValidatorIndices() { + return intersectingIndicesCalculator.getIntersectingValidatorIndices(); + } - IndexedAttestation getAttestation1(); + public IndexedAttestation getAttestation1() { + return getField0(); + } - IndexedAttestation getAttestation2(); + public IndexedAttestation getAttestation2() { + return getField1(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingSchema.java index b75ffc5194f..28f0ad29536 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingSchema.java @@ -13,15 +13,29 @@ package tech.pegasys.teku.spec.datastructures.operations; -import tech.pegasys.teku.infrastructure.ssz.schema.SszContainerSchema; +import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.INDEXED_ATTESTATION_SCHEMA; -public interface AttesterSlashingSchema extends SszContainerSchema { +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.schemas.registry.SchemaRegistry; - @SuppressWarnings("unchecked") - default AttesterSlashingSchema castTypeToAttesterSlashingSchema() { - return (AttesterSlashingSchema) this; +public class AttesterSlashingSchema + extends ContainerSchema2 { + + public AttesterSlashingSchema(final String containerName, final SchemaRegistry schemaRegistry) { + super( + containerName, + namedSchema("attestation_1", schemaRegistry.get(INDEXED_ATTESTATION_SCHEMA)), + namedSchema("attestation_2", schemaRegistry.get(INDEXED_ATTESTATION_SCHEMA))); + } + + @Override + public AttesterSlashing createFromBackingNode(final TreeNode node) { + return new AttesterSlashing(this, node); } - AttesterSlashing create( - final IndexedAttestation attestation1, final IndexedAttestation attestation2); + public AttesterSlashing create( + final IndexedAttestation attestation1, final IndexedAttestation attestation2) { + return new AttesterSlashing(this, attestation1, attestation2); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestation.java index ca512cc3faa..09b54890150 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestation.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestation.java @@ -14,17 +14,40 @@ package tech.pegasys.teku.spec.datastructures.operations; import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.SszContainer; import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; -public interface IndexedAttestation extends SszContainer { +public class IndexedAttestation + extends Container3 { + + IndexedAttestation(final IndexedAttestationSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + IndexedAttestation( + final IndexedAttestationSchema schema, + final SszUInt64List attestingIndices, + final AttestationData data, + final BLSSignature signature) { + super(schema, attestingIndices, data, new SszSignature(signature)); + } @Override - IndexedAttestationSchema getSchema(); + public IndexedAttestationSchema getSchema() { + return (IndexedAttestationSchema) super.getSchema(); + } - SszUInt64List getAttestingIndices(); + public SszUInt64List getAttestingIndices() { + return getField0(); + } - AttestationData getData(); + public AttestationData getData() { + return getField1(); + } - BLSSignature getSignature(); + public BLSSignature getSignature() { + return getField2().getSignature(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestationSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestationSchema.java index 2efb82ebaf0..47f8baf0866 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestationSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/IndexedAttestationSchema.java @@ -15,19 +15,38 @@ import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; -import tech.pegasys.teku.infrastructure.ssz.schema.SszContainerSchema; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszSignature; +import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; -public interface IndexedAttestationSchema - extends SszContainerSchema { +public class IndexedAttestationSchema + extends ContainerSchema3 { - @SuppressWarnings("unchecked") - default IndexedAttestationSchema castTypeToIndexedAttestationSchema() { - return (IndexedAttestationSchema) this; + public IndexedAttestationSchema( + final String containerName, final long maxValidatorsPerIndexedAttestation) { + super( + containerName, + namedSchema( + "attesting_indices", SszUInt64ListSchema.create(maxValidatorsPerIndexedAttestation)), + namedSchema("data", AttestationData.SSZ_SCHEMA), + namedSchema("signature", SszSignatureSchema.INSTANCE)); } - SszUInt64ListSchema getAttestingIndicesSchema(); + public SszUInt64ListSchema getAttestingIndicesSchema() { + return (SszUInt64ListSchema) super.getFieldSchema0(); + } + + @Override + public IndexedAttestation createFromBackingNode(final TreeNode node) { + return new IndexedAttestation(this, node); + } - IndexedAttestation create( - SszUInt64List attestingIndices, AttestationData data, BLSSignature signature); + public IndexedAttestation create( + final SszUInt64List attestingIndices, + final AttestationData data, + final BLSSignature signature) { + return new IndexedAttestation(this, attestingIndices, data, signature); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttesterSlashingElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttesterSlashingElectra.java deleted file mode 100644 index f2239f54f78..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttesterSlashingElectra.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.electra; - -import java.util.Set; -import tech.pegasys.teku.infrastructure.ssz.containers.Container2; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IntersectingIndicesCalculator; - -public class AttesterSlashingElectra - extends Container2 - implements AttesterSlashing { - private final IntersectingIndicesCalculator intersectingIndicesCalculator; - - AttesterSlashingElectra(final AttesterSlashingElectraSchema type, final TreeNode backingNode) { - super(type, backingNode); - this.intersectingIndicesCalculator = new IntersectingIndicesCalculator(this); - } - - AttesterSlashingElectra( - final AttesterSlashingElectraSchema schema, - final IndexedAttestation attestation1, - final IndexedAttestation attestation2) { - super(schema, attestation1, attestation2); - this.intersectingIndicesCalculator = new IntersectingIndicesCalculator(this); - } - - @Override - public AttesterSlashingElectraSchema getSchema() { - return (AttesterSlashingElectraSchema) super.getSchema(); - } - - @Override - public Set getIntersectingValidatorIndices() { - return intersectingIndicesCalculator.getIntersectingValidatorIndices(); - } - - @Override - public IndexedAttestation getAttestation1() { - return getField0(); - } - - @Override - public IndexedAttestation getAttestation2() { - return getField1(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttesterSlashingElectraSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttesterSlashingElectraSchema.java deleted file mode 100644 index 38ccc60a349..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/AttesterSlashingElectraSchema.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.electra; - -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; - -public class AttesterSlashingElectraSchema - extends ContainerSchema2 - implements AttesterSlashingSchema { - public AttesterSlashingElectraSchema( - final IndexedAttestationSchema indexedAttestationSchema) { - super( - "AttesterSlashingElectra", - namedSchema("attestation_1", indexedAttestationSchema), - namedSchema("attestation_2", indexedAttestationSchema)); - } - - @Override - public AttesterSlashingElectra createFromBackingNode(final TreeNode node) { - return new AttesterSlashingElectra(this, node); - } - - @Override - public AttesterSlashing create( - final IndexedAttestation attestation1, final IndexedAttestation attestation2) { - return new AttesterSlashingElectra(this, attestation1, attestation2); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/IndexedAttestationElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/IndexedAttestationElectra.java deleted file mode 100644 index 77800c71ae5..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/IndexedAttestationElectra.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.electra; - -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; -import tech.pegasys.teku.infrastructure.ssz.containers.Container3; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.operations.AttestationData; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.type.SszSignature; - -public class IndexedAttestationElectra - extends Container3 - implements IndexedAttestation { - - IndexedAttestationElectra( - final IndexedAttestationElectraSchema type, final TreeNode backingNode) { - super(type, backingNode); - } - - IndexedAttestationElectra( - final IndexedAttestationElectraSchema schema, - final SszUInt64List attestingIndices, - final AttestationData data, - final BLSSignature signature) { - super(schema, attestingIndices, data, new SszSignature(signature)); - } - - @Override - public IndexedAttestationElectraSchema getSchema() { - return (IndexedAttestationElectraSchema) super.getSchema(); - } - - @Override - public SszUInt64List getAttestingIndices() { - return getField0(); - } - - @Override - public AttestationData getData() { - return getField1(); - } - - @Override - public BLSSignature getSignature() { - return getField2().getSignature(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/IndexedAttestationElectraSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/IndexedAttestationElectraSchema.java deleted file mode 100644 index aaeb9fd039b..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/electra/IndexedAttestationElectraSchema.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.electra; - -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.operations.AttestationData; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; -import tech.pegasys.teku.spec.datastructures.type.SszSignature; -import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; - -public class IndexedAttestationElectraSchema - extends ContainerSchema3< - IndexedAttestationElectra, SszUInt64List, AttestationData, SszSignature> - implements IndexedAttestationSchema { - - public IndexedAttestationElectraSchema(final long maxValidatorsPerIndexedAttestation) { - super( - "IndexedAttestationElectra", - namedSchema( - "attesting_indices", SszUInt64ListSchema.create(maxValidatorsPerIndexedAttestation)), - namedSchema("data", AttestationData.SSZ_SCHEMA), - namedSchema("signature", SszSignatureSchema.INSTANCE)); - } - - @Override - public SszUInt64ListSchema getAttestingIndicesSchema() { - return (SszUInt64ListSchema) super.getFieldSchema0(); - } - - @Override - public IndexedAttestationElectra createFromBackingNode(final TreeNode node) { - return new IndexedAttestationElectra(this, node); - } - - @Override - public IndexedAttestation create( - final SszUInt64List attestingIndices, - final AttestationData data, - final BLSSignature signature) { - return new IndexedAttestationElectra(this, attestingIndices, data, signature); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttesterSlashingPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttesterSlashingPhase0.java deleted file mode 100644 index 79d617c51c8..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttesterSlashingPhase0.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.phase0; - -import java.util.Set; -import tech.pegasys.teku.infrastructure.ssz.containers.Container2; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IntersectingIndicesCalculator; - -public class AttesterSlashingPhase0 - extends Container2 - implements AttesterSlashing { - private final IntersectingIndicesCalculator intersectingIndicesCalculator; - - AttesterSlashingPhase0(final AttesterSlashingPhase0Schema type, final TreeNode backingNode) { - super(type, backingNode); - this.intersectingIndicesCalculator = new IntersectingIndicesCalculator(this); - } - - AttesterSlashingPhase0( - final AttesterSlashingPhase0Schema schema, - final IndexedAttestation attestation1, - final IndexedAttestation attestation2) { - super(schema, attestation1, attestation2); - this.intersectingIndicesCalculator = new IntersectingIndicesCalculator(this); - } - - @Override - public AttesterSlashingPhase0Schema getSchema() { - return (AttesterSlashingPhase0Schema) super.getSchema(); - } - - @Override - public Set getIntersectingValidatorIndices() { - return intersectingIndicesCalculator.getIntersectingValidatorIndices(); - } - - @Override - public IndexedAttestation getAttestation1() { - return getField0(); - } - - @Override - public IndexedAttestation getAttestation2() { - return getField1(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttesterSlashingPhase0Schema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttesterSlashingPhase0Schema.java deleted file mode 100644 index fce3ef2108a..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/AttesterSlashingPhase0Schema.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.phase0; - -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; - -public class AttesterSlashingPhase0Schema - extends ContainerSchema2 - implements AttesterSlashingSchema { - public AttesterSlashingPhase0Schema( - final IndexedAttestationSchema indexedAttestationSchema) { - super( - "AttesterSlashingPhase0", - namedSchema("attestation_1", indexedAttestationSchema), - namedSchema("attestation_2", indexedAttestationSchema)); - } - - @Override - public AttesterSlashingPhase0 createFromBackingNode(final TreeNode node) { - return new AttesterSlashingPhase0(this, node); - } - - @Override - public AttesterSlashing create( - final IndexedAttestation attestation1, final IndexedAttestation attestation2) { - return new AttesterSlashingPhase0(this, attestation1, attestation2); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/IndexedAttestationPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/IndexedAttestationPhase0.java deleted file mode 100644 index 023876d2d02..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/IndexedAttestationPhase0.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.phase0; - -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; -import tech.pegasys.teku.infrastructure.ssz.containers.Container3; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.operations.AttestationData; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.type.SszSignature; - -public class IndexedAttestationPhase0 - extends Container3 - implements IndexedAttestation { - - IndexedAttestationPhase0(final IndexedAttestationPhase0Schema type, final TreeNode backingNode) { - super(type, backingNode); - } - - IndexedAttestationPhase0( - final IndexedAttestationPhase0Schema schema, - final SszUInt64List attestingIndices, - final AttestationData data, - final BLSSignature signature) { - super(schema, attestingIndices, data, new SszSignature(signature)); - } - - @Override - public IndexedAttestationPhase0Schema getSchema() { - return (IndexedAttestationPhase0Schema) super.getSchema(); - } - - @Override - public SszUInt64List getAttestingIndices() { - return getField0(); - } - - @Override - public AttestationData getData() { - return getField1(); - } - - @Override - public BLSSignature getSignature() { - return getField2().getSignature(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/IndexedAttestationPhase0Schema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/IndexedAttestationPhase0Schema.java deleted file mode 100644 index 2b07b235793..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/operations/versions/phase0/IndexedAttestationPhase0Schema.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations.versions.phase0; - -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.operations.AttestationData; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; -import tech.pegasys.teku.spec.datastructures.type.SszSignature; -import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; - -public class IndexedAttestationPhase0Schema - extends ContainerSchema3 - implements IndexedAttestationSchema { - - public IndexedAttestationPhase0Schema(final long maxValidatorsPerIndexedAttestation) { - super( - "IndexedAttestationPhase0", - namedSchema( - "attesting_indices", SszUInt64ListSchema.create(maxValidatorsPerIndexedAttestation)), - namedSchema("data", AttestationData.SSZ_SCHEMA), - namedSchema("signature", SszSignatureSchema.INSTANCE)); - } - - @Override - public SszUInt64ListSchema getAttestingIndicesSchema() { - return (SszUInt64ListSchema) super.getFieldSchema0(); - } - - @Override - public IndexedAttestationPhase0 createFromBackingNode(final TreeNode node) { - return new IndexedAttestationPhase0(this, node); - } - - @Override - public IndexedAttestation create( - final SszUInt64List attestingIndices, - final AttestationData data, - final BLSSignature signature) { - return new IndexedAttestationPhase0(this, attestingIndices, data, signature); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/PayloadBuildingAttributes.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/PayloadBuildingAttributes.java index 30a505f1d6e..7ec469600b8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/PayloadBuildingAttributes.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/PayloadBuildingAttributes.java @@ -34,6 +34,8 @@ public class PayloadBuildingAttributes { private final Optional validatorRegistration; private final Optional> withdrawals; private final Bytes32 parentBeaconBlockRoot; + private final Optional targetBlobCount; + private final Optional maximumBlobCount; public PayloadBuildingAttributes( final UInt64 proposerIndex, @@ -44,6 +46,30 @@ public PayloadBuildingAttributes( final Optional validatorRegistration, final Optional> withdrawals, final Bytes32 parentBeaconBlockRoot) { + this( + proposerIndex, + proposalSlot, + timestamp, + prevRandao, + feeRecipient, + validatorRegistration, + withdrawals, + parentBeaconBlockRoot, + Optional.empty(), + Optional.empty()); + } + + public PayloadBuildingAttributes( + final UInt64 proposerIndex, + final UInt64 proposalSlot, + final UInt64 timestamp, + final Bytes32 prevRandao, + final Eth1Address feeRecipient, + final Optional validatorRegistration, + final Optional> withdrawals, + final Bytes32 parentBeaconBlockRoot, + final Optional targetBlobCount, + final Optional maximumBlobCount) { this.proposerIndex = proposerIndex; this.proposalSlot = proposalSlot; this.timestamp = timestamp; @@ -52,6 +78,8 @@ public PayloadBuildingAttributes( this.validatorRegistration = validatorRegistration; this.withdrawals = withdrawals; this.parentBeaconBlockRoot = parentBeaconBlockRoot; + this.targetBlobCount = targetBlobCount; + this.maximumBlobCount = maximumBlobCount; } public UInt64 getProposerIndex() { @@ -78,6 +106,14 @@ public Bytes32 getParentBeaconBlockRoot() { return parentBeaconBlockRoot; } + public Optional getTargetBlobCount() { + return targetBlobCount; + } + + public Optional getMaximumBlobCount() { + return maximumBlobCount; + } + public Optional getValidatorRegistration() { return validatorRegistration; } @@ -107,7 +143,9 @@ public boolean equals(final Object o) { && Objects.equals(feeRecipient, that.feeRecipient) && Objects.equals(validatorRegistration, that.validatorRegistration) && Objects.equals(withdrawals, that.withdrawals) - && Objects.equals(parentBeaconBlockRoot, that.parentBeaconBlockRoot); + && Objects.equals(parentBeaconBlockRoot, that.parentBeaconBlockRoot) + && Objects.equals(targetBlobCount, that.targetBlobCount) + && Objects.equals(maximumBlobCount, that.maximumBlobCount); } @Override @@ -120,7 +158,9 @@ public int hashCode() { feeRecipient, validatorRegistration, withdrawals, - parentBeaconBlockRoot); + parentBeaconBlockRoot, + targetBlobCount, + maximumBlobCount); } @Override @@ -134,6 +174,8 @@ public String toString() { .add("validatorRegistration", validatorRegistration) .add("withdrawals", withdrawals) .add("parentBeaconBlockRoot", parentBeaconBlockRoot) + .add("targetBlobCount", targetBlobCount) + .add("maximumBlobCount", maximumBlobCount) .toString(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index 3ba1a0c6355..5fb68722f14 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -42,6 +42,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.state.ForkData; import tech.pegasys.teku.spec.datastructures.state.SigningData; +import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; @@ -386,6 +387,10 @@ public UInt64 getMaxRequestBlocks() { return UInt64.valueOf(specConfig.getNetworkingConfig().getMaxRequestBlocks()); } + public UInt64 getMaxEffectiveBalance(final Validator validator) { + return specConfig.getMaxEffectiveBalance(); + } + public boolean isFormerDepositMechanismDisabled(final BeaconState state) { return false; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java index 528564150be..f5007fcb110 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/Predicates.java @@ -160,7 +160,7 @@ public boolean isPartiallyWithdrawableValidator(final Validator validator, final public boolean isPartiallyWithdrawableValidatorEth1CredentialsChecked( final Validator validator, final UInt64 balance) { - final UInt64 maxEffectiveBalance = getValidatorMaxEffectiveBalance(validator); + final UInt64 maxEffectiveBalance = specConfig.getMaxEffectiveBalance(); final boolean hasMaxEffectiveBalance = validator.getEffectiveBalance().equals(maxEffectiveBalance); final boolean hasExcessBalance = balance.isGreaterThan(maxEffectiveBalance); @@ -168,10 +168,6 @@ public boolean isPartiallyWithdrawableValidatorEth1CredentialsChecked( return hasMaxEffectiveBalance && hasExcessBalance; } - public UInt64 getValidatorMaxEffectiveBalance(final Validator validator) { - return specConfig.getMaxEffectiveBalance(); - } - public Optional toVersionElectra() { return Optional.empty(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java index 36230c1b63d..5da5b45f023 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/util/AttestationUtil.java @@ -100,7 +100,7 @@ public IndexedAttestation getIndexedAttestation( final BeaconState state, final Attestation attestation) { final List attestingIndices = getAttestingIndices(state, attestation); - final IndexedAttestationSchema indexedAttestationSchema = + final IndexedAttestationSchema indexedAttestationSchema = schemaDefinitions.getIndexedAttestationSchema(); return indexedAttestationSchema.create( diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java index 7815a5f46f3..03c9228ec12 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java @@ -725,8 +725,7 @@ protected Validator getValidatorFromDeposit( FAR_FUTURE_EPOCH, FAR_FUTURE_EPOCH); - final UInt64 maxEffectiveBalance = - beaconStateAccessorsElectra.getValidatorMaxEffectiveBalance(validator); + final UInt64 maxEffectiveBalance = miscHelpers.getMaxEffectiveBalance(validator); final UInt64 validatorEffectiveBalance = amount .minusMinZero(amount.mod(specConfig.getEffectiveBalanceIncrement())) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java index 9aa6cc60272..2bc48d0ba4f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/BeaconStateAccessorsElectra.java @@ -60,7 +60,7 @@ public UInt64 getActivationExitChurnLimit(final BeaconStateElectra state) { */ public UInt64 getActiveBalance(final BeaconState state, final int validatorIndex) { final Validator validator = state.getValidators().get(validatorIndex); - final UInt64 maxEffectiveBalance = predicatesElectra.getValidatorMaxEffectiveBalance(validator); + final UInt64 maxEffectiveBalance = miscHelpers.getMaxEffectiveBalance(validator); final UInt64 validatorBalance = state.getBalances().get(validatorIndex).get(); return validatorBalance.min(maxEffectiveBalance); } @@ -115,17 +115,6 @@ public static BeaconStateAccessorsElectra required( return (BeaconStateAccessorsElectra) beaconStateAccessors; } - /** - * implements get_validator_max_effective_balance state accessor - * - * @param validator - a validator from a state. - * @return the max effective balance for the specified validator based on its withdrawal - * credentials. - */ - public UInt64 getValidatorMaxEffectiveBalance(final Validator validator) { - return predicatesElectra.getValidatorMaxEffectiveBalance(validator); - } - @Override public IntList getNextSyncCommitteeIndices(final BeaconState state) { return getNextSyncCommitteeIndices(state, configElectra.getMaxEffectiveBalanceElectra()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index 3d2f1a0ccd7..2c99e2236cf 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -16,8 +16,10 @@ import it.unimi.dsi.fastutil.ints.IntList; import java.util.Optional; import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; @@ -27,6 +29,8 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; public class MiscHelpersElectra extends MiscHelpersDeneb { + private final SpecConfigElectra specConfigElectra; + private final PredicatesElectra predicatesElectra; public MiscHelpersElectra( final SpecConfigElectra specConfig, @@ -36,6 +40,8 @@ public MiscHelpersElectra( SpecConfigDeneb.required(specConfig), predicates, SchemaDefinitionsDeneb.required(schemaDefinitions)); + this.specConfigElectra = SpecConfigElectra.required(specConfig); + this.predicatesElectra = PredicatesElectra.required(predicates); } public static MiscHelpersElectra required(final MiscHelpers miscHelpers) { @@ -58,6 +64,13 @@ public int computeProposerIndex( SpecConfigElectra.required(specConfig).getMaxEffectiveBalanceElectra()); } + @Override + public UInt64 getMaxEffectiveBalance(final Validator validator) { + return predicatesElectra.hasCompoundingWithdrawalCredential(validator) + ? specConfigElectra.getMaxEffectiveBalanceElectra() + : specConfigElectra.getMinActivationBalance(); + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/PredicatesElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/PredicatesElectra.java index 0e61afee911..61798e22439 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/PredicatesElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/PredicatesElectra.java @@ -55,12 +55,22 @@ public Optional toVersionElectra() { */ @Override public boolean isPartiallyWithdrawableValidator(final Validator validator, final UInt64 balance) { - if (hasExecutionWithdrawalCredential(validator)) { - final UInt64 maxEffectiveBalance = getValidatorMaxEffectiveBalance(validator); - return (balance.isGreaterThan(maxEffectiveBalance) - && maxEffectiveBalance.equals(validator.getEffectiveBalance())); - } - return false; + return hasExecutionWithdrawalCredential(validator) + && isPartiallyWithdrawableValidatorEth1CredentialsChecked(validator, balance); + } + + @Override + public boolean isPartiallyWithdrawableValidatorEth1CredentialsChecked( + final Validator validator, final UInt64 balance) { + final UInt64 maxEffectiveBalance = + hasCompoundingWithdrawalCredential(validator) + ? configElectra.getMaxEffectiveBalanceElectra() + : configElectra.getMinActivationBalance(); + final boolean hasMaxEffectiveBalance = + validator.getEffectiveBalance().equals(maxEffectiveBalance); + final boolean hasExcessBalance = balance.isGreaterThan(maxEffectiveBalance); + + return hasMaxEffectiveBalance && hasExcessBalance; } /** @@ -108,18 +118,4 @@ public boolean hasCompoundingWithdrawalCredential(final Validator validator) { public boolean isCompoundingWithdrawalCredential(final Bytes32 withdrawalCredentials) { return withdrawalCredentials.get(0) == COMPOUNDING_WITHDRAWAL_BYTE; } - - /** - * implements get_validator_max_effective_balance state accessor - * - * @param validator - a validator from a state. - * @return the max effective balance for the specified validator based on its withdrawal - * credentials. - */ - @Override - public UInt64 getValidatorMaxEffectiveBalance(final Validator validator) { - return hasCompoundingWithdrawalCredential(validator) - ? configElectra.getMaxEffectiveBalanceElectra() - : configElectra.getMinActivationBalance(); - } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java index b4d33c0dfb9..2dafe8d7328 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/statetransition/epoch/EpochProcessorElectra.java @@ -153,7 +153,7 @@ protected boolean isEligibleForActivationQueue(final ValidatorStatus status) { @Override protected UInt64 getEffectiveBalanceLimitForValidator(final Validator validator) { - return stateAccessorsElectra.getValidatorMaxEffectiveBalance(validator); + return miscHelpers.getMaxEffectiveBalance(validator); } // process_effective_balance_updates @@ -266,8 +266,7 @@ private Validator getValidatorFromDeposit( FAR_FUTURE_EPOCH, FAR_FUTURE_EPOCH); - final UInt64 maxEffectiveBalance = - stateAccessorsElectra.getValidatorMaxEffectiveBalance(validator); + final UInt64 maxEffectiveBalance = miscHelpers.getMaxEffectiveBalance(validator); final UInt64 validatorEffectiveBalance = amount .minusMinZero(amount.mod(specConfig.getEffectiveBalanceIncrement())) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java index 75ad884784d..58509463de8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java @@ -33,9 +33,7 @@ import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof.AggregateAndProofSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof.SignedAggregateAndProofSchema; import tech.pegasys.teku.spec.datastructures.state.HistoricalBatch.HistoricalBatchSchema; @@ -80,9 +78,9 @@ public interface SchemaDefinitions { AttestationSchema getAttestationSchema(); - IndexedAttestationSchema getIndexedAttestationSchema(); + IndexedAttestationSchema getIndexedAttestationSchema(); - AttesterSlashingSchema getAttesterSlashingSchema(); + AttesterSlashingSchema getAttesterSlashingSchema(); BeaconBlocksByRootRequestMessage.BeaconBlocksByRootRequestMessageSchema getBeaconBlocksByRootRequestMessageSchema(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java index 99f977c999f..8e932b11a93 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsAltair.java @@ -35,9 +35,7 @@ import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof.AggregateAndProofSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof.SignedAggregateAndProofSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.ContributionAndProofSchema; @@ -46,17 +44,16 @@ import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContributionSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeMessageSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttestationPhase0Schema; -import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttesterSlashingPhase0Schema; -import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.IndexedAttestationPhase0Schema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateAltair; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateSchemaAltair; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.MutableBeaconStateAltair; import tech.pegasys.teku.spec.schemas.registry.SchemaRegistry; +import tech.pegasys.teku.spec.schemas.registry.SchemaTypes; public class SchemaDefinitionsAltair extends AbstractSchemaDefinitions { - private final IndexedAttestationSchema indexedAttestationSchema; - private final AttesterSlashingSchema attesterSlashingSchema; + private final IndexedAttestationSchema indexedAttestationSchema; + private final AttesterSlashingSchema attesterSlashingSchema; private final AttestationSchema attestationSchema; private final SignedAggregateAndProofSchema signedAggregateAndProofSchema; private final AggregateAndProofSchema aggregateAndProofSchema; @@ -76,12 +73,8 @@ public class SchemaDefinitionsAltair extends AbstractSchemaDefinitions { public SchemaDefinitionsAltair(final SchemaRegistry schemaRegistry) { super(schemaRegistry); final SpecConfigAltair specConfig = SpecConfigAltair.required(schemaRegistry.getSpecConfig()); - this.indexedAttestationSchema = - new IndexedAttestationPhase0Schema(getMaxValidatorPerAttestation(specConfig)) - .castTypeToIndexedAttestationSchema(); - this.attesterSlashingSchema = - new AttesterSlashingPhase0Schema(indexedAttestationSchema) - .castTypeToAttesterSlashingSchema(); + this.indexedAttestationSchema = schemaRegistry.get(SchemaTypes.INDEXED_ATTESTATION_SCHEMA); + this.attesterSlashingSchema = schemaRegistry.get(SchemaTypes.ATTESTER_SLASHING_SCHEMA); this.attestationSchema = new AttestationPhase0Schema(getMaxValidatorPerAttestation(specConfig)) .castTypeToAttestationSchema(); @@ -90,7 +83,10 @@ public SchemaDefinitionsAltair(final SchemaRegistry schemaRegistry) { this.beaconStateSchema = BeaconStateSchemaAltair.create(specConfig); this.beaconBlockBodySchema = BeaconBlockBodySchemaAltairImpl.create( - specConfig, getMaxValidatorPerAttestation(specConfig), "BeaconBlockBodyAltair"); + specConfig, + getMaxValidatorPerAttestation(specConfig), + "BeaconBlockBodyAltair", + schemaRegistry); this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockAltair"); this.signedBeaconBlockSchema = new SignedBeaconBlockSchema(beaconBlockSchema, "SignedBeaconBlockAltair"); @@ -131,12 +127,12 @@ public AttestationSchema getAttestationSchema() { } @Override - public IndexedAttestationSchema getIndexedAttestationSchema() { + public IndexedAttestationSchema getIndexedAttestationSchema() { return indexedAttestationSchema; } @Override - public AttesterSlashingSchema getAttesterSlashingSchema() { + public AttesterSlashingSchema getAttesterSlashingSchema() { return attesterSlashingSchema; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsBellatrix.java index ce0e6b8b567..6c843e97e62 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsBellatrix.java @@ -62,12 +62,13 @@ public SchemaDefinitionsBellatrix(final SchemaRegistry schemaRegistry) { this.executionPayloadHeaderSchema = beaconStateSchema.getLastExecutionPayloadHeaderSchema(); this.beaconBlockBodySchema = BeaconBlockBodySchemaBellatrixImpl.create( - specConfig, maxValidatorsPerAttestation, "BeaconBlockBodyBellatrix"); + specConfig, maxValidatorsPerAttestation, "BeaconBlockBodyBellatrix", schemaRegistry); this.blindedBeaconBlockBodySchema = BlindedBeaconBlockBodySchemaBellatrixImpl.create( specConfig, maxValidatorsPerAttestation, "BlindedBlockBodyBellatrix", + schemaRegistry, executionPayloadHeaderSchema); this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockBellatrix"); this.blindedBeaconBlockSchema = diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsCapella.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsCapella.java index 61aa76e92af..94b46e09b06 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsCapella.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsCapella.java @@ -87,13 +87,15 @@ public SchemaDefinitionsCapella(final SchemaRegistry schemaRegistry) { specConfig, signedBlsToExecutionChangeSchema, getMaxValidatorPerAttestation(specConfig), - "BeaconBlockBodyCapella"); + "BeaconBlockBodyCapella", + schemaRegistry); this.blindedBeaconBlockBodySchema = BlindedBeaconBlockBodySchemaCapellaImpl.create( specConfig, signedBlsToExecutionChangeSchema, getMaxValidatorPerAttestation(specConfig), - "BlindedBlockBodyCapella"); + "BlindedBlockBodyCapella", + schemaRegistry); this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockCapella"); this.blindedBeaconBlockSchema = new BeaconBlockSchema(blindedBeaconBlockBodySchema, "BlindedBlockCapella"); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsDeneb.java index af9dbb4e5b2..c13f6c3a310 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsDeneb.java @@ -98,14 +98,16 @@ public SchemaDefinitionsDeneb(final SchemaRegistry schemaRegistry) { getSignedBlsToExecutionChangeSchema(), blobKzgCommitmentsSchema, getMaxValidatorPerAttestation(specConfig), - "BeaconBlockBodyDeneb"); + "BeaconBlockBodyDeneb", + schemaRegistry); this.blindedBeaconBlockBodySchema = BlindedBeaconBlockBodySchemaDenebImpl.create( specConfig, getSignedBlsToExecutionChangeSchema(), blobKzgCommitmentsSchema, getMaxValidatorPerAttestation(specConfig), - "BlindedBlockBodyDeneb"); + "BlindedBlockBodyDeneb", + schemaRegistry); this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockDeneb"); this.blindedBeaconBlockSchema = new BeaconBlockSchema(blindedBeaconBlockBodySchema, "BlindedBlockDeneb"); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index 036dcb26cb0..bcd78684b51 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -48,13 +48,7 @@ import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof.AggregateAndProofSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof.SignedAggregateAndProofSchema; -import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttesterSlashingElectraSchema; -import tech.pegasys.teku.spec.datastructures.operations.versions.electra.IndexedAttestationElectraSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; @@ -66,9 +60,6 @@ import tech.pegasys.teku.spec.schemas.registry.SchemaTypes; public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { - - private final IndexedAttestationSchema indexedAttestationSchema; - private final AttesterSlashingSchema attesterSlashingSchema; private final AttestationSchema attestationSchema; private final SignedAggregateAndProofSchema signedAggregateAndProofSchema; private final AggregateAndProofSchema aggregateAndProofSchema; @@ -107,12 +98,6 @@ public SchemaDefinitionsElectra(final SchemaRegistry schemaRegistry) { final SpecConfigElectra specConfig = SpecConfigElectra.required(schemaRegistry.getSpecConfig()); final long maxValidatorsPerAttestation = getMaxValidatorPerAttestation(specConfig); - this.indexedAttestationSchema = - new IndexedAttestationElectraSchema(maxValidatorsPerAttestation) - .castTypeToIndexedAttestationSchema(); - this.attesterSlashingSchema = - new AttesterSlashingElectraSchema(indexedAttestationSchema) - .castTypeToAttesterSlashingSchema(); this.attestationSchema = schemaRegistry.get(SchemaTypes.ATTESTATION_SCHEMA); this.aggregateAndProofSchema = new AggregateAndProofSchema(attestationSchema); @@ -123,21 +108,21 @@ public SchemaDefinitionsElectra(final SchemaRegistry schemaRegistry) { this.beaconBlockBodySchema = BeaconBlockBodySchemaElectraImpl.create( specConfig, - getAttesterSlashingSchema(), getSignedBlsToExecutionChangeSchema(), getBlobKzgCommitmentsSchema(), getExecutionRequestsSchema(), maxValidatorsPerAttestation, - "BeaconBlockBodyElectra"); + "BeaconBlockBodyElectra", + schemaRegistry); this.blindedBeaconBlockBodySchema = BlindedBeaconBlockBodySchemaElectraImpl.create( specConfig, - getAttesterSlashingSchema(), getSignedBlsToExecutionChangeSchema(), getBlobKzgCommitmentsSchema(), getExecutionRequestsSchema(), maxValidatorsPerAttestation, - "BlindedBlockBodyElectra"); + "BlindedBlockBodyElectra", + schemaRegistry); this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockElectra"); this.blindedBeaconBlockSchema = new BeaconBlockSchema(blindedBeaconBlockBodySchema, "BlindedBlockElectra"); @@ -199,16 +184,6 @@ public AttestationSchema getAttestationSchema() { return attestationSchema; } - @Override - public IndexedAttestationSchema getIndexedAttestationSchema() { - return indexedAttestationSchema; - } - - @Override - public AttesterSlashingSchema getAttesterSlashingSchema() { - return attesterSlashingSchema; - } - @Override public BeaconStateSchema getBeaconStateSchema() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsPhase0.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsPhase0.java index 6176c070e32..3352cef3609 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsPhase0.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsPhase0.java @@ -29,21 +29,17 @@ import tech.pegasys.teku.spec.datastructures.operations.AggregateAndProof.AggregateAndProofSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; -import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestation; import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof.SignedAggregateAndProofSchema; -import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttesterSlashingPhase0Schema; -import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.IndexedAttestationPhase0Schema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.BeaconStateSchemaPhase0; import tech.pegasys.teku.spec.schemas.registry.SchemaRegistry; import tech.pegasys.teku.spec.schemas.registry.SchemaTypes; public class SchemaDefinitionsPhase0 extends AbstractSchemaDefinitions { - private final IndexedAttestationSchema indexedAttestationSchema; - private final AttesterSlashingSchema attesterSlashingSchema; + private final IndexedAttestationSchema indexedAttestationSchema; + private final AttesterSlashingSchema attesterSlashingSchema; private final AttestationSchema attestationSchema; private final SignedAggregateAndProofSchema signedAggregateAndProofSchema; private final AggregateAndProofSchema aggregateAndProofSchema; @@ -56,12 +52,8 @@ public class SchemaDefinitionsPhase0 extends AbstractSchemaDefinitions { public SchemaDefinitionsPhase0(final SchemaRegistry schemaRegistry) { super(schemaRegistry); final SpecConfig specConfig = schemaRegistry.getSpecConfig(); - this.indexedAttestationSchema = - new IndexedAttestationPhase0Schema(getMaxValidatorPerAttestation(specConfig)) - .castTypeToIndexedAttestationSchema(); - this.attesterSlashingSchema = - new AttesterSlashingPhase0Schema(indexedAttestationSchema) - .castTypeToAttesterSlashingSchema(); + this.indexedAttestationSchema = schemaRegistry.get(SchemaTypes.INDEXED_ATTESTATION_SCHEMA); + this.attesterSlashingSchema = schemaRegistry.get(SchemaTypes.ATTESTER_SLASHING_SCHEMA); this.attestationSchema = schemaRegistry.get(SchemaTypes.ATTESTATION_SCHEMA); this.aggregateAndProofSchema = new AggregateAndProofSchema(attestationSchema); @@ -70,9 +62,9 @@ public SchemaDefinitionsPhase0(final SchemaRegistry schemaRegistry) { this.beaconBlockBodySchema = BeaconBlockBodySchemaPhase0.create( specConfig, - getAttesterSlashingSchema(), getMaxValidatorPerAttestation(specConfig), - "BeaconBlockBodyPhase0"); + "BeaconBlockBodyPhase0", + schemaRegistry); this.metadataMessageSchema = new MetadataMessageSchemaPhase0(specConfig.getNetworkingConfig()); beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockPhase0"); signedBeaconBlockSchema = @@ -100,12 +92,12 @@ public AttestationSchema getAttestationSchema() { } @Override - public IndexedAttestationSchema getIndexedAttestationSchema() { + public IndexedAttestationSchema getIndexedAttestationSchema() { return indexedAttestationSchema; } @Override - public AttesterSlashingSchema getAttesterSlashingSchema() { + public AttesterSlashingSchema getAttesterSlashingSchema() { return attesterSlashingSchema; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java index 833990c1339..6d17881ffe7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaRegistryBuilder.java @@ -13,11 +13,15 @@ package tech.pegasys.teku.spec.schemas.registry; +import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; import static tech.pegasys.teku.spec.schemas.registry.BaseSchemaProvider.constantProviderBuilder; import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTESTATION_SCHEMA; +import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTESTER_SLASHING_SCHEMA; import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.ATTNETS_ENR_FIELD_SCHEMA; import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.BEACON_BLOCKS_BY_ROOT_REQUEST_MESSAGE_SCHEMA; import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.HISTORICAL_BATCH_SCHEMA; +import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.INDEXED_ATTESTATION_SCHEMA; import static tech.pegasys.teku.spec.schemas.registry.SchemaTypes.SYNCNETS_ENR_FIELD_SCHEMA; import com.google.common.annotations.VisibleForTesting; @@ -30,6 +34,8 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRootRequestMessage.BeaconBlocksByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; +import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.electra.AttestationElectraSchema; import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttestationPhase0Schema; import tech.pegasys.teku.spec.datastructures.state.HistoricalBatch.HistoricalBatchSchema; @@ -47,13 +53,15 @@ public static SchemaRegistryBuilder create() { .addProvider(createSyncnetsENRFieldSchemaProvider()) .addProvider(createBeaconBlocksByRootRequestMessageSchemaProvider()) .addProvider(createHistoricalBatchSchemaProvider()) + .addProvider(createIndexedAttestationSchemaProvider()) + .addProvider(createAttesterSlashingSchemaProvider()) .addProvider(createAttestationSchemaProvider()); } private static SchemaProvider createAttnetsENRFieldSchemaProvider() { return constantProviderBuilder(ATTNETS_ENR_FIELD_SCHEMA) .withCreator( - SpecMilestone.PHASE0, + PHASE0, (registry, specConfig) -> SszBitvectorSchema.create(specConfig.getAttestationSubnetCount())) .build(); @@ -62,7 +70,7 @@ private static SchemaProvider createAttnetsENRFieldSchemaProvider() { private static SchemaProvider createSyncnetsENRFieldSchemaProvider() { return constantProviderBuilder(SYNCNETS_ENR_FIELD_SCHEMA) .withCreator( - SpecMilestone.PHASE0, + PHASE0, (registry, specConfig) -> SszBitvectorSchema.create(NetworkConstants.SYNC_COMMITTEE_SUBNET_COUNT)) .build(); @@ -71,7 +79,7 @@ private static SchemaProvider createSyncnetsENRFieldSchemaProvider() { private static SchemaProvider createBeaconBlocksByRootRequestMessageSchemaProvider() { return constantProviderBuilder(BEACON_BLOCKS_BY_ROOT_REQUEST_MESSAGE_SCHEMA) .withCreator( - SpecMilestone.PHASE0, + PHASE0, (registry, specConfig) -> new BeaconBlocksByRootRequestMessageSchema(specConfig)) .build(); } @@ -79,30 +87,69 @@ private static SchemaProvider createBeaconBlocksByRootRequestMessageSchemaPro private static SchemaProvider createHistoricalBatchSchemaProvider() { return constantProviderBuilder(HISTORICAL_BATCH_SCHEMA) .withCreator( - SpecMilestone.PHASE0, + PHASE0, (registry, specConfig) -> new HistoricalBatchSchema(specConfig.getSlotsPerHistoricalRoot())) .build(); } + private static SchemaProvider createAttesterSlashingSchemaProvider() { + return constantProviderBuilder(ATTESTER_SLASHING_SCHEMA) + .withCreator( + PHASE0, + (registry, specConfig) -> + new AttesterSlashingSchema( + ATTESTER_SLASHING_SCHEMA.getContainerName(registry.getMilestone()), registry)) + .withCreator( + ELECTRA, + (registry, specConfig) -> + new AttesterSlashingSchema( + ATTESTER_SLASHING_SCHEMA.getContainerName(registry.getMilestone()), registry)) + .build(); + } + + private static SchemaProvider createIndexedAttestationSchemaProvider() { + return constantProviderBuilder(INDEXED_ATTESTATION_SCHEMA) + .withCreator( + PHASE0, + (registry, specConfig) -> + new IndexedAttestationSchema( + INDEXED_ATTESTATION_SCHEMA.getContainerName(registry.getMilestone()), + getMaxValidatorPerAttestationPhase0(specConfig))) + .withCreator( + ELECTRA, + (registry, specConfig) -> + new IndexedAttestationSchema( + INDEXED_ATTESTATION_SCHEMA.getContainerName(registry.getMilestone()), + getMaxValidatorPerAttestationElectra(specConfig))) + .build(); + } + private static SchemaProvider> createAttestationSchemaProvider() { return constantProviderBuilder(ATTESTATION_SCHEMA) .withCreator( - SpecMilestone.PHASE0, + PHASE0, (registry, specConfig) -> - new AttestationPhase0Schema(specConfig.getMaxValidatorsPerCommittee()) + new AttestationPhase0Schema(getMaxValidatorPerAttestationPhase0(specConfig)) .castTypeToAttestationSchema()) .withCreator( SpecMilestone.DENEB, (registry, specConfig) -> new AttestationElectraSchema( - (long) specConfig.getMaxValidatorsPerCommittee() - * specConfig.getMaxCommitteesPerSlot(), + getMaxValidatorPerAttestationElectra(specConfig), specConfig.getMaxCommitteesPerSlot()) .castTypeToAttestationSchema()) .build(); } + private static long getMaxValidatorPerAttestationPhase0(final SpecConfig specConfig) { + return specConfig.getMaxValidatorsPerCommittee(); + } + + private static long getMaxValidatorPerAttestationElectra(final SpecConfig specConfig) { + return (long) specConfig.getMaxValidatorsPerCommittee() * specConfig.getMaxCommitteesPerSlot(); + } + public SchemaRegistryBuilder() { this.cache = SchemaCache.createDefault(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java index c1d830df20e..1062b0c6d63 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/registry/SchemaTypes.java @@ -26,6 +26,8 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRootRequestMessage.BeaconBlocksByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationSchema; +import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashingSchema; +import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; import tech.pegasys.teku.spec.datastructures.state.HistoricalBatch.HistoricalBatchSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; @@ -42,6 +44,10 @@ public class SchemaTypes { public static final SchemaId BEACON_BLOCKS_BY_ROOT_REQUEST_MESSAGE_SCHEMA = create("BEACON_BLOCKS_BY_ROOT_REQUEST_MESSAGE_SCHEMA"); + public static final SchemaId ATTESTER_SLASHING_SCHEMA = + create("ATTESTER_SLASHING_SCHEMA"); + public static final SchemaId INDEXED_ATTESTATION_SCHEMA = + create("INDEXED_ATTESTATION_SCHEMA"); public static final SchemaId> ATTESTATION_SCHEMA = create("ATTESTATION_SCHEMA"); diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0Test.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0Test.java index fa23725f3a2..c1613970362 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0Test.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/phase0/BeaconBlockBodySchemaPhase0Test.java @@ -19,8 +19,6 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.datastructures.operations.IndexedAttestationSchema; -import tech.pegasys.teku.spec.datastructures.operations.versions.phase0.AttesterSlashingPhase0Schema; public class BeaconBlockBodySchemaPhase0Test { @@ -28,24 +26,18 @@ public class BeaconBlockBodySchemaPhase0Test { public void create_minimal() { final Spec spec = TestSpecFactory.createMinimalPhase0(); final SpecConfig specConfig = spec.getGenesisSpecConfig(); - final IndexedAttestationSchema indexAttestationSchemaA = - spec.getGenesisSchemaDefinitions().getIndexedAttestationSchema(); - final IndexedAttestationSchema indexAttestationSchemaB = - spec.getGenesisSchemaDefinitions().getIndexedAttestationSchema(); final BeaconBlockBodySchemaPhase0 specA = BeaconBlockBodySchemaPhase0.create( specConfig, - new AttesterSlashingPhase0Schema( - indexAttestationSchemaA.castTypeToIndexedAttestationSchema()), specConfig.getMaxValidatorsPerCommittee(), - "BeaconBlockBodyPhase0"); + "BeaconBlockBodyPhase0", + spec.getGenesisSchemaDefinitions().getSchemaRegistry()); final BeaconBlockBodySchemaPhase0 specB = BeaconBlockBodySchemaPhase0.create( specConfig, - new AttesterSlashingPhase0Schema( - indexAttestationSchemaB.castTypeToIndexedAttestationSchema()), specConfig.getMaxValidatorsPerCommittee(), - "BeaconBlockBodyPhase0"); + "BeaconBlockBodyPhase0", + spec.getGenesisSchemaDefinitions().getSchemaRegistry()); assertThat(specA).isEqualTo(specB); } @@ -54,24 +46,18 @@ public void create_minimal() { public void create_mainnet() { final Spec spec = TestSpecFactory.createMainnetPhase0(); final SpecConfig specConfig = spec.getGenesisSpecConfig(); - final IndexedAttestationSchema indexAttestationSchemaA = - spec.getGenesisSchemaDefinitions().getIndexedAttestationSchema(); - final IndexedAttestationSchema indexAttestationSchemaB = - spec.getGenesisSchemaDefinitions().getIndexedAttestationSchema(); final BeaconBlockBodySchemaPhase0 specA = BeaconBlockBodySchemaPhase0.create( specConfig, - new AttesterSlashingPhase0Schema( - indexAttestationSchemaA.castTypeToIndexedAttestationSchema()), specConfig.getMaxValidatorsPerCommittee(), - "BeaconBlockBodyPhase0"); + "BeaconBlockBodyPhase0", + spec.getGenesisSchemaDefinitions().getSchemaRegistry()); final BeaconBlockBodySchemaPhase0 specB = BeaconBlockBodySchemaPhase0.create( specConfig, - new AttesterSlashingPhase0Schema( - indexAttestationSchemaB.castTypeToIndexedAttestationSchema()), specConfig.getMaxValidatorsPerCommittee(), - "BeaconBlockBodyPhase0"); + "BeaconBlockBodyPhase0", + spec.getGenesisSchemaDefinitions().getSchemaRegistry()); assertThat(specA).isEqualTo(specB); } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingTest.java index 02c2d0342e6..8326d117d3c 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/AttesterSlashingTest.java @@ -26,7 +26,7 @@ class AttesterSlashingTest { private final Spec spec = TestSpecFactory.createDefault(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); - private final AttesterSlashingSchema attesterSlashingSchema = + private final AttesterSlashingSchema attesterSlashingSchema = spec.getGenesisSchemaDefinitions().getAttesterSlashingSchema(); private final IndexedAttestation indexedAttestation1 = dataStructureUtil.randomIndexedAttestation(); diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java index 9cd9376ecfc..86acdd78d47 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java @@ -34,7 +34,6 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.spec.util.DataStructureUtil; @@ -42,20 +41,19 @@ public class MiscHelpersElectraTest { private final Spec spec = TestSpecFactory.createMinimalElectra(); private static final int PROPOSER_INDEX = 3; - private final Predicates predicates = new Predicates(spec.getGenesisSpecConfig()); + private final PredicatesElectra predicatesElectra = + new PredicatesElectra(spec.getGenesisSpecConfig()); private final SchemaDefinitionsElectra schemaDefinitionsElectra = SchemaDefinitionsElectra.required(spec.getGenesisSchemaDefinitions()); private final MiscHelpersElectra miscHelpersElectra = new MiscHelpersElectra( spec.getGenesisSpecConfig().toVersionElectra().orElseThrow(), - predicates, + predicatesElectra, schemaDefinitionsElectra); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); final BeaconStateAccessorsElectra beaconStateAccessors = new BeaconStateAccessorsElectra( - spec.getGenesisSpecConfig(), - new PredicatesElectra(spec.getGenesisSpecConfig()), - miscHelpersElectra); + spec.getGenesisSpecConfig(), predicatesElectra, miscHelpersElectra); private final IntList validatorIndices = IntArrayList.of(1, 2, 3, 4, 5, 6, 7, 0); @@ -98,7 +96,7 @@ public void computeProposerIndexShouldUseMaxEffectiveBalanceElectra() { final SpecConfigElectra specConfigElectra = spy(SpecConfigElectra.required(spec.getGenesisSpecConfig())); final MiscHelpersElectra miscHelpersElectra = - new MiscHelpersElectra(specConfigElectra, predicates, schemaDefinitionsElectra); + new MiscHelpersElectra(specConfigElectra, predicatesElectra, schemaDefinitionsElectra); final BeaconState state = new BeaconStateTestBuilder(dataStructureUtil) 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 55ca1200742..f864a53f41f 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 @@ -1539,7 +1539,7 @@ public IndexedAttestation randomIndexedAttestation(final UInt64... attestingIndi public IndexedAttestation randomIndexedAttestation( final AttestationData data, final UInt64... attestingIndicesInput) { - final IndexedAttestationSchema indexedAttestationSchema = + final IndexedAttestationSchema indexedAttestationSchema = spec.getGenesisSchemaDefinitions().getIndexedAttestationSchema(); final SszUInt64List attestingIndices = indexedAttestationSchema.getAttestingIndicesSchema().of(attestingIndicesInput); @@ -1783,7 +1783,9 @@ public PayloadBuildingAttributes randomPayloadBuildingAttributes( ? Optional.of(randomSignedValidatorRegistration()) : Optional.empty(), randomWithdrawalList(), - randomBytes32()); + randomBytes32(), + Optional.of(randomUInt64()), + Optional.of(randomUInt64())); } public ClientVersion randomClientVersion() { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java index d32f9e6fed3..5f8f459a3cf 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java @@ -238,6 +238,8 @@ private Optional calculatePayloadBuildingAttributes( final Eth1Address feeRecipient = getFeeRecipient(proposerInfo, blockSlot); + // TODO EIP-7742 add targetBlobCount and maximumBlobCount + // (https://github.com/Consensys/teku/issues/8745) return Optional.of( new PayloadBuildingAttributes( proposerIndex, @@ -247,7 +249,9 @@ private Optional calculatePayloadBuildingAttributes( feeRecipient, validatorRegistration, spec.getExpectedWithdrawals(state), - currentHeadBlockRoot)); + currentHeadBlockRoot, + Optional.empty(), + Optional.empty())); } // this function MUST return a fee recipient. diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceTest.java index 6273128322b..fda3089f451 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ForkChoiceTest.java @@ -1339,7 +1339,7 @@ private UInt64 applyAttestationFromValidator( new Checkpoint( spec.computeEpochAtSlot(updatedAttestationSlot), targetBlock.getRoot())), dataStructureUtil.randomSignature())); - final IndexedAttestationSchema indexedAttestationSchema = + final IndexedAttestationSchema indexedAttestationSchema = spec.atSlot(updatedAttestationSlot).getSchemaDefinitions().getIndexedAttestationSchema(); updatedVote.setIndexedAttestation( indexedAttestationSchema.create( diff --git a/fuzz/src/main/java/tech/pegasys/teku/fuzz/input/AttesterSlashingFuzzInput.java b/fuzz/src/main/java/tech/pegasys/teku/fuzz/input/AttesterSlashingFuzzInput.java index e22ca87402a..88d52a86f01 100644 --- a/fuzz/src/main/java/tech/pegasys/teku/fuzz/input/AttesterSlashingFuzzInput.java +++ b/fuzz/src/main/java/tech/pegasys/teku/fuzz/input/AttesterSlashingFuzzInput.java @@ -29,7 +29,7 @@ public class AttesterSlashingFuzzInput createType(final SpecVersion spec) { return ContainerSchema2.create( SszSchema.as(BeaconState.class, spec.getSchemaDefinitions().getBeaconStateSchema()), - spec.getSchemaDefinitions().getAttesterSlashingSchema().castTypeToAttesterSlashingSchema(), + spec.getSchemaDefinitions().getAttesterSlashingSchema(), AttesterSlashingFuzzInput::new); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/AttesterSlashingGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/AttesterSlashingGossipManager.java index c1b6c29b2a3..3eb4167c3b8 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/AttesterSlashingGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/AttesterSlashingGossipManager.java @@ -45,8 +45,7 @@ public AttesterSlashingGossipManager( processor, spec.atEpoch(forkInfo.getFork().getEpoch()) .getSchemaDefinitions() - .getAttesterSlashingSchema() - .castTypeToAttesterSlashingSchema(), + .getAttesterSlashingSchema(), message -> spec.computeEpochAtSlot(message.getAttestation1().getData().getSlot()), spec.getNetworkingConfig(), debugDataDumper); diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java index 64328576af2..f324a202205 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java @@ -27,13 +27,20 @@ import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NO_CONTENT; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.BUILDER_BOOST_FACTOR; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.COMMITTEE_INDEX; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.EPOCH; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.GRAFFITI; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_BROADCAST_VALIDATION; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.RANDAO_REVEAL; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT; import static tech.pegasys.teku.infrastructure.json.JsonUtil.serialize; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; import static tech.pegasys.teku.spec.SpecMilestone.ALTAIR; import static tech.pegasys.teku.spec.SpecMilestone.BELLATRIX; import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; import com.fasterxml.jackson.core.JsonProcessingException; @@ -43,6 +50,7 @@ import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.Set; import okhttp3.mockwebserver.MockResponse; @@ -54,12 +62,15 @@ import org.junit.jupiter.api.TestTemplate; import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; +import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuty; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuty; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSubnetSubscription; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; +import tech.pegasys.teku.infrastructure.json.types.SerializableTypeDefinition; import tech.pegasys.teku.infrastructure.ssz.SszDataAssert; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -69,9 +80,12 @@ import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.operations.SignedAggregateAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContributionSchema; +import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeMessage; +import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeMessageSchema; import tech.pegasys.teku.spec.datastructures.state.Validator; import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.datastructures.validator.SubnetSubscription; @@ -150,15 +164,23 @@ void publishesBlindedBlockSszEncoded() throws InterruptedException { final SignedBeaconBlock signedBeaconBlock = dataStructureUtil.randomSignedBlindedBeaconBlock(); + final BroadcastValidationLevel broadcastValidationLevel = BroadcastValidationLevel.GOSSIP; final SendSignedBlockResult result = okHttpValidatorTypeDefClientWithPreferredSsz.sendSignedBlock( - signedBeaconBlock, BroadcastValidationLevel.GOSSIP); + signedBeaconBlock, broadcastValidationLevel); assertThat(result.isPublished()).isTrue(); final RecordedRequest recordedRequest = mockWebServer.takeRequest(); assertThat(recordedRequest.getBody().readByteArray()) .isEqualTo(signedBeaconBlock.sszSerialize().toArrayUnsafe()); + if (specMilestone.isLessThanOrEqualTo(ALTAIR)) { + assertThat(recordedRequest.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_BLOCK_V2.getPath(emptyMap())); + } else { + assertThat(recordedRequest.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_BLINDED_BLOCK_V2.getPath(emptyMap())); + } assertThat(recordedRequest.getRequestUrl().queryParameter(PARAM_BROADCAST_VALIDATION)) .isEqualTo("gossip"); assertThat(recordedRequest.getHeader(HEADER_CONSENSUS_VERSION)) @@ -186,13 +208,21 @@ void publishesBlindedBlockJsonEncoded() throws InterruptedException, JsonProcess .getSignedBlindedBlockContainerSchema() .getJsonTypeDefinition()); + if (specMilestone.isLessThanOrEqualTo(ALTAIR)) { + assertThat(recordedRequest.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_BLOCK_V2.getPath(emptyMap())); + } else { + assertThat(recordedRequest.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_BLINDED_BLOCK_V2.getPath(emptyMap())); + } + final String actualRequest = recordedRequest.getBody().readUtf8(); assertJsonEquals(actualRequest, expectedRequest); } @TestTemplate - void getsSyncingStatus() { + void getsSyncingStatus() throws InterruptedException { mockWebServer.enqueue( new MockResponse() .setResponseCode(200) @@ -208,6 +238,8 @@ void getsSyncingStatus() { final SyncingStatus result = typeDefClient.getSyncingStatus(); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(result) .satisfies( syncingStatus -> { @@ -216,6 +248,33 @@ void getsSyncingStatus() { assertThat(syncingStatus.isSyncing()).isTrue(); assertThat(syncingStatus.getIsOptimistic()).hasValue(true); }); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.GET_SYNCING_STATUS.getPath(emptyMap())); + } + + @TestTemplate + void getProposerDuties_shouldMakeExpectedRequest() throws InterruptedException { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final UInt64 epoch = dataStructureUtil.randomEpoch(); + typeDefClient.getProposerDuties(epoch); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()) + .isEqualTo( + "/" + ValidatorApiMethod.GET_PROPOSER_DUTIES.getPath(Map.of(EPOCH, epoch.toString()))); + } + + @TestTemplate + void getPeerCount_shouldMakeExpectedRequest() throws InterruptedException { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + typeDefClient.getPeerCount(); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()).contains(ValidatorApiMethod.GET_PEER_COUNT.getPath(emptyMap())); } @TestTemplate @@ -313,7 +372,7 @@ void registerValidators_fallbacksToJsonIfSszNotSupported() throws InterruptedExc mockWebServer.enqueue(new MockResponse().setResponseCode(200)); mockWebServer.enqueue(new MockResponse().setResponseCode(200)); - SszList validatorRegistrations = + final SszList validatorRegistrations = dataStructureUtil.randomSignedValidatorRegistrations(5); sszRegisterValidatorsRequest.submit(validatorRegistrations); @@ -391,7 +450,7 @@ public void postValidators_whenSuccess_returnsResponse() throws JsonProcessingEx final String body = serialize(response, STATE_VALIDATORS_RESPONSE_TYPE); mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(body)); - Optional> result = + final Optional> result = typeDefClient.postStateValidators(List.of("1", "2")); assertThat(result).isPresent(); @@ -433,7 +492,8 @@ public void postSyncDuties_whenSuccess_returnsResponse() final UInt64 epoch = ONE; final IntList validatorIndices = IntList.of(1, 2); - Optional result = typeDefClient.postSyncDuties(epoch, validatorIndices); + final Optional result = + typeDefClient.postSyncDuties(epoch, validatorIndices); final RecordedRequest recordedRequest = mockWebServer.takeRequest(); assertThat(recordedRequest.getPath()).isEqualTo("/eth/v1/validator/duties/sync/" + epoch); @@ -458,7 +518,8 @@ public void postAttesterDuties_whenSuccess_returnsResponse() final UInt64 epoch = ONE; final IntList validatorIndices = IntList.of(1, 2); - Optional result = typeDefClient.postAttesterDuties(epoch, validatorIndices); + final Optional result = + typeDefClient.postAttesterDuties(epoch, validatorIndices); final RecordedRequest recordedRequest = mockWebServer.takeRequest(); assertThat(recordedRequest.getPath()).isEqualTo("/eth/v1/validator/duties/attester/" + epoch); @@ -501,7 +562,7 @@ public void subscribeToPersistentSubnets_makesExpectedRequest() throws Exception typeDefClient.subscribeToPersistentSubnets(subnetSubscriptions); - RecordedRequest request = mockWebServer.takeRequest(); + final RecordedRequest request = mockWebServer.takeRequest(); assertThat(request.getMethod()).isEqualTo("POST"); assertThat(request.getPath()) @@ -563,7 +624,7 @@ public void subscribeToBeaconCommitteeForAggregation_makesExpectedRequest() thro new CommitteeSubscriptionRequest( validatorIndex2, committeeIndex2, committeesAtSlot2, slot2, aggregator2))); - RecordedRequest request = mockWebServer.takeRequest(); + final RecordedRequest request = mockWebServer.takeRequest(); assertThat(request.getMethod()).isEqualTo("POST"); assertThat(request.getPath()) @@ -680,7 +741,7 @@ public void createAggregate_makesExpectedRequest_preElectra() throws Exception { typeDefClient.createAggregate(slot, attestationHashTreeRoot, Optional.empty()); - RecordedRequest request = mockWebServer.takeRequest(); + final RecordedRequest request = mockWebServer.takeRequest(); assertThat(request.getMethod()).isEqualTo("GET"); assertThat(request.getPath()).contains(ValidatorApiMethod.GET_AGGREGATE.getPath(emptyMap())); @@ -700,7 +761,7 @@ public void createAggregate_makesExpectedRequest_postElectra() throws Exception typeDefClient.createAggregate( slot, attestationHashTreeRoot, Optional.of(dataStructureUtil.randomUInt64())); - RecordedRequest request = mockWebServer.takeRequest(); + final RecordedRequest request = mockWebServer.takeRequest(); assertThat(request.getMethod()).isEqualTo("GET"); assertThat(request.getPath()).contains(ValidatorApiMethod.GET_AGGREGATE_V2.getPath(emptyMap())); @@ -709,6 +770,211 @@ public void createAggregate_makesExpectedRequest_postElectra() throws Exception .isEqualTo(attestationHashTreeRoot.toHexString()); } + @TestTemplate + public void sendValidatorsLiveness_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final UInt64 epoch = dataStructureUtil.randomEpoch(); + final List validatorIndices = + List.of(dataStructureUtil.randomValidatorIndex(), dataStructureUtil.randomValidatorIndex()); + + typeDefClient.sendValidatorsLiveness(epoch, validatorIndices); + + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.SEND_VALIDATOR_LIVENESS.getPath( + Map.of(RestApiConstants.EPOCH, epoch.toString()))); + assertThat(request.getBody().readUtf8()) + .isEqualTo("[\"" + validatorIndices.get(0) + "\",\"" + validatorIndices.get(1) + "\"]"); + } + + @TestTemplate + public void sendSyncCommitteeMessages_makesExpectedRequest() throws Exception { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + + final UInt64 epoch = dataStructureUtil.randomEpoch(); + final List syncCommitteeMessages = + List.of( + dataStructureUtil.randomSyncCommitteeMessage(), + dataStructureUtil.randomSyncCommitteeMessage()); + + typeDefClient.sendSyncCommitteeMessages(syncCommitteeMessages); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.SEND_SYNC_COMMITTEE_MESSAGES.getPath( + Map.of(RestApiConstants.EPOCH, epoch.toString()))); + + final String expectedRequestPayloadBody = + serialize( + syncCommitteeMessages, + SerializableTypeDefinition.listOf( + SyncCommitteeMessageSchema.INSTANCE.getJsonTypeDefinition())); + assertThat(request.getBody().readString(StandardCharsets.UTF_8)) + .isEqualTo(expectedRequestPayloadBody); + } + + @TestTemplate + public void createAttestationData_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final UInt64 slot = dataStructureUtil.randomSlot(); + final int committeeIndex = dataStructureUtil.randomPositiveInt(); + + typeDefClient.createAttestationData(slot, committeeIndex); + + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.GET_ATTESTATION_DATA.getPath(emptyMap())); + assertThat(request.getRequestUrl().queryParameter(SLOT)).isEqualTo(slot.toString()); + assertThat(request.getRequestUrl().queryParameter(COMMITTEE_INDEX)) + .isEqualTo(String.valueOf(committeeIndex)); + } + + @TestTemplate + public void createUnsignedBlock_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final UInt64 slot = dataStructureUtil.randomSlot(); + final BLSSignature randaoReveal = dataStructureUtil.randomSignature(); + final Bytes32 graffiti = dataStructureUtil.randomBytes32(); + final UInt64 boostFactor = dataStructureUtil.randomUInt64(); + + typeDefClient.createUnsignedBlock( + slot, randaoReveal, Optional.of(graffiti), Optional.of(boostFactor)); + + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.GET_UNSIGNED_BLOCK_V3.getPath(Map.of(SLOT, slot.toString()))); + assertThat(request.getRequestUrl().queryParameter(RANDAO_REVEAL)) + .isEqualTo(randaoReveal.toString()); + assertThat(request.getRequestUrl().queryParameter(GRAFFITI)).isEqualTo(graffiti.toString()); + assertThat(request.getRequestUrl().queryParameter(BUILDER_BOOST_FACTOR)) + .isEqualTo(boostFactor.toString()); + } + + @TestTemplate + public void sendAggregate_makesExpectedRequest_preElectra() throws Exception { + assumeThat(specMilestone).isLessThan(ELECTRA); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + + final List aggregateAndProofs = + List.of( + dataStructureUtil.randomSignedAggregateAndProof(), + dataStructureUtil.randomSignedAggregateAndProof()); + + typeDefClient.sendAggregateAndProofs(aggregateAndProofs); + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_AGGREGATE_AND_PROOFS.getPath(emptyMap())); + + final String expectedRequestPayloadBody = + serialize( + aggregateAndProofs, + SerializableTypeDefinition.listOf( + spec.getGenesisSchemaDefinitions() + .getSignedAggregateAndProofSchema() + .getJsonTypeDefinition())); + + assertThat(request.getBody().readString(StandardCharsets.UTF_8)) + .isEqualTo(expectedRequestPayloadBody); + } + + @TestTemplate + public void sendAggregate_makesExpectedRequest_postElectra() throws Exception { + assumeThat(specMilestone).isGreaterThanOrEqualTo(ELECTRA); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final List aggregateAndProofs = + List.of( + dataStructureUtil.randomSignedAggregateAndProof(), + dataStructureUtil.randomSignedAggregateAndProof()); + + typeDefClient.sendAggregateAndProofs(aggregateAndProofs); + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_AGGREGATE_AND_PROOFS_V2.getPath(emptyMap())); + + final String expectedRequestPayloadBody = + serialize( + aggregateAndProofs, + SerializableTypeDefinition.listOf( + spec.getGenesisSchemaDefinitions() + .getSignedAggregateAndProofSchema() + .getJsonTypeDefinition())); + + assertThat(request.getBody().readString(StandardCharsets.UTF_8)) + .isEqualTo(expectedRequestPayloadBody); + assertThat(request.getHeader(HEADER_CONSENSUS_VERSION)) + .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); + } + + @TestTemplate + public void sendSignedAttestation_makesExpectedRequest_preElectra() throws Exception { + assumeThat(specMilestone).isLessThan(ELECTRA); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final List attestations = + List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + + typeDefClient.sendSignedAttestations(attestations); + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_ATTESTATION.getPath(emptyMap())); + + final String expectedRequestPayloadBody = + serialize( + attestations, + SerializableTypeDefinition.listOf( + spec.getGenesisSchemaDefinitions().getAttestationSchema().getJsonTypeDefinition())); + + assertThat(request.getBody().readString(StandardCharsets.UTF_8)) + .isEqualTo(expectedRequestPayloadBody); + } + + @TestTemplate + public void sendSignedAttestation_makesExpectedRequest_postElectra() throws Exception { + assumeThat(specMilestone).isGreaterThanOrEqualTo(ELECTRA); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + final List attestations = + List.of(dataStructureUtil.randomAttestation(), dataStructureUtil.randomAttestation()); + + typeDefClient.sendSignedAttestations(attestations); + final RecordedRequest request = mockWebServer.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_ATTESTATION_V2.getPath(emptyMap())); + + final String expectedRequestPayloadBody = + serialize( + attestations, + SerializableTypeDefinition.listOf( + spec.getGenesisSchemaDefinitions().getAttestationSchema().getJsonTypeDefinition())); + + assertThat(request.getBody().readString(StandardCharsets.UTF_8)) + .isEqualTo(expectedRequestPayloadBody); + assertThat(request.getHeader(HEADER_CONSENSUS_VERSION)) + .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); + } + @TestTemplate public void createAggregate_whenBadParameters_throwsIllegalArgumentException() { final Bytes32 attestationHashTreeRoot = Bytes32.random(); diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequestTest.java new file mode 100644 index 00000000000..217749e991a --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequestTest.java @@ -0,0 +1,133 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assumptions.assumeThat; +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_NO_CONTENT; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.BEACON_BLOCK_ROOT; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SUBCOMMITTEE_INDEX; +import static tech.pegasys.teku.infrastructure.json.JsonUtil.serialize; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.Collections; +import java.util.Optional; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncCommitteeContribution; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class CreateSyncCommitteeContributionRequestTest extends AbstractTypeDefRequestTestBase { + + private CreateSyncCommitteeContributionRequest request; + private UInt64 slot; + private int subcommitteeIndex; + private Bytes32 root; + + @BeforeEach + public void setup() { + request = + new CreateSyncCommitteeContributionRequest(mockWebServer.url("/"), okHttpClient, spec); + slot = dataStructureUtil.randomSlot(); + subcommitteeIndex = dataStructureUtil.randomPositiveInt(); + root = dataStructureUtil.randomBytes32(); + } + + @TestTemplate + public void createSyncCommitteeContribution_noRequestAtPhase0() { + assumeThat(specMilestone).isLessThanOrEqualTo(PHASE0); + request.submit(slot, subcommitteeIndex, root); + assertThat(mockWebServer.getRequestCount()).isZero(); + } + + @TestTemplate + public void createSyncCommitteeContribution_makesExpectedRequest() throws Exception { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(slot, subcommitteeIndex, root); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.GET_SYNC_COMMITTEE_CONTRIBUTION.getPath(Collections.emptyMap())); + assertThat(request.getRequestUrl().queryParameter(SLOT)).isEqualTo(slot.toString()); + assertThat(request.getRequestUrl().queryParameter(SUBCOMMITTEE_INDEX)) + .isEqualTo(String.valueOf(subcommitteeIndex)); + assertThat(request.getRequestUrl().queryParameter(BEACON_BLOCK_ROOT)) + .isEqualTo(String.valueOf(root.toHexString())); + } + + @TestTemplate + public void shouldGetSyncCommitteeContribution() throws JsonProcessingException { + assumeThat(specMilestone).isGreaterThan(PHASE0); + final SyncCommitteeContribution response = dataStructureUtil.randomSyncCommitteeContribution(); + final String jsonResponse = + serialize( + response, + spec.getGenesisSchemaDefinitions() + .toVersionAltair() + .orElseThrow() + .getSyncCommitteeContributionSchema() + .getJsonTypeDefinition()); + + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(SC_OK) + .setBody(String.format("{\"data\":%s}", jsonResponse))); + + final Optional maybeSyncCommitteeContribution = + request.submit(slot, subcommitteeIndex, root); + assertThat(maybeSyncCommitteeContribution).isPresent(); + assertThat(maybeSyncCommitteeContribution.get()).isEqualTo(response); + } + + @TestTemplate + void handle400() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(slot, subcommitteeIndex, root)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle404() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND)); + assertThat(request.submit(slot, subcommitteeIndex, root)).isEmpty(); + } + + @TestTemplate + void handle500() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(slot, subcommitteeIndex, root)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequestTest.java new file mode 100644 index 00000000000..d46b00546cf --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequestTest.java @@ -0,0 +1,133 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assumptions.assumeThat; +import static tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorDataBuilder.STATE_VALIDATORS_RESPONSE_TYPE; +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_NO_CONTENT; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_ID; +import static tech.pegasys.teku.infrastructure.json.JsonUtil.serialize; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; +import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.api.response.v1.beacon.ValidatorStatus; +import tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorData; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; +import tech.pegasys.teku.spec.datastructures.state.Validator; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class GetStateValidatorsRequestTest extends AbstractTypeDefRequestTestBase { + + private GetStateValidatorsRequest request; + private List validatorIds; + + @BeforeEach + public void setup() { + request = new GetStateValidatorsRequest(mockWebServer.url("/"), okHttpClient); + validatorIds = + List.of( + dataStructureUtil.randomPublicKey().toHexString(), + dataStructureUtil.randomPublicKey().toHexString()); + } + + @TestTemplate + public void getStateValidatorsRequest_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(validatorIds); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.GET_VALIDATORS.getPath(Collections.emptyMap())); + assertThat(request.getRequestUrl().queryParameter(PARAM_ID)) + .isEqualTo(String.join(",", validatorIds)); + } + + @TestTemplate + public void shouldGetStateValidatorsData() throws JsonProcessingException { + final List expected = + List.of(generateStateValidatorData(), generateStateValidatorData()); + final ObjectAndMetaData> response = + new ObjectAndMetaData<>(expected, specMilestone, false, true, false); + + final String body = serialize(response, STATE_VALIDATORS_RESPONSE_TYPE); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK).setBody(body)); + + final Optional>> maybeStateValidatorsData = + request.submit(validatorIds); + assertThat(maybeStateValidatorsData).isPresent(); + assertThat(maybeStateValidatorsData.get().getData()).isEqualTo(expected); + } + + @TestTemplate + void handle400() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(validatorIds)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle404() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND)); + assertThat(request.submit(validatorIds)).isEmpty(); + } + + @TestTemplate + void handle500() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(validatorIds)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } + + private StateValidatorData generateStateValidatorData() { + final long index = dataStructureUtil.randomLong(); + final Validator validator = + new Validator( + dataStructureUtil.randomPublicKey(), + dataStructureUtil.randomBytes32(), + dataStructureUtil.randomUInt64(), + false, + UInt64.ZERO, + UInt64.ZERO, + FAR_FUTURE_EPOCH, + FAR_FUTURE_EPOCH); + return new StateValidatorData( + UInt64.valueOf(index), + dataStructureUtil.randomUInt64(), + ValidatorStatus.active_ongoing, + validator); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequestTest.java new file mode 100644 index 00000000000..f01c87f9633 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequestTest.java @@ -0,0 +1,91 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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_NO_CONTENT; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.INTEGER_TYPE; + +import java.util.List; +import java.util.Map; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class PostAttesterDutiesRequestTest extends AbstractTypeDefRequestTestBase { + + private PostAttesterDutiesRequest request; + private UInt64 epoch; + private List validatorIndices; + + @BeforeEach + public void setup() { + request = new PostAttesterDutiesRequest(mockWebServer.url("/"), okHttpClient); + epoch = dataStructureUtil.randomEpoch(); + validatorIndices = + List.of( + dataStructureUtil.randomValidatorIndex().intValue(), + dataStructureUtil.randomValidatorIndex().intValue()); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(epoch, validatorIndices); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.GET_ATTESTATION_DUTIES.getPath( + Map.of(RestApiConstants.EPOCH, epoch.toString()))); + final String requestBody = + JsonUtil.serialize(validatorIndices, DeserializableTypeDefinition.listOf(INTEGER_TYPE)); + assertThat(request.getBody().readUtf8()).isEqualTo(requestBody); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(epoch, validatorIndices)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle404() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND)); + assertThat(request.submit(epoch, validatorIndices)).isEmpty(); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(epoch, validatorIndices)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequestTest.java new file mode 100644 index 00000000000..1b0db23b982 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequestTest.java @@ -0,0 +1,90 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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_NO_CONTENT; +import static tech.pegasys.teku.infrastructure.json.types.CoreTypes.INTEGER_TYPE; + +import java.util.List; +import java.util.Map; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class PostSyncDutiesRequestTest extends AbstractTypeDefRequestTestBase { + private PostSyncDutiesRequest request; + private UInt64 epoch; + private List validatorIndices; + + @BeforeEach + public void setup() { + request = new PostSyncDutiesRequest(mockWebServer.url("/"), okHttpClient); + epoch = dataStructureUtil.randomEpoch(); + validatorIndices = + List.of( + dataStructureUtil.randomValidatorIndex().intValue(), + dataStructureUtil.randomValidatorIndex().intValue()); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(epoch, validatorIndices); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.GET_SYNC_COMMITTEE_DUTIES.getPath( + Map.of(RestApiConstants.EPOCH, epoch.toString()))); + final String requestBody = + JsonUtil.serialize(validatorIndices, DeserializableTypeDefinition.listOf(INTEGER_TYPE)); + assertThat(request.getBody().readUtf8()).isEqualTo(requestBody); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(epoch, validatorIndices)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle404() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NOT_FOUND)); + assertThat(request.submit(epoch, validatorIndices)).isEmpty(); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(epoch, validatorIndices)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PrepareBeaconProposersRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PrepareBeaconProposersRequestTest.java new file mode 100644 index 00000000000..0d1cf6dcec2 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/PrepareBeaconProposersRequestTest.java @@ -0,0 +1,85 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; + +import java.util.Collections; +import java.util.List; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.validator.BeaconPreparableProposer; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class PrepareBeaconProposersRequestTest extends AbstractTypeDefRequestTestBase { + private PrepareBeaconProposersRequest request; + private List beaconPreparableProposers; + + @BeforeEach + public void setup() { + request = new PrepareBeaconProposersRequest(mockWebServer.url("/"), okHttpClient); + beaconPreparableProposers = + List.of( + dataStructureUtil.randomBeaconPreparableProposer(), + dataStructureUtil.randomBeaconPreparableProposer()); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + request.submit(beaconPreparableProposers); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.PREPARE_BEACON_PROPOSER.getPath(Collections.emptyMap())); + final String requestBody = + JsonUtil.serialize( + beaconPreparableProposers, + DeserializableTypeDefinition.listOf(BeaconPreparableProposer.SSZ_DATA)); + assertThat(request.getBody().readUtf8()).isEqualTo(requestBody); + } + + @TestTemplate + public void prepareBeaconProposer_noRequestWhenEmptyList() { + request.submit(List.of()); + assertThat(mockWebServer.getRequestCount()).isEqualTo(0); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(beaconPreparableProposers)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(beaconPreparableProposers)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/RegisterValidatorsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/RegisterValidatorsRequestTest.java new file mode 100644 index 00000000000..306bac622c0 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/RegisterValidatorsRequestTest.java @@ -0,0 +1,93 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; + +import java.util.Collections; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.builder.SignedValidatorRegistration; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.spec.schemas.ApiSchemas; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class RegisterValidatorsRequestTest extends AbstractTypeDefRequestTestBase { + + private RegisterValidatorsRequest request; + private SszList validatorRegistrations; + + @BeforeEach + public void setup() { + request = new RegisterValidatorsRequest(mockWebServer.url("/"), okHttpClient, true); + validatorRegistrations = dataStructureUtil.randomSignedValidatorRegistrations(10); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequestAsSsz() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + request.submit(validatorRegistrations); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.REGISTER_VALIDATOR.getPath(Collections.emptyMap())); + final byte[] requestBody = + ApiSchemas.SIGNED_VALIDATOR_REGISTRATIONS_SCHEMA + .sszSerialize(validatorRegistrations) + .toArray(); + assertThat(request.getBody().readByteArray()).isEqualTo(requestBody); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequestAsJson() throws Exception { + request = new RegisterValidatorsRequest(mockWebServer.url("/"), okHttpClient, false); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + request.submit(validatorRegistrations); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.REGISTER_VALIDATOR.getPath(Collections.emptyMap())); + final String requestBody = + JsonUtil.serialize( + validatorRegistrations, + ApiSchemas.SIGNED_VALIDATOR_REGISTRATIONS_SCHEMA.getJsonTypeDefinition()); + assertThat(request.getBody().readUtf8()).isEqualTo(requestBody); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(validatorRegistrations)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(validatorRegistrations)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendAggregatesAndProofsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendAggregatesAndProofsRequestTest.java index 3b568906691..7b2a269cd6b 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendAggregatesAndProofsRequestTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendAggregatesAndProofsRequestTest.java @@ -19,6 +19,7 @@ import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; import com.fasterxml.jackson.core.JsonProcessingException; import java.util.List; @@ -74,6 +75,8 @@ void handle200() throws InterruptedException, JsonProcessingException { .contains(ValidatorApiMethod.SEND_SIGNED_AGGREGATE_AND_PROOFS_V2.getPath(emptyMap())); assertThat(recordedRequest.getHeader(RestApiConstants.HEADER_CONSENSUS_VERSION)) .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); + assertThat(recordedRequest.getHeader(HEADER_CONSENSUS_VERSION)) + .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); } else { assertThat(recordedRequest.getPath()) .contains(ValidatorApiMethod.SEND_SIGNED_AGGREGATE_AND_PROOFS.getPath(emptyMap())); @@ -99,8 +102,7 @@ void handle400() { } @TestTemplate - void shouldUseV2ApiWhenUseAttestationsV2ApisEnabled() - throws InterruptedException, JsonProcessingException { + void shouldUseV2ApiWhenUseAttestationsV2ApisEnabled() throws InterruptedException { this.request = new SendAggregateAndProofsRequest(mockWebServer.url("/"), okHttpClient, true, spec); mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendContributionAndProofsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendContributionAndProofsRequestTest.java new file mode 100644 index 00000000000..03fe13a5b2f --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendContributionAndProofsRequestTest.java @@ -0,0 +1,88 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assumptions.assumeThat; +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_NO_CONTENT; +import static tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition.listOf; +import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; + +import java.util.Collections; +import java.util.List; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.json.JsonUtil; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class SendContributionAndProofsRequestTest extends AbstractTypeDefRequestTestBase { + private SendContributionAndProofsRequest request; + private List contributionAndProofs; + + @BeforeEach + public void setup() { + assumeThat(specMilestone).isGreaterThan(PHASE0); + request = new SendContributionAndProofsRequest(mockWebServer.url("/"), okHttpClient); + contributionAndProofs = + List.of( + dataStructureUtil.randomSignedContributionAndProof(), + dataStructureUtil.randomSignedContributionAndProof()); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(contributionAndProofs); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains(ValidatorApiMethod.SEND_CONTRIBUTION_AND_PROOF.getPath(Collections.emptyMap())); + final String requestBody = + JsonUtil.serialize( + contributionAndProofs, + listOf(contributionAndProofs.getFirst().getSchema().getJsonTypeDefinition())); + assertThat(request.getBody().readUtf8()).isEqualTo(requestBody); + } + + @TestTemplate + void noRequestWhenEmptyList() { + request.submit(List.of()); + assertThat(mockWebServer.getRequestCount()).isZero(); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(contributionAndProofs)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(contributionAndProofs)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedAttestationsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedAttestationsRequestTest.java index 52cee239506..6123a8dfe86 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedAttestationsRequestTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedAttestationsRequestTest.java @@ -117,5 +117,7 @@ void shouldUseV2ApiWhenUseAttestationsV2ApisEnabled() assertThat(recordedRequest.getMethod()).isEqualTo("POST"); assertThat(recordedRequest.getPath()) .contains(ValidatorApiMethod.SEND_SIGNED_ATTESTATION_V2.getPath(emptyMap())); + assertThat(recordedRequest.getHeader(HEADER_CONSENSUS_VERSION)) + .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); } } diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequestTest.java new file mode 100644 index 00000000000..fd5817d4a8e --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequestTest.java @@ -0,0 +1,107 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_BAD_REQUEST; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_INTERNAL_SERVER_ERROR; +import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; + +import java.util.Locale; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; +import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext( + milestone = { + SpecMilestone.BELLATRIX, + SpecMilestone.CAPELLA, + SpecMilestone.DENEB, + SpecMilestone.ELECTRA + }, + network = Eth2Network.MINIMAL) +public class SendSignedBlockRequestTest extends AbstractTypeDefRequestTestBase { + private SignedBlockContainer block; + private SendSignedBlockRequest request; + + @BeforeEach + public void setup() { + request = new SendSignedBlockRequest(spec, mockWebServer.url("/"), okHttpClient, true); + this.block = + specMilestone.isGreaterThanOrEqualTo(SpecMilestone.DENEB) + ? dataStructureUtil.randomSignedBlockContents() + : dataStructureUtil.randomSignedBeaconBlock(); + } + + @TestTemplate + public void shouldIncludeConsensusHeaderInJsonRequest() throws InterruptedException { + request = new SendSignedBlockRequest(spec, mockWebServer.url("/"), okHttpClient, false); + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + + request.submit(block, BroadcastValidationLevel.NOT_REQUIRED); + + final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_BLOCK_V2.getPath(emptyMap())); + assertThat(recordedRequest.getHeader(HEADER_CONSENSUS_VERSION)) + .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); + assertThat(recordedRequest.getHeader("Content-Type")).contains("json"); + } + + @TestTemplate + public void shouldIncludeConsensusHeaderInSszRequest() throws InterruptedException { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_OK)); + + request.submit(block, BroadcastValidationLevel.NOT_REQUIRED); + + final RecordedRequest recordedRequest = mockWebServer.takeRequest(); + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getPath()) + .contains(ValidatorApiMethod.SEND_SIGNED_BLOCK_V2.getPath(emptyMap())); + assertThat(recordedRequest.getHeader(HEADER_CONSENSUS_VERSION)) + .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); + assertThat(recordedRequest.getHeader("Content-Type")).contains("octet"); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(block, BroadcastValidationLevel.NOT_REQUIRED)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue( + new MockResponse() + .setResponseCode(SC_BAD_REQUEST) + .setBody("{\"code\": 400,\"message\": \"z\"}")); + assertThatThrownBy(() -> request.submit(block, BroadcastValidationLevel.NOT_REQUIRED)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageStartingWith("Invalid params response from Beacon Node API"); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSubscribeToSyncCommitteeSubnetsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSubscribeToSyncCommitteeSubnetsRequestTest.java new file mode 100644 index 00000000000..9e04a5b53b4 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSubscribeToSyncCommitteeSubnetsRequestTest.java @@ -0,0 +1,76 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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_NO_CONTENT; + +import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSubnetSubscription; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class SendSubscribeToSyncCommitteeSubnetsRequestTest extends AbstractTypeDefRequestTestBase { + private SendSubscribeToSyncCommitteeSubnetsRequest request; + final Collection subscriptions = + List.of(new SyncCommitteeSubnetSubscription(0, IntSet.of(1), UInt64.ZERO)); + + @BeforeEach + public void setup() { + request = new SendSubscribeToSyncCommitteeSubnetsRequest(mockWebServer.url("/"), okHttpClient); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(subscriptions); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.SUBSCRIBE_TO_SYNC_COMMITTEE_SUBNET.getPath(Collections.emptyMap())); + assertThat(request.getBody().readUtf8()) + .isEqualTo( + "[{\"validator_index\":\"0\",\"sync_committee_indices\":[\"1\"],\"until_epoch\":\"0\"}]"); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(subscriptions)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(subscriptions)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSyncCommitteeMessagesRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSyncCommitteeMessagesRequestTest.java index 98ded9db926..8fc75a6ba49 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSyncCommitteeMessagesRequestTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSyncCommitteeMessagesRequestTest.java @@ -66,6 +66,13 @@ void handle200() throws InterruptedException, JsonProcessingException { .contains(ValidatorApiMethod.SEND_SYNC_COMMITTEE_MESSAGES.getPath(emptyMap())); } + @TestTemplate + void shouldNotMakeRequestWhenEmptyMessages() { + final List response = request.submit(List.of()); + assertThat(response).isEmpty(); + assertThat(mockWebServer.getRequestCount()).isZero(); + } + @TestTemplate void handle400() { mockWebServer.enqueue( diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SubscribeToBeaconCommitteeRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SubscribeToBeaconCommitteeRequestTest.java new file mode 100644 index 00000000000..345c4b86d1d --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SubscribeToBeaconCommitteeRequestTest.java @@ -0,0 +1,92 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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_NO_CONTENT; + +import java.util.Collections; +import java.util.List; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.api.CommitteeSubscriptionRequest; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class SubscribeToBeaconCommitteeRequestTest extends AbstractTypeDefRequestTestBase { + private SubscribeToBeaconCommitteeRequest request; + private List subscriptions; + final int committeeIndex1 = 1; + final int validatorIndex1 = 6; + final UInt64 committeesAtSlot1 = UInt64.valueOf(10); + final UInt64 slot1 = UInt64.valueOf(15); + final boolean aggregator1 = true; + + final int committeeIndex2 = 2; + final int validatorIndex2 = 7; + final UInt64 committeesAtSlot2 = UInt64.valueOf(11); + final UInt64 slot2 = UInt64.valueOf(16); + final boolean aggregator2 = false; + + @BeforeEach + public void setup() { + request = new SubscribeToBeaconCommitteeRequest(mockWebServer.url("/"), okHttpClient); + subscriptions = + List.of( + new CommitteeSubscriptionRequest( + validatorIndex1, committeeIndex1, committeesAtSlot1, slot1, aggregator1), + new CommitteeSubscriptionRequest( + validatorIndex2, committeeIndex2, committeesAtSlot2, slot2, aggregator2)); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(subscriptions); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.SUBSCRIBE_TO_BEACON_COMMITTEE_SUBNET.getPath( + Collections.emptyMap())); + final String expectedBody = + "[{\"validator_index\":\"6\",\"committee_index\":\"1\",\"committees_at_slot\":\"10\",\"slot\":\"15\",\"is_aggregator\":true}," + + "{\"validator_index\":\"7\",\"committee_index\":\"2\",\"committees_at_slot\":\"11\",\"slot\":\"16\",\"is_aggregator\":false}]"; + assertThat(request.getBody().readUtf8()).isEqualTo(expectedBody); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(subscriptions)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(subscriptions)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SubscribeToPersistentSubnetsRequestTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SubscribeToPersistentSubnetsRequestTest.java new file mode 100644 index 00000000000..dfe564dab94 --- /dev/null +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/handlers/SubscribeToPersistentSubnetsRequestTest.java @@ -0,0 +1,75 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.remote.typedef.handlers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +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_NO_CONTENT; + +import java.util.Collections; +import java.util.List; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; +import tech.pegasys.teku.api.exceptions.RemoteServiceNotAvailableException; +import tech.pegasys.teku.spec.TestSpecContext; +import tech.pegasys.teku.spec.datastructures.validator.SubnetSubscription; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; +import tech.pegasys.teku.validator.remote.typedef.AbstractTypeDefRequestTestBase; + +@TestSpecContext(allMilestones = true, network = Eth2Network.MINIMAL) +public class SubscribeToPersistentSubnetsRequestTest extends AbstractTypeDefRequestTestBase { + private SubscribeToPersistentSubnetsRequest request; + private List subnetSubscriptions; + + @BeforeEach + public void setup() { + request = new SubscribeToPersistentSubnetsRequest(mockWebServer.url("/"), okHttpClient); + subnetSubscriptions = + List.of( + new SubnetSubscription( + dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomSlot())); + } + + @TestTemplate + public void postAttesterDuties_makesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + request.submit(subnetSubscriptions); + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()) + .contains( + ValidatorApiMethod.SUBSCRIBE_TO_PERSISTENT_SUBNETS.getPath(Collections.emptyMap())); + assertThat(request.getBody().readUtf8()) + .isEqualTo("[{\"subnet_id\":\"35\",\"unsubscription_slot\":\"24752339414\"}]"); + } + + @TestTemplate + void handle400() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_BAD_REQUEST)); + assertThatThrownBy(() -> request.submit(subnetSubscriptions)) + .isInstanceOf(IllegalArgumentException.class); + } + + @TestTemplate + void handle500() { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_INTERNAL_SERVER_ERROR)); + assertThatThrownBy(() -> request.submit(subnetSubscriptions)) + .isInstanceOf(RemoteServiceNotAvailableException.class); + } +} diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandler.java index b8b396e4058..05e063d5f88 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandler.java @@ -121,10 +121,7 @@ private void handleHeadEvent(final String data) throws JsonProcessingException { private void handleAttesterSlashingEvent(final String data) throws JsonProcessingException { final DeserializableTypeDefinition attesterSlashingTypeDefinition = - spec.getGenesisSchemaDefinitions() - .getAttesterSlashingSchema() - .castTypeToAttesterSlashingSchema() - .getJsonTypeDefinition(); + spec.getGenesisSchemaDefinitions().getAttesterSlashingSchema().getJsonTypeDefinition(); final AttesterSlashing attesterSlashing = JsonUtil.parse(data, attesterSlashingTypeDefinition); validatorTimingChannel.onAttesterSlashing(attesterSlashing); } diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAttestationDataRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAttestationDataRequest.java index 1f0020ecce9..5a62b43ec36 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAttestationDataRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateAttestationDataRequest.java @@ -14,6 +14,8 @@ package tech.pegasys.teku.validator.remote.typedef.handlers; import static tech.pegasys.teku.ethereum.json.types.SharedApiTypes.withDataWrapper; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.COMMITTEE_INDEX; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.SLOT; import java.util.HashMap; import java.util.Map; @@ -33,8 +35,8 @@ public CreateAttestationDataRequest(final HttpUrl baseEndpoint, final OkHttpClie public Optional submit(final UInt64 slot, final int committeeIndex) { final Map queryParams = new HashMap<>(); - queryParams.put("slot", slot.toString()); - queryParams.put("committee_index", Integer.toString(committeeIndex)); + queryParams.put(SLOT, slot.toString()); + queryParams.put(COMMITTEE_INDEX, Integer.toString(committeeIndex)); return get( ValidatorApiMethod.GET_ATTESTATION_DATA, queryParams, diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequest.java index a065d654993..a72d6296c0b 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateSyncCommitteeContributionRequest.java @@ -22,6 +22,7 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; @@ -49,11 +50,11 @@ public Optional submit( .getSyncCommitteeContributionSchema(); final Map queryParams = Map.of( - "slot", + RestApiConstants.SLOT, slot.toString(), - "subcommittee_index", + RestApiConstants.SUBCOMMITTEE_INDEX, Integer.toString(subcommitteeIndex), - "beacon_block_root", + RestApiConstants.BEACON_BLOCK_ROOT, beaconBlockRoot.toHexString()); return get( GET_SYNC_COMMITTEE_CONTRIBUTION, diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequest.java index 0b63c07a6c7..3407b7dce5f 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostAttesterDutiesRequest.java @@ -23,6 +23,7 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.validator.remote.typedef.ResponseHandler; @@ -36,7 +37,7 @@ public Optional submit( final UInt64 epoch, final Collection validatorIndices) { return postJson( GET_ATTESTATION_DUTIES, - Map.of("epoch", epoch.toString()), + Map.of(RestApiConstants.EPOCH, epoch.toString()), validatorIndices.stream().toList(), DeserializableTypeDefinition.listOf(INTEGER_TYPE, 1), new ResponseHandler<>(ATTESTER_DUTIES_RESPONSE_TYPE)); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequest.java index 87daeb9384e..023110760d7 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/PostSyncDutiesRequest.java @@ -24,6 +24,7 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.validator.remote.typedef.ResponseHandler; @@ -36,7 +37,7 @@ public Optional submit( final UInt64 epoch, final Collection validatorIndices) { return postJson( GET_SYNC_COMMITTEE_DUTIES, - Map.of("epoch", epoch.toString()), + Map.of(RestApiConstants.EPOCH, epoch.toString()), validatorIndices.stream().toList(), listOf(INTEGER_TYPE, 1), new ResponseHandler<>(SYNC_COMMITTEE_DUTIES_TYPE)); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequest.java index c36cac6aefa..bb70aa7ef79 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendSignedBlockRequest.java @@ -68,29 +68,31 @@ public SendSignedBlockResult submit( final SchemaDefinitions schemaDefinitions = spec.atSlot(signedBlockContainer.getSlot()).getSchemaDefinitions(); + final SpecMilestone milestone = spec.atSlot(signedBlockContainer.getSlot()).getMilestone(); + final Map headers = + Map.of(HEADER_CONSENSUS_VERSION, milestone.name().toLowerCase(Locale.ROOT)); final DeserializableTypeDefinition typeDefinition = blinded ? schemaDefinitions.getSignedBlindedBlockContainerSchema().getJsonTypeDefinition() : schemaDefinitions.getSignedBlockContainerSchema().getJsonTypeDefinition(); - return preferSszBlockEncoding.get() ? sendSignedBlockAsSszOrFallback( - apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition) + apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition, headers) : sendSignedBlockAsJson( - apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition); + apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition, headers); } private SendSignedBlockResult sendSignedBlockAsSszOrFallback( final ValidatorApiMethod apiMethod, final SignedBlockContainer signedBlockContainer, final BroadcastValidationLevel broadcastValidationLevel, - final DeserializableTypeDefinition typeDefinition) { - final SpecMilestone milestone = spec.atSlot(signedBlockContainer.getSlot()).getMilestone(); + final DeserializableTypeDefinition typeDefinition, + final Map headers) { final SendSignedBlockResult result = - sendSignedBlockAsSsz(apiMethod, signedBlockContainer, broadcastValidationLevel, milestone); + sendSignedBlockAsSsz(apiMethod, signedBlockContainer, broadcastValidationLevel, headers); if (!result.isPublished() && !preferSszBlockEncoding.get()) { return sendSignedBlockAsJson( - apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition); + apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition, headers); } return result; } @@ -99,14 +101,14 @@ private SendSignedBlockResult sendSignedBlockAsSsz( final ValidatorApiMethod apiMethod, final SignedBlockContainer signedBlockContainer, final BroadcastValidationLevel broadcastValidationLevel, - final SpecMilestone milestone) { + final Map headers) { return postOctetStream( apiMethod, emptyMap(), Map.of( PARAM_BROADCAST_VALIDATION, broadcastValidationLevel.name().toLowerCase(Locale.ROOT)), - Map.of(HEADER_CONSENSUS_VERSION, milestone.name().toLowerCase(Locale.ROOT)), + headers, signedBlockContainer.sszSerialize().toArray(), sszResponseHandler) .map(__ -> SendSignedBlockResult.success(signedBlockContainer.getRoot())) @@ -117,14 +119,15 @@ private SendSignedBlockResult sendSignedBlockAsJson( final ValidatorApiMethod apiMethod, final SignedBlockContainer signedBlockContainer, final BroadcastValidationLevel broadcastValidationLevel, - final DeserializableTypeDefinition typeDefinition) { + final DeserializableTypeDefinition typeDefinition, + final Map headers) { return postJson( apiMethod, emptyMap(), Map.of( PARAM_BROADCAST_VALIDATION, broadcastValidationLevel.name().toLowerCase(Locale.ROOT)), - emptyMap(), + headers, signedBlockContainer, typeDefinition, new ResponseHandler<>()) diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java index fde71c9fc44..4daabcb531c 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/SendValidatorLivenessRequest.java @@ -25,6 +25,7 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import tech.pegasys.teku.api.migrated.ValidatorLivenessAtEpoch; +import tech.pegasys.teku.infrastructure.http.RestApiConstants; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.validator.remote.typedef.ResponseHandler; @@ -38,7 +39,7 @@ public Optional> submit( final UInt64 epoch, final List validatorIndices) { return postJson( SEND_VALIDATOR_LIVENESS, - Map.of("epoch", epoch.toString()), + Map.of(RestApiConstants.EPOCH, epoch.toString()), Collections.emptyMap(), Collections.emptyMap(), validatorIndices, diff --git a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandlerTest.java b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandlerTest.java index 50d035d0c42..3838c12355d 100644 --- a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandlerTest.java +++ b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/eventsource/EventSourceHandlerTest.java @@ -84,7 +84,7 @@ void onMessage_shouldHandleHeadEvent() throws Exception { void onMessage_shouldHandleAttesterSlashingEvent() throws Exception { final IndexedAttestation indexedAttestation1 = dataStructureUtil.randomIndexedAttestation(); final IndexedAttestation indexedAttestation2 = dataStructureUtil.randomIndexedAttestation(); - final AttesterSlashingSchema attesterSlashingSchema = + final AttesterSlashingSchema attesterSlashingSchema = spec.getGenesisSchemaDefinitions().getAttesterSlashingSchema(); final AttesterSlashing attesterSlashing = attesterSlashingSchema.create(indexedAttestation1, indexedAttestation2); @@ -92,11 +92,7 @@ void onMessage_shouldHandleAttesterSlashingEvent() throws Exception { handler.onMessage( EventType.attester_slashing.name(), new MessageEvent( - JsonUtil.serialize( - attesterSlashing, - attesterSlashingSchema - .castTypeToAttesterSlashingSchema() - .getJsonTypeDefinition()))); + JsonUtil.serialize(attesterSlashing, attesterSlashingSchema.getJsonTypeDefinition()))); verify(validatorTimingChannel).onAttesterSlashing(eq(attesterSlashing)); verifyNoMoreInteractions(validatorTimingChannel);