diff --git a/CHANGELOG.md b/CHANGELOG.md index 26546074afe..b3e5f63ee40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,10 @@ the [releases page](https://github.com/Consensys/teku/releases). ### Breaking Changes ### Additions and Improvements + - Added metadata fields to `/eth/v1/beacon/blob_sidecars/{block_id}` Beacon API response as per https://github.com/ethereum/beacon-APIs/pull/441 - Added rest api endpoint `/teku/v1/beacon/state/finalized/slot/before/{slot}` to return most recent stored state at or before a specified slot. +- The validator client will start using the `v2` variant of the beacon node block publishing + endpoints. In the cases where the block has been produced in the same beacon node, only equivocation validation will be done instead of the entire gossip validation. ### Bug Fixes diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/RemoteValidatorCompatibilityAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/RemoteValidatorCompatibilityAcceptanceTest.java index 5980eae48be..862291aa32f 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/RemoteValidatorCompatibilityAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/RemoteValidatorCompatibilityAcceptanceTest.java @@ -29,7 +29,7 @@ public class RemoteValidatorCompatibilityAcceptanceTest extends AcceptanceTestBa @Test void shouldRunUpdatedValidatorAgainstOldBeaconNode() throws Exception { - verifyCompatibility(TekuDockerVersion.V23_9_0, TekuDockerVersion.LOCAL_BUILD); + verifyCompatibility(TekuDockerVersion.V24_2_0, TekuDockerVersion.LOCAL_BUILD); } @Test diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuDockerVersion.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuDockerVersion.java index f4dd49451d8..660a9692187 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuDockerVersion.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuDockerVersion.java @@ -16,7 +16,7 @@ public enum TekuDockerVersion { LOCAL_BUILD("develop"), LAST_RELEASE("latest"), - V23_9_0("23.9.0"); + V24_2_0("24.2.0"); private final String version; diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index afa2bb13300..2f462c20d4e 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -21,6 +21,8 @@ import static tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricUtils.startTimer; import static tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricsSteps.CREATE; import static tech.pegasys.teku.spec.config.SpecConfig.GENESIS_SLOT; +import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.EQUIVOCATION; +import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.GOSSIP; import com.google.common.annotations.VisibleForTesting; import it.unimi.dsi.fastutil.ints.IntCollection; @@ -32,6 +34,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.LogManager; @@ -57,6 +60,7 @@ import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.collections.LimitedMap; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; @@ -116,6 +120,9 @@ public class ValidatorApiHandler implements ValidatorApiChannel { */ private static final int DUTY_EPOCH_TOLERANCE = 1; + private final Map createdBlockRootsBySlotCache = + LimitedMap.createSynchronizedLRU(2); + private final BlockProductionAndPublishingPerformanceFactory blockProductionAndPublishingPerformanceFactory; private final ChainDataProvider chainDataProvider; @@ -376,7 +383,12 @@ private SafeFuture> createBlock( requestedBlinded, requestedBuilderBoostFactor, blockProductionPerformance) - .thenApply(Optional::of); + .thenApply( + block -> { + final Bytes32 blockRoot = block.blockContainer().getBlock().getRoot(); + createdBlockRootsBySlotCache.put(slot, blockRoot); + return Optional.of(block); + }); } @Override @@ -636,7 +648,13 @@ public SafeFuture sendSignedBlock( maybeBlindedBlockContainer.getSlot()); return blockPublisher .sendSignedBlock( - maybeBlindedBlockContainer, broadcastValidationLevel, blockPublishingPerformance) + maybeBlindedBlockContainer, + // do only EQUIVOCATION validation when GOSSIP validation has been requested and the + // block has been locally created + broadcastValidationLevel == GOSSIP && isLocallyCreatedBlock(maybeBlindedBlockContainer) + ? EQUIVOCATION + : broadcastValidationLevel, + blockPublishingPerformance) .exceptionally( ex -> { final String reason = getRootCauseMessage(ex); @@ -842,6 +860,13 @@ private List getProposalSlotsForEpoch(final BeaconState state, fin return proposerSlots; } + private boolean isLocallyCreatedBlock(final SignedBlockContainer blockContainer) { + final Bytes32 blockRoot = blockContainer.getSignedBlock().getMessage().getRoot(); + final Bytes32 locallyCreatedBlockRoot = + createdBlockRootsBySlotCache.get(blockContainer.getSlot()); + return Objects.equals(blockRoot, locallyCreatedBlockRoot); + } + @Override public SafeFuture>> getBeaconCommitteeSelectionProof( final List requests) { diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index 8b9f3a2d89d..8dc4ba4959b 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -33,6 +33,8 @@ import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.safeJoin; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO; +import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.EQUIVOCATION; +import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.GOSSIP; import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.NOT_REQUIRED; import static tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult.FailureReason.DOES_NOT_DESCEND_FROM_LATEST_FINALIZED; @@ -892,6 +894,56 @@ public void sendSignedBlock_shouldConvertKnownBlockResult() { assertThat(result).isCompletedWithValue(SendSignedBlockResult.success(block.getRoot())); } + @Test + public void + sendSignedBlock_shouldOnlyDoEquivocationValidationIfBlockIsLocallyCreatedAngGossipValidationRequested() { + // creating a block first in order to cache the block root + final UInt64 newSlot = UInt64.valueOf(25); + final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(newSlot); + final BLSSignature randaoReveal = dataStructureUtil.randomSignature(); + final BlockContainerAndMetaData blockContainerAndMetaData = + dataStructureUtil.randomBlockContainerAndMetaData(newSlot); + + when(chainDataClient.getStateForBlockProduction(newSlot, false)) + .thenReturn(SafeFuture.completedFuture(Optional.of(blockSlotState))); + when(blockFactory.createUnsignedBlock( + blockSlotState, + newSlot, + randaoReveal, + Optional.empty(), + Optional.of(false), + Optional.of(ONE), + BlockProductionPerformance.NOOP)) + .thenReturn(SafeFuture.completedFuture(blockContainerAndMetaData)); + + assertThat( + validatorApiHandler.createUnsignedBlock( + newSlot, randaoReveal, Optional.empty(), Optional.of(false), Optional.of(ONE))) + .isCompleted(); + + final SignedBeaconBlock block = + dataStructureUtil + .getSpec() + .atSlot(newSlot) + .getSchemaDefinitions() + .getSignedBeaconBlockSchema() + .create( + blockContainerAndMetaData.blockContainer().getBlock(), + dataStructureUtil.randomSignature()); + + when(blockImportChannel.importBlock(eq(block), any())) + .thenReturn(prepareBlockImportResult(BlockImportResult.successful(block))); + + // require GOSSIP validation + final SafeFuture result = + validatorApiHandler.sendSignedBlock(block, GOSSIP); + + assertThat(result).isCompletedWithValue(SendSignedBlockResult.success(block.getRoot())); + + // for locally created blocks, the validation level should have been changed to EQUIVOCATION + verify(blockImportChannel).importBlock(block, EQUIVOCATION); + } + @Test public void sendSignedBlock_shouldConvertBlockContentsSuccessfulResult() { setupDeneb(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/validator/BroadcastValidationLevel.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/validator/BroadcastValidationLevel.java index f93cd2507cc..a5a9db821a2 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/validator/BroadcastValidationLevel.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/validator/BroadcastValidationLevel.java @@ -15,6 +15,7 @@ public enum BroadcastValidationLevel { NOT_REQUIRED, + EQUIVOCATION, GOSSIP, CONSENSUS, CONSENSUS_AND_EQUIVOCATION diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidator.java index efb79326e34..f4ec4e7dfdc 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidator.java @@ -58,7 +58,7 @@ enum BroadcastValidationResult { SUCCESS, GOSSIP_FAILURE, CONSENSUS_FAILURE, - FINAL_EQUIVOCATION_FAILURE; + EQUIVOCATION_FAILURE; public boolean isFailure() { return this != SUCCESS; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorImpl.java index 4b9c9b5ea1e..34f9555965d 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorImpl.java @@ -13,10 +13,11 @@ package tech.pegasys.teku.statetransition.validation; +import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.EQUIVOCATION; import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.GOSSIP; import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.NOT_REQUIRED; import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.CONSENSUS_FAILURE; -import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.FINAL_EQUIVOCATION_FAILURE; +import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.EQUIVOCATION_FAILURE; import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.GOSSIP_FAILURE; import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.SUCCESS; @@ -59,10 +60,10 @@ public void onConsensusValidationSucceeded() { @Override public void attachToBlockImport(final SafeFuture blockImportResult) { switch (broadcastValidationLevel) { - case NOT_REQUIRED, GOSSIP: - // GOSSIP validation isn't dependent on block import result, + case NOT_REQUIRED, EQUIVOCATION, GOSSIP: + // EQUIVOCATION/GOSSIP validation isn't dependent on block import result, // so not propagating exceptions to consensusValidationSuccessResult allow blocks\blobs - // to be published even in case block import fails before gossip validation completes + // to be published even in case block import fails before the validation completes return; case CONSENSUS, CONSENSUS_AND_EQUIVOCATION: // Any successful block import will be considered as a consensus validation success, but @@ -87,7 +88,20 @@ private void buildValidationPipeline(final SignedBeaconBlock block) { return; } - // GOSSIP only validation + // EQUIVOCATION only validation + if (broadcastValidationLevel == EQUIVOCATION) { + final BroadcastValidationResult validationResult; + if (isEquivocatingBlock(block)) { + validationResult = EQUIVOCATION_FAILURE; + } else { + validationResult = SUCCESS; + } + broadcastValidationResult.complete(validationResult); + consensusValidationSuccessResult.cancel(true); + return; + } + + // GOSSIP only validation (includes EQUIVOCATION validation) SafeFuture validationPipeline = blockGossipValidator .validate(block, true) @@ -128,7 +142,7 @@ private void buildValidationPipeline(final SignedBeaconBlock block) { return; } - // GOSSIP, CONSENSUS and additional EQUIVOCATION validation + // GOSSIP, CONSENSUS and final EQUIVOCATION validation validationPipeline .thenApply( broadcastValidationResult -> { @@ -138,14 +152,18 @@ private void buildValidationPipeline(final SignedBeaconBlock block) { } // perform final equivocation validation - if (blockGossipValidator - .performBlockEquivocationCheck(block) - .equals(EquivocationCheckResult.EQUIVOCATING_BLOCK_FOR_SLOT_PROPOSER)) { - return FINAL_EQUIVOCATION_FAILURE; + if (isEquivocatingBlock(block)) { + return EQUIVOCATION_FAILURE; } return SUCCESS; }) .propagateTo(broadcastValidationResult); } + + private boolean isEquivocatingBlock(final SignedBeaconBlock block) { + return blockGossipValidator + .performBlockEquivocationCheck(block) + .equals(EquivocationCheckResult.EQUIVOCATING_BLOCK_FOR_SLOT_PROPOSER); + } } diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorTest.java index 3d36ea16fcb..dde34f0d1c7 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockBroadcastValidatorTest.java @@ -17,13 +17,15 @@ 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.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION; +import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.EQUIVOCATION; import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.GOSSIP; import static tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel.NOT_REQUIRED; import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.CONSENSUS_FAILURE; -import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.FINAL_EQUIVOCATION_FAILURE; +import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.EQUIVOCATION_FAILURE; import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.GOSSIP_FAILURE; import static tech.pegasys.teku.statetransition.validation.BlockBroadcastValidator.BroadcastValidationResult.SUCCESS; import static tech.pegasys.teku.statetransition.validation.ValidationResultCode.ValidationResultSubCode.IGNORE_ALREADY_SEEN; @@ -56,6 +58,32 @@ public class BlockBroadcastValidatorTest { final SafeFuture blockImportResult = new SafeFuture<>(); + @Test + public void shouldReturnSuccessWhenValidationIsEquivocationAndBlockIsNotEquivocating() { + when(blockGossipValidator.performBlockEquivocationCheck(block)) + .thenReturn(EquivocationCheckResult.FIRST_BLOCK_FOR_SLOT_PROPOSER); + + prepareBlockBroadcastValidator(EQUIVOCATION); + + assertThat(blockBroadcastValidator.getResult()) + .isCompletedWithValueMatching(result -> result.equals(SUCCESS)); + verify(blockGossipValidator).performBlockEquivocationCheck(block); + verifyNoMoreInteractions(blockGossipValidator); + } + + @Test + public void shouldReturnEquivocationFailureWhenValidationIsEquivocationAndBlockIsEquivocating() { + when(blockGossipValidator.performBlockEquivocationCheck(block)) + .thenReturn(EquivocationCheckResult.EQUIVOCATING_BLOCK_FOR_SLOT_PROPOSER); + + prepareBlockBroadcastValidator(EQUIVOCATION); + + assertThat(blockBroadcastValidator.getResult()) + .isCompletedWithValueMatching(result -> result.equals(EQUIVOCATION_FAILURE)); + verify(blockGossipValidator).performBlockEquivocationCheck(block); + verifyNoMoreInteractions(blockGossipValidator); + } + @Test public void shouldReturnSuccessWhenValidationIsGossipAndGossipValidationReturnsAccept() { when(blockGossipValidator.validate(eq(block), eq(true))) @@ -109,10 +137,19 @@ public void shouldReturnGossipFailureImmediatelyWhenGossipValidationIsNotAccept( if (broadcastValidation == NOT_REQUIRED) { prepareBlockBroadcastValidator(broadcastValidation); + assertThat(blockBroadcastValidator.getResult()) + .isCompletedWithValueMatching(result -> result.equals(SUCCESS)); + verifyNoInteractions(blockGossipValidator); + return; + } + if (broadcastValidation == EQUIVOCATION) { + when(blockGossipValidator.performBlockEquivocationCheck(block)) + .thenReturn(EquivocationCheckResult.FIRST_BLOCK_FOR_SLOT_PROPOSER); + prepareBlockBroadcastValidator(broadcastValidation); assertThat(blockBroadcastValidator.getResult()) .isCompletedWithValueMatching(result -> result.equals(SUCCESS)); - verifyNoMoreInteractions(blockGossipValidator); + verify(blockGossipValidator).performBlockEquivocationCheck(block); return; } @@ -193,7 +230,7 @@ public void shouldReturnFinalEquivocationFailureOnlyForEquivocatingBlocks( result -> { if (equivocationCheckResult.equals( EquivocationCheckResult.EQUIVOCATING_BLOCK_FOR_SLOT_PROPOSER)) { - return result.equals(FINAL_EQUIVOCATION_FAILURE); + return result.equals(EQUIVOCATION_FAILURE); } return result.equals(SUCCESS); }); diff --git a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/BlockProductionDuty.java b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/BlockProductionDuty.java index 5e5513f5699..1913eb7f3ab 100644 --- a/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/BlockProductionDuty.java +++ b/validator/client/src/main/java/tech/pegasys/teku/validator/client/duties/BlockProductionDuty.java @@ -155,7 +155,7 @@ private SafeFuture signBlockContainer( private SafeFuture sendBlock(final SignedBlockContainer signedBlockContainer) { return validatorApiChannel - .sendSignedBlock(signedBlockContainer, BroadcastValidationLevel.NOT_REQUIRED) + .sendSignedBlock(signedBlockContainer, BroadcastValidationLevel.GOSSIP) .thenApply( result -> { if (result.isPublished()) { diff --git a/validator/client/src/test/java/tech/pegasys/teku/validator/client/duties/BlockProductionDutyTest.java b/validator/client/src/test/java/tech/pegasys/teku/validator/client/duties/BlockProductionDutyTest.java index 280816f0ebd..ea9ab265f4b 100644 --- a/validator/client/src/test/java/tech/pegasys/teku/validator/client/duties/BlockProductionDutyTest.java +++ b/validator/client/src/test/java/tech/pegasys/teku/validator/client/duties/BlockProductionDutyTest.java @@ -150,12 +150,12 @@ public void shouldCreateAndPublishBlock(final boolean isBlindedBlocksEnabled) { when(signer.signBlock(unsignedBlock, fork)).thenReturn(completedFuture(blockSignature)); final SignedBeaconBlock signedBlock = dataStructureUtil.signedBlock(unsignedBlock, blockSignature); - when(validatorApiChannel.sendSignedBlock(signedBlock, BroadcastValidationLevel.NOT_REQUIRED)) + when(validatorApiChannel.sendSignedBlock(signedBlock, BroadcastValidationLevel.GOSSIP)) .thenReturn(completedFuture(SendSignedBlockResult.success(signedBlock.getRoot()))); performAndReportDuty(); - verify(validatorApiChannel).sendSignedBlock(signedBlock, BroadcastValidationLevel.NOT_REQUIRED); + verify(validatorApiChannel).sendSignedBlock(signedBlock, BroadcastValidationLevel.GOSSIP); verify(validatorLogger) .dutyCompleted( eq(TYPE), @@ -563,7 +563,7 @@ public void shouldUseBlockV3ToCreateAndPublishBlock(final boolean isBlindedBlock final SignedBeaconBlock signedBlock = dataStructureUtil.signedBlock( blockContainerAndMetaData.blockContainer().getBlock(), blockSignature); - when(validatorApiChannel.sendSignedBlock(signedBlock, BroadcastValidationLevel.NOT_REQUIRED)) + when(validatorApiChannel.sendSignedBlock(signedBlock, BroadcastValidationLevel.GOSSIP)) .thenReturn(completedFuture(SendSignedBlockResult.success(signedBlock.getRoot()))); performAndReportDuty(); @@ -571,7 +571,7 @@ public void shouldUseBlockV3ToCreateAndPublishBlock(final boolean isBlindedBlock .createUnsignedBlock( CAPELLA_SLOT, randaoReveal, Optional.of(graffiti), Optional.empty(), Optional.empty()); - verify(validatorApiChannel).sendSignedBlock(signedBlock, BroadcastValidationLevel.NOT_REQUIRED); + verify(validatorApiChannel).sendSignedBlock(signedBlock, BroadcastValidationLevel.GOSSIP); verify(validatorLogger) .dutyCompleted( eq(TYPE), @@ -636,7 +636,7 @@ public void forDeneb_shouldUseBlockV3ToCreateAndPublishBlockContents() { verify(validatorApiChannel) .sendSignedBlock( - signedBlockContentsArgumentCaptor.capture(), eq(BroadcastValidationLevel.NOT_REQUIRED)); + signedBlockContentsArgumentCaptor.capture(), eq(BroadcastValidationLevel.GOSSIP)); verify(validatorLogger) .dutyCompleted( eq(TYPE), 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 ce59452d114..9a2c3ec4d1b 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,6 +27,7 @@ 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.HEADER_CONSENSUS_VERSION; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_BROADCAST_VALIDATION; import static tech.pegasys.teku.infrastructure.json.JsonUtil.serialize; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; @@ -63,6 +64,7 @@ import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; import tech.pegasys.teku.spec.datastructures.state.Validator; +import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.teku.spec.schemas.ApiSchemas; import tech.pegasys.teku.validator.api.SendSignedBlockResult; @@ -141,13 +143,16 @@ void publishesBlindedBlockSszEncoded() throws InterruptedException { final SignedBeaconBlock signedBeaconBlock = dataStructureUtil.randomSignedBlindedBeaconBlock(); final SendSignedBlockResult result = - okHttpValidatorTypeDefClientWithPreferredSsz.sendSignedBlock(signedBeaconBlock); + okHttpValidatorTypeDefClientWithPreferredSsz.sendSignedBlock( + signedBeaconBlock, BroadcastValidationLevel.GOSSIP); assertThat(result.isPublished()).isTrue(); final RecordedRequest recordedRequest = mockWebServer.takeRequest(); assertThat(recordedRequest.getBody().readByteArray()) .isEqualTo(signedBeaconBlock.sszSerialize().toArrayUnsafe()); + assertThat(recordedRequest.getRequestUrl().queryParameter(PARAM_BROADCAST_VALIDATION)) + .isEqualTo("gossip"); assertThat(recordedRequest.getHeader(HEADER_CONSENSUS_VERSION)) .isEqualTo(specMilestone.name().toLowerCase(Locale.ROOT)); } @@ -159,7 +164,8 @@ void publishesBlindedBlockJsonEncoded() throws InterruptedException, JsonProcess final SignedBeaconBlock signedBeaconBlock = dataStructureUtil.randomSignedBlindedBeaconBlock(); final SendSignedBlockResult result = - okHttpValidatorTypeDefClient.sendSignedBlock(signedBeaconBlock); + okHttpValidatorTypeDefClient.sendSignedBlock( + signedBeaconBlock, BroadcastValidationLevel.GOSSIP); assertThat(result.isPublished()).isTrue(); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java index 88ddb8142bd..a323cfaa34c 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandler.java @@ -262,12 +262,8 @@ public SafeFuture> createUnsignedBlock( public SafeFuture sendSignedBlock( final SignedBlockContainer blockContainer, final BroadcastValidationLevel broadcastValidationLevel) { - // we are not going to use V2 to send blocks. If V1 will be deprecated we won't specify a - // validation level in any case - if (broadcastValidationLevel != BroadcastValidationLevel.NOT_REQUIRED) { - LOG.warn("broadcastValidationLevel has been requested but will be ignored."); - } - return sendRequest(() -> typeDefClient.sendSignedBlock(blockContainer)); + return sendRequest( + () -> typeDefClient.sendSignedBlock(blockContainer, broadcastValidationLevel)); } @Override diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorApiMethod.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorApiMethod.java index e7c8dd8c0b6..d7296cb1f57 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorApiMethod.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/apiclient/ValidatorApiMethod.java @@ -27,7 +27,9 @@ public enum ValidatorApiMethod { GET_UNSIGNED_BLINDED_BLOCK("eth/v1/validator/blinded_blocks/:slot"), GET_UNSIGNED_BLOCK_V3("eth/v3/validator/blocks/:slot"), SEND_SIGNED_BLOCK("eth/v1/beacon/blocks"), + SEND_SIGNED_BLOCK_V2("eth/v2/beacon/blocks"), SEND_SIGNED_BLINDED_BLOCK("eth/v1/beacon/blinded_blocks"), + SEND_SIGNED_BLINDED_BLOCK_V2("eth/v2/beacon/blinded_blocks"), GET_ATTESTATION_DATA("eth/v1/validator/attestation_data"), SEND_SIGNED_ATTESTATION("eth/v1/beacon/pool/attestations"), SEND_SIGNED_VOLUNTARY_EXIT("eth/v1/beacon/pool/voluntary_exits"), diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java index 8dbfb28d453..05755b6ca4d 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClient.java @@ -37,6 +37,7 @@ import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; import tech.pegasys.teku.spec.datastructures.metadata.ObjectAndMetaData; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; +import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.validator.api.SendSignedBlockResult; import tech.pegasys.teku.validator.api.required.SyncingStatus; import tech.pegasys.teku.validator.remote.typedef.handlers.BeaconCommitteeSelectionsRequest; @@ -138,8 +139,10 @@ public Optional postAttesterDuties( return postAttesterDutiesRequest.postAttesterDuties(epoch, validatorIndices); } - public SendSignedBlockResult sendSignedBlock(final SignedBlockContainer blockContainer) { - return sendSignedBlockRequest.sendSignedBlock(blockContainer); + public SendSignedBlockResult sendSignedBlock( + final SignedBlockContainer blockContainer, + final BroadcastValidationLevel broadcastValidationLevel) { + return sendSignedBlockRequest.sendSignedBlock(blockContainer, broadcastValidationLevel); } @Deprecated diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java index 0a17e1cc0a6..1f0ac8de8ba 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java @@ -120,7 +120,21 @@ protected Optional postJson( final TObject requestBodyObj, final SerializableTypeDefinition objectTypeDefinition, final ResponseHandler responseHandler) { + return postJson( + apiMethod, urlParams, emptyMap(), requestBodyObj, objectTypeDefinition, responseHandler); + } + + protected Optional postJson( + final ValidatorApiMethod apiMethod, + final Map urlParams, + final Map queryParams, + final TObject requestBodyObj, + final SerializableTypeDefinition objectTypeDefinition, + final ResponseHandler responseHandler) { final HttpUrl.Builder httpUrlBuilder = urlBuilder(apiMethod, urlParams); + if (queryParams != null && !queryParams.isEmpty()) { + queryParams.forEach(httpUrlBuilder::addQueryParameter); + } final String requestBody; final Request request; try { @@ -142,7 +156,20 @@ protected Optional postOctetStream( final Map headers, final byte[] objectBytes, final ResponseHandler responseHandler) { + return postOctetStream(apiMethod, urlParams, emptyMap(), headers, objectBytes, responseHandler); + } + + protected Optional postOctetStream( + final ValidatorApiMethod apiMethod, + final Map urlParams, + final Map queryParams, + final Map headers, + final byte[] objectBytes, + final ResponseHandler responseHandler) { final HttpUrl.Builder httpUrlBuilder = urlBuilder(apiMethod, urlParams); + if (queryParams != null && !queryParams.isEmpty()) { + queryParams.forEach(httpUrlBuilder::addQueryParameter); + } final Request.Builder builder = requestBuilder(); headers.forEach(builder::addHeader); final Request request = 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 b39d334aad4..f5235edd595 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 @@ -13,12 +13,13 @@ package tech.pegasys.teku.validator.remote.typedef.handlers; +import static java.util.Collections.emptyMap; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_UNSUPPORTED_MEDIA_TYPE; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.HEADER_CONSENSUS_VERSION; -import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SIGNED_BLINDED_BLOCK; -import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SIGNED_BLOCK; +import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_BROADCAST_VALIDATION; +import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SIGNED_BLINDED_BLOCK_V2; +import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.SEND_SIGNED_BLOCK_V2; -import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -31,6 +32,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; +import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.validator.api.SendSignedBlockResult; import tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod; @@ -55,10 +57,13 @@ public SendSignedBlockRequest( this.preferSszBlockEncoding = new AtomicBoolean(preferSszBlockEncoding); } - public SendSignedBlockResult sendSignedBlock(final SignedBlockContainer signedBlockContainer) { + public SendSignedBlockResult sendSignedBlock( + final SignedBlockContainer signedBlockContainer, + final BroadcastValidationLevel broadcastValidationLevel) { final boolean blinded = signedBlockContainer.isBlinded(); - final ValidatorApiMethod apiMethod = blinded ? SEND_SIGNED_BLINDED_BLOCK : SEND_SIGNED_BLOCK; + final ValidatorApiMethod apiMethod = + blinded ? SEND_SIGNED_BLINDED_BLOCK_V2 : SEND_SIGNED_BLOCK_V2; final SchemaDefinitions schemaDefinitions = spec.atSlot(signedBlockContainer.getSlot()).getSchemaDefinitions(); @@ -69,19 +74,23 @@ public SendSignedBlockResult sendSignedBlock(final SignedBlockContainer signedBl : schemaDefinitions.getSignedBlockContainerSchema().getJsonTypeDefinition(); return preferSszBlockEncoding.get() - ? sendSignedBlockAsSszOrFallback(apiMethod, signedBlockContainer, typeDefinition) - : sendSignedBlockAsJson(apiMethod, signedBlockContainer, typeDefinition); + ? sendSignedBlockAsSszOrFallback( + apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition) + : sendSignedBlockAsJson( + apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition); } private SendSignedBlockResult sendSignedBlockAsSszOrFallback( final ValidatorApiMethod apiMethod, final SignedBlockContainer signedBlockContainer, + final BroadcastValidationLevel broadcastValidationLevel, final DeserializableTypeDefinition typeDefinition) { final SpecMilestone milestone = spec.atSlot(signedBlockContainer.getSlot()).getMilestone(); final SendSignedBlockResult result = - sendSignedBlockAsSsz(apiMethod, signedBlockContainer, milestone); + sendSignedBlockAsSsz(apiMethod, signedBlockContainer, broadcastValidationLevel, milestone); if (!result.isPublished() && !preferSszBlockEncoding.get()) { - return sendSignedBlockAsJson(apiMethod, signedBlockContainer, typeDefinition); + return sendSignedBlockAsJson( + apiMethod, signedBlockContainer, broadcastValidationLevel, typeDefinition); } return result; } @@ -89,10 +98,14 @@ private SendSignedBlockResult sendSignedBlockAsSszOrFallback( private SendSignedBlockResult sendSignedBlockAsSsz( final ValidatorApiMethod apiMethod, final SignedBlockContainer signedBlockContainer, + final BroadcastValidationLevel broadcastValidationLevel, final SpecMilestone milestone) { return postOctetStream( apiMethod, - Collections.emptyMap(), + emptyMap(), + Map.of( + PARAM_BROADCAST_VALIDATION, + broadcastValidationLevel.name().toLowerCase(Locale.ROOT)), Map.of(HEADER_CONSENSUS_VERSION, milestone.name().toLowerCase(Locale.ROOT)), signedBlockContainer.sszSerialize().toArray(), sszResponseHandler) @@ -103,10 +116,14 @@ private SendSignedBlockResult sendSignedBlockAsSsz( private SendSignedBlockResult sendSignedBlockAsJson( final ValidatorApiMethod apiMethod, final SignedBlockContainer signedBlockContainer, + final BroadcastValidationLevel broadcastValidationLevel, final DeserializableTypeDefinition typeDefinition) { return postJson( apiMethod, - Collections.emptyMap(), + emptyMap(), + Map.of( + PARAM_BROADCAST_VALIDATION, + broadcastValidationLevel.name().toLowerCase(Locale.ROOT)), signedBlockContainer, typeDefinition, new ResponseHandler<>()) diff --git a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java index 2133e51ce7b..c4bf27b9682 100644 --- a/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java +++ b/validator/remote/src/test/java/tech/pegasys/teku/validator/remote/RemoteValidatorApiHandlerTest.java @@ -582,16 +582,17 @@ public void sendSignedBlock_InvokeApiWithCorrectRequest() { dataStructureUtil.signedBlock(beaconBlock, signature); final SendSignedBlockResult expectedResult = SendSignedBlockResult.success(Bytes32.ZERO); - when(typeDefClient.sendSignedBlock(any())).thenReturn(expectedResult); + when(typeDefClient.sendSignedBlock(any(), any())).thenReturn(expectedResult); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(SignedBeaconBlock.class); final SafeFuture result = - apiHandler.sendSignedBlock(signedBeaconBlock, BroadcastValidationLevel.NOT_REQUIRED); + apiHandler.sendSignedBlock(signedBeaconBlock, BroadcastValidationLevel.GOSSIP); asyncRunner.executeQueuedActions(); - verify(typeDefClient).sendSignedBlock(argumentCaptor.capture()); + verify(typeDefClient) + .sendSignedBlock(argumentCaptor.capture(), eq(BroadcastValidationLevel.GOSSIP)); assertThat(argumentCaptor.getValue()).isEqualTo(signedBeaconBlock); assertThat(result).isCompletedWithValue(expectedResult); }