From bcc288ed634750d436dcea78c0aeb238dd5ea3a0 Mon Sep 17 00:00:00 2001 From: Stefan Bratanov Date: Tue, 4 Jun 2024 12:21:35 +0100 Subject: [PATCH] Use v2 of the block publishing endpoints in VC --- CHANGELOG.md | 1 + .../coordinator/ValidatorApiHandler.java | 26 +++++++++- .../coordinator/ValidatorApiHandlerTest.java | 50 +++++++++++++++++++ .../client/duties/BlockProductionDuty.java | 2 +- .../duties/BlockProductionDutyTest.java | 10 ++-- .../OkHttpValidatorTypeDefClientTest.java | 10 +++- .../remote/RemoteValidatorApiHandler.java | 8 +-- .../remote/apiclient/ValidatorApiMethod.java | 2 + .../typedef/OkHttpValidatorTypeDefClient.java | 7 ++- .../handlers/AbstractTypeDefRequest.java | 27 ++++++++++ .../handlers/SendSignedBlockRequest.java | 39 +++++++++++---- .../remote/RemoteValidatorApiHandlerTest.java | 7 +-- 12 files changed, 157 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26546074afe..4263fe7f33d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,5 +14,6 @@ the [releases page](https://github.com/Consensys/teku/releases). ### 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 which perform an additional gossip validation unless the block has been produced in the same beacon node. ### Bug Fixes 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..c3f96b14b3f 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 @@ -32,6 +32,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 +58,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 +118,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 +381,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 +646,12 @@ public SafeFuture sendSignedBlock( maybeBlindedBlockContainer.getSlot()); return blockPublisher .sendSignedBlock( - maybeBlindedBlockContainer, broadcastValidationLevel, blockPublishingPerformance) + maybeBlindedBlockContainer, + // no validation required for locally created blocks + isLocallyCreatedBlock(maybeBlindedBlockContainer) + ? BroadcastValidationLevel.NOT_REQUIRED + : broadcastValidationLevel, + blockPublishingPerformance) .exceptionally( ex -> { final String reason = getRootCauseMessage(ex); @@ -842,6 +857,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..c5e2f0d0435 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,7 @@ 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.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 +893,55 @@ public void sendSignedBlock_shouldConvertKnownBlockResult() { assertThat(result).isCompletedWithValue(SendSignedBlockResult.success(block.getRoot())); } + @Test + public void sendSignedBlock_shouldNotRequireValidationIfBlockIsLocallyCreated() { + // 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 NOT_REQUIRED + verify(blockImportChannel).importBlock(block, NOT_REQUIRED); + } + @Test public void sendSignedBlock_shouldConvertBlockContentsSuccessfulResult() { setupDeneb(); 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); }