Skip to content

Commit

Permalink
Use v2 of the block publishing endpoints in VC
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanBratanov committed Jun 4, 2024
1 parent 670fca6 commit bcc288e
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -116,6 +118,9 @@ public class ValidatorApiHandler implements ValidatorApiChannel {
*/
private static final int DUTY_EPOCH_TOLERANCE = 1;

private final Map<UInt64, Bytes32> createdBlockRootsBySlotCache =
LimitedMap.createSynchronizedLRU(2);

private final BlockProductionAndPublishingPerformanceFactory
blockProductionAndPublishingPerformanceFactory;
private final ChainDataProvider chainDataProvider;
Expand Down Expand Up @@ -376,7 +381,12 @@ private SafeFuture<Optional<BlockContainerAndMetaData>> 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
Expand Down Expand Up @@ -636,7 +646,12 @@ public SafeFuture<SendSignedBlockResult> 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);
Expand Down Expand Up @@ -842,6 +857,13 @@ private List<ProposerDuty> 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<Optional<List<BeaconCommitteeSelectionProof>>> getBeaconCommitteeSelectionProof(
final List<BeaconCommitteeSelectionProof> requests) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<SendSignedBlockResult> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private SafeFuture<SignedBlockContainer> signBlockContainer(

private SafeFuture<DutyResult> sendBlock(final SignedBlockContainer signedBlockContainer) {
return validatorApiChannel
.sendSignedBlock(signedBlockContainer, BroadcastValidationLevel.NOT_REQUIRED)
.sendSignedBlock(signedBlockContainer, BroadcastValidationLevel.GOSSIP)
.thenApply(
result -> {
if (result.isPublished()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -563,15 +563,15 @@ 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();
verify(validatorApiChannel)
.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),
Expand Down Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
Expand All @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,8 @@ public SafeFuture<Optional<BlockContainerAndMetaData>> createUnsignedBlock(
public SafeFuture<SendSignedBlockResult> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -138,8 +139,10 @@ public Optional<AttesterDuties> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,21 @@ protected <T, TObject> Optional<T> postJson(
final TObject requestBodyObj,
final SerializableTypeDefinition<TObject> objectTypeDefinition,
final ResponseHandler<T> responseHandler) {
return postJson(
apiMethod, urlParams, emptyMap(), requestBodyObj, objectTypeDefinition, responseHandler);
}

protected <T, TObject> Optional<T> postJson(
final ValidatorApiMethod apiMethod,
final Map<String, String> urlParams,
final Map<String, String> queryParams,
final TObject requestBodyObj,
final SerializableTypeDefinition<TObject> objectTypeDefinition,
final ResponseHandler<T> responseHandler) {
final HttpUrl.Builder httpUrlBuilder = urlBuilder(apiMethod, urlParams);
if (queryParams != null && !queryParams.isEmpty()) {
queryParams.forEach(httpUrlBuilder::addQueryParameter);
}
final String requestBody;
final Request request;
try {
Expand All @@ -142,7 +156,20 @@ protected <T> Optional<T> postOctetStream(
final Map<String, String> headers,
final byte[] objectBytes,
final ResponseHandler<T> responseHandler) {
return postOctetStream(apiMethod, urlParams, emptyMap(), headers, objectBytes, responseHandler);
}

protected <T> Optional<T> postOctetStream(
final ValidatorApiMethod apiMethod,
final Map<String, String> urlParams,
final Map<String, String> queryParams,
final Map<String, String> headers,
final byte[] objectBytes,
final ResponseHandler<T> 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 =
Expand Down
Loading

0 comments on commit bcc288e

Please sign in to comment.