diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java index 5e59f7427fa..7b2de4e9f92 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java @@ -49,7 +49,7 @@ import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadResult; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsBuilderElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -64,7 +64,6 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.statetransition.OperationPool; import tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool; import tech.pegasys.teku.statetransition.attestation.AttestationForkChecker; @@ -240,19 +239,11 @@ private SafeFuture setExecutionData( requestedBuilderBoostFactor, blockProductionPerformance); - // TODO Update as part of Electra Engine API updates - // (https://github.com/Consensys/teku/issues/8620) - if (bodyBuilder.supportsExecutionRequests()) { - bodyBuilder.executionRequests( - new ExecutionRequestsBuilderElectra( - SchemaDefinitionsElectra.required(schemaDefinitions).getExecutionRequestsSchema()) - .build()); - } - return SafeFuture.allOf( cacheExecutionPayloadValue(executionPayloadResult, blockSlotState), setPayloadOrPayloadHeader(bodyBuilder, executionPayloadResult), - setKzgCommitments(bodyBuilder, schemaDefinitions, executionPayloadResult)); + setKzgCommitments(bodyBuilder, schemaDefinitions, executionPayloadResult), + setExecutionRequests(bodyBuilder, executionPayloadResult)); } private SafeFuture cacheExecutionPayloadValue( @@ -350,6 +341,29 @@ private SszList getBlobKzgCommitmentsFromBuilderFlow( .orElseThrow(); } + private SafeFuture setExecutionRequests( + final BeaconBlockBodyBuilder bodyBuilder, + final ExecutionPayloadResult executionPayloadResult) { + if (!bodyBuilder.supportsExecutionRequests()) { + return SafeFuture.COMPLETE; + } + + final SafeFuture executionRequestsFuture; + if (executionPayloadResult.isFromLocalFlow()) { + executionRequestsFuture = + executionPayloadResult + .getExecutionRequestsFromLocalFlow() + .orElseThrow() + .thenApply(Optional::orElseThrow); + } else { + // TODO Add support for builder flow in Electra + // (https://github.com/Consensys/teku/issues/8624) + executionRequestsFuture = SafeFuture.completedFuture(null); + } + + return executionRequestsFuture.thenAccept(bodyBuilder::executionRequests); + } + public Consumer createBlockUnblinderSelector( final BlockPublishingPerformance blockPublishingPerformance) { return bodyUnblinder -> { diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java index 76cce2892eb..ac06984d4b9 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java @@ -100,7 +100,7 @@ import tech.pegasys.teku.validator.api.ClientGraffitiAppendFormat; class BlockOperationSelectorFactoryTest { - private final Spec spec = TestSpecFactory.createMinimalDeneb(); + private final Spec spec = TestSpecFactory.createMinimalElectra(); private final Spec specBellatrix = TestSpecFactory.createMinimalBellatrix(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); @@ -177,7 +177,7 @@ class BlockOperationSelectorFactoryTest { .getDefault(); private final CapturingBeaconBlockBodyBuilder bodyBuilder = - new CapturingBeaconBlockBodyBuilder(false); + new CapturingBeaconBlockBodyBuilder(false, false); private final GraffitiBuilder graffitiBuilder = new GraffitiBuilder(ClientGraffitiAppendFormat.DISABLED); @@ -963,6 +963,63 @@ void shouldThrowWhenExecutionPayloadContextNotProvided() { "ExecutionPayloadContext is not provided for production of post-merge block at slot 1"); } + @Test + void shouldGetExecutionRequestsForLocallyProducedBlocks() { + final UInt64 slot = UInt64.valueOf(2); + final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); + final SignedVoluntaryExit voluntaryExit = dataStructureUtil.randomSignedVoluntaryExit(); + final ProposerSlashing proposerSlashing = dataStructureUtil.randomProposerSlashing(); + final AttesterSlashing attesterSlashing = dataStructureUtil.randomAttesterSlashing(); + final SignedContributionAndProof contribution = + dataStructureUtil.randomSignedContributionAndProof(1, parentRoot); + final SignedBlsToExecutionChange blsToExecutionChange = + dataStructureUtil.randomSignedBlsToExecutionChange(); + addToPool(voluntaryExitPool, voluntaryExit); + addToPool(proposerSlashingPool, proposerSlashing); + addToPool(attesterSlashingPool, attesterSlashing); + assertThat(contributionPool.addLocal(contribution)).isCompletedWithValue(ACCEPT); + addToPool(blsToExecutionChangePool, blsToExecutionChange); + + final CapturingBeaconBlockBodyBuilder bodyBuilder = + new CapturingBeaconBlockBodyBuilder(true, true); + + final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); + final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); + + final ExecutionRequests expectedExecutionRequests = dataStructureUtil.randomExecutionRequests(); + + prepareBlockWithBlobsAndExecutionRequestsProduction( + randomExecutionPayload, + executionPayloadContext, + blockSlotState, + dataStructureUtil.randomBlobsBundle(), + expectedExecutionRequests, + blockExecutionValue); + + safeJoin( + factory + .createSelector( + parentRoot, + blockSlotState, + randaoReveal, + Optional.of(defaultGraffiti), + Optional.empty(), + BlockProductionPerformance.NOOP) + .apply(bodyBuilder)); + + assertThat(bodyBuilder.randaoReveal).isEqualTo(randaoReveal); + assertThat(bodyBuilder.graffiti).isEqualTo(defaultGraffiti); + assertThat(bodyBuilder.proposerSlashings).containsOnly(proposerSlashing); + assertThat(bodyBuilder.attesterSlashings).containsOnly(attesterSlashing); + assertThat(bodyBuilder.voluntaryExits).containsOnly(voluntaryExit); + assertThat(bodyBuilder.syncAggregate) + .isEqualTo( + spec.getSyncCommitteeUtilRequired(slot) + .createSyncAggregate(List.of(contribution.getMessage().getContribution()))); + assertThat(bodyBuilder.blsToExecutionChanges).containsOnly(blsToExecutionChange); + assertThat(bodyBuilder.executionRequests).isEqualTo(expectedExecutionRequests); + } + private void prepareBlockProductionWithPayload( final ExecutionPayload executionPayload, final ExecutionPayloadContext executionPayloadContext, @@ -1044,7 +1101,36 @@ private void prepareBlockAndBlobsProduction( executionPayloadContext, SafeFuture.completedFuture( new GetPayloadResponse( - executionPayload, executionPayloadValue, blobsBundle, false)))); + executionPayload, + executionPayloadValue, + blobsBundle, + false, + dataStructureUtil.randomExecutionRequests())))); + } + + private void prepareBlockWithBlobsAndExecutionRequestsProduction( + final ExecutionPayload executionPayload, + final ExecutionPayloadContext executionPayloadContext, + final BeaconState blockSlotState, + final BlobsBundle blobsBundle, + final ExecutionRequests executionRequests, + final UInt256 executionPayloadValue) { + when(executionLayer.initiateBlockProduction( + executionPayloadContext, + blockSlotState, + false, + Optional.empty(), + BlockProductionPerformance.NOOP)) + .thenReturn( + ExecutionPayloadResult.createForLocalFlow( + executionPayloadContext, + SafeFuture.completedFuture( + new GetPayloadResponse( + executionPayload, + executionPayloadValue, + blobsBundle, + false, + executionRequests)))); } private void prepareBlindedBlockAndBlobsProduction( @@ -1154,6 +1240,7 @@ private void prepareCachedFallbackData( private static class CapturingBeaconBlockBodyBuilder implements BeaconBlockBodyBuilder { private final boolean supportsKzgCommitments; + private final boolean supportExecutionRequests; protected BLSSignature randaoReveal; protected Bytes32 graffiti; @@ -1165,14 +1252,17 @@ private static class CapturingBeaconBlockBodyBuilder implements BeaconBlockBodyB protected ExecutionPayload executionPayload; protected ExecutionPayloadHeader executionPayloadHeader; protected SszList blobKzgCommitments; - - // TODO Update as part of Electra Engine API updates - // (https://github.com/Consensys/teku/issues/8620) - @SuppressWarnings("unused") protected ExecutionRequests executionRequests; public CapturingBeaconBlockBodyBuilder(final boolean supportsKzgCommitments) { this.supportsKzgCommitments = supportsKzgCommitments; + this.supportExecutionRequests = false; + } + + public CapturingBeaconBlockBodyBuilder( + final boolean supportsKzgCommitments, final boolean supportExecutionRequests) { + this.supportsKzgCommitments = supportsKzgCommitments; + this.supportExecutionRequests = supportExecutionRequests; } @Override @@ -1275,6 +1365,11 @@ public Boolean supportsKzgCommitments() { return supportsKzgCommitments; } + @Override + public boolean supportsExecutionRequests() { + return supportExecutionRequests; + } + @Override public BeaconBlockBodyBuilder blobKzgCommitments( final SszList blobKzgCommitments) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadResult.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadResult.java index 497003d00b4..35c85e1ac72 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadResult.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadResult.java @@ -19,6 +19,7 @@ import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.builder.BuilderBid; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; /** * In non-blinded flow, {@link #getPayloadResponseFuture} will be present. @@ -73,6 +74,12 @@ public Optional> getBuilderBidOrFallbackDat return builderBidOrFallbackDataFuture; } + public Optional>> getExecutionRequestsFromLocalFlow() { + return getPayloadResponseFuture.map( + getPayloadResponse -> + getPayloadResponse.thenApply(GetPayloadResponse::getExecutionRequests)); + } + /** * @return the value from the local payload, the builder bid or the local fallback payload */ diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java index 8c62c6ddf6d..d7a91c1650b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java @@ -65,6 +65,9 @@ import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; import tech.pegasys.teku.spec.datastructures.execution.PowBlock; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsBuilderElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.datastructures.util.BlobsUtil; @@ -72,8 +75,10 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; public class ExecutionLayerChannelStub implements ExecutionLayerChannel { + private static final Logger LOG = LogManager.getLogger(); private static final ClientVersion STUB_CLIENT_VERSION = new ClientVersion("SB", ExecutionLayerChannel.STUB_ENDPOINT_PREFIX, "0.0.0", Bytes4.ZERO); @@ -303,20 +308,43 @@ public SafeFuture engineGetPayload( state.getSlot(), executionPayload.getBlockHash()); + final Optional maybeExecutionRequests = getExecutionRequests(slot); + final GetPayloadResponse getPayloadResponse = headAndAttrs .currentBlobsBundle .map( blobsBundle -> { LOG.info("getPayload: blobsBundle: {}", blobsBundle.toBriefString()); - return new GetPayloadResponse( - executionPayload, UInt256.valueOf(424242424242424242L), blobsBundle, false); + if (maybeExecutionRequests.isPresent()) { + return new GetPayloadResponse( + executionPayload, + UInt256.valueOf(424242424242424242L), + blobsBundle, + false, + maybeExecutionRequests.get()); + } else { + return new GetPayloadResponse( + executionPayload, UInt256.valueOf(424242424242424242L), blobsBundle, false); + } }) .orElse(new GetPayloadResponse(executionPayload, UInt256.valueOf(434242424242424242L))); return SafeFuture.completedFuture(getPayloadResponse); } + private Optional getExecutionRequests(final UInt64 slot) { + if (spec.atSlot(slot).getMilestone().isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)) { + final ExecutionRequestsSchema executionRequestsSchema = + SchemaDefinitionsElectra.required( + spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + .getExecutionRequestsSchema(); + return Optional.of(new ExecutionRequestsBuilderElectra(executionRequestsSchema).build()); + } else { + return Optional.empty(); + } + } + @Override public SafeFuture engineNewPayload( final NewPayloadRequest newPayloadRequest, final UInt64 slot) { @@ -450,7 +478,8 @@ public SafeFuture builderGetPayload( executionPayloadHeader .hashTreeRoot() .equals(lastBuilderPayloadToBeUnblinded.get().hashTreeRoot()), - "provided signed blinded block contains an execution payload header not matching the previously retrieved execution payload via getPayloadHeader"); + "provided signed blinded block contains an execution payload header not matching the previously retrieved " + + "execution payload via getPayloadHeader"); LOG.info( "proposeBlindedBlock: slot: {} block: {} -> unblinded executionPayload blockHash: {}",