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 b1fcae43faf..d25b6221bd3 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 @@ -206,6 +206,13 @@ private SafeFuture setExecutionData( final BeaconState blockSlotState, final BlockProductionPerformance blockProductionPerformance) { + if (spec.isMergeTransitionComplete(blockSlotState) && executionPayloadContext.isEmpty()) { + throw new IllegalStateException( + String.format( + "ExecutionPayloadContext is not provided for production of post-merge block at slot %s", + blockSlotState.getSlot())); + } + // if requestedBlinded has been specified, we strictly follow it otherwise, we should run // Builder // flow (blinded) only if we have a validator registration @@ -246,10 +253,7 @@ private SafeFuture setExecutionData( // Post-Deneb: Execution Payload / Execution Payload Header + KZG Commitments final ExecutionPayloadResult executionPayloadResult = executionLayerBlockProductionManager.initiateBlockAndBlobsProduction( - // kzg commitments are supported: we should have already merged by now, so we - // can safely assume we have an executionPayloadContext - executionPayloadContext.orElseThrow( - () -> new IllegalStateException("Cannot provide kzg commitments before The Merge")), + executionPayloadContext.orElseThrow(), blockSlotState, shouldTryBuilderFlow, requestedBuilderBoostFactor, 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 8df566f0227..7bb6cc5a70c 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 @@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.assertThatSafeFuture; import static tech.pegasys.teku.infrastructure.async.SafeFutureAssert.safeJoin; import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.ACCEPT; @@ -91,6 +92,7 @@ class BlockOperationSelectorFactoryTest { private final Spec spec = TestSpecFactory.createMinimalDeneb(); + private final Spec specBellatrix = TestSpecFactory.createMinimalBellatrix(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); private final Function> beaconBlockSchemaSupplier = @@ -161,12 +163,12 @@ class BlockOperationSelectorFactoryTest { mock(ExecutionLayerBlockProductionManager.class); private final ExecutionPayload defaultExecutionPayload = - SchemaDefinitionsBellatrix.required(spec.getGenesisSpec().getSchemaDefinitions()) + SchemaDefinitionsBellatrix.required(specBellatrix.getGenesisSpec().getSchemaDefinitions()) .getExecutionPayloadSchema() .getDefault(); private final ExecutionPayloadHeader executionPayloadHeaderOfDefaultPayload = - SchemaDefinitionsBellatrix.required(spec.getGenesisSpec().getSchemaDefinitions()) + SchemaDefinitionsBellatrix.required(specBellatrix.getGenesisSpec().getSchemaDefinitions()) .getExecutionPayloadHeaderSchema() .getHeaderOfDefaultPayload(); @@ -187,6 +189,21 @@ class BlockOperationSelectorFactoryTest { defaultGraffiti, forkChoiceNotifier, executionLayer); + private final BlockOperationSelectorFactory factoryBellatrix = + new BlockOperationSelectorFactory( + specBellatrix, + attestationPool, + attesterSlashingPool, + proposerSlashingPool, + voluntaryExitPool, + blsToExecutionChangePool, + contributionPool, + depositProvider, + eth1DataCache, + defaultGraffiti, + forkChoiceNotifier, + executionLayer); + private ExecutionPayloadContext executionPayloadContext; @BeforeEach void setUp() { @@ -202,14 +219,24 @@ void setUp() { .thenReturn(SafeFuture.completedFuture(ACCEPT)); when(blsToExecutionChangeValidator.validateForGossip(any())) .thenReturn(SafeFuture.completedFuture(ACCEPT)); + this.executionPayloadContext = dataStructureUtil.randomPayloadExecutionContext(false); when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.empty())); + .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); } @Test void shouldNotSelectOperationsWhenNoneAreAvailable() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); + final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); + final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); + + prepareBlockProductionWithPayload( + randomExecutionPayload, + executionPayloadContext, + blockSlotState, + Optional.of(blockExecutionValue)); + safeJoin( factory .createSelector( @@ -248,6 +275,15 @@ void shouldIncludeValidOperations() { assertThat(contributionPool.addLocal(contribution)).isCompletedWithValue(ACCEPT); addToPool(blsToExecutionChangePool, blsToExecutionChange); + final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); + final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); + + prepareBlockProductionWithPayload( + randomExecutionPayload, + executionPayloadContext, + blockSlotState, + Optional.of(blockExecutionValue)); + safeJoin( factory .createSelector( @@ -328,6 +364,14 @@ void shouldNotIncludeInvalidOperations() { blockSlotState, blsToExecutionChange2)) .thenReturn(Optional.of(BlsToExecutionChangeInvalidReason.invalidValidatorIndex())); + final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); + final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); + prepareBlockProductionWithPayload( + randomExecutionPayload, + executionPayloadContext, + blockSlotState, + Optional.of(blockExecutionValue)); + safeJoin( factory .createSelector( @@ -357,8 +401,11 @@ void shouldNotIncludeInvalidOperations() { void shouldIncludeDefaultExecutionPayload() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconStatePreMerge(slot); + when(forkChoiceNotifier.getPayloadId(any(), any())) + .thenReturn(SafeFuture.completedFuture(Optional.empty())); + safeJoin( - factory + factoryBellatrix .createSelector( parentRoot, blockSlotState, @@ -375,8 +422,11 @@ void shouldIncludeDefaultExecutionPayload() { void shouldIncludeExecutionPayloadHeaderOfDefaultPayload() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconStatePreMerge(slot); + when(forkChoiceNotifier.getPayloadId(any(), any())) + .thenReturn(SafeFuture.completedFuture(Optional.empty())); + safeJoin( - factory + factoryBellatrix .createSelector( parentRoot, blockSlotState, @@ -394,14 +444,9 @@ void shouldIncludeExecutionPayloadHeaderOfDefaultPayload() { void shouldIncludeNonDefaultExecutionPayload() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); - - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); prepareBlockProductionWithPayload( randomExecutionPayload, executionPayloadContext, @@ -430,16 +475,10 @@ void shouldIncludeNonDefaultExecutionPayload() { void shouldIncludeExecutionPayloadHeaderIfBlindedBlockRequested() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); - - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); final ExecutionPayloadHeader randomExecutionPayloadHeader = dataStructureUtil.randomExecutionPayloadHeader(); - final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); prepareBlockProductionWithPayloadHeader( randomExecutionPayloadHeader, executionPayloadContext, @@ -468,15 +507,9 @@ void shouldIncludeExecutionPayloadHeaderIfBlindedBlockRequested() { void shouldIncludeExecutionPayloadIfUnblindedBlockRequested() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); - - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); - final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); prepareBlockProductionWithPayload( randomExecutionPayload, executionPayloadContext, @@ -505,15 +538,9 @@ void shouldIncludeExecutionPayloadIfUnblindedBlockRequested() { void shouldIncludeExecutionPayloadIfRequestedBlindedIsEmpty() { final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); - - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false, false); final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); - final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); prepareBlockProductionWithPayload( randomExecutionPayload, executionPayloadContext, @@ -543,14 +570,12 @@ void shouldIncludeExecutionPayloadIfRequestedBlindedIsEmptyAndBuilderFlowFallsBa final UInt64 slot = UInt64.ONE; final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false, true); - final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); - - final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - + this.executionPayloadContext = dataStructureUtil.randomPayloadExecutionContext(false, true); when(forkChoiceNotifier.getPayloadId(any(), any())) .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); + + final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); + final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); prepareBlindedBlockProductionWithFallBack( randomExecutionPayload, executionPayloadContext, @@ -639,15 +664,10 @@ void shouldUnblindSignedBlindedBeaconBlock() { void shouldIncludeKzgCommitmentsInBlock() { final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(); - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); final ExecutionPayload randomExecutionPayload = dataStructureUtil.randomExecutionPayload(); final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); - final BlobsBundle blobsBundle = dataStructureUtil.randomBlobsBundle(); prepareBlockAndBlobsProduction( @@ -683,16 +703,11 @@ void shouldIncludeKzgCommitmentsInBlock() { void shouldIncludeKzgCommitmentsInBlindedBlock() { final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(); - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); final ExecutionPayloadHeader randomExecutionPayloadHeader = dataStructureUtil.randomExecutionPayloadHeader(); final UInt256 blockExecutionValue = dataStructureUtil.randomUInt256(); - when(forkChoiceNotifier.getPayloadId(any(), any())) - .thenReturn(SafeFuture.completedFuture(Optional.of(executionPayloadContext))); - final SszList blobKzgCommitments = dataStructureUtil.randomBlobKzgCommitments(); @@ -866,6 +881,29 @@ void shouldCreateBlobSidecarsForBlindedBlock() { }); } + @Test + void shouldThrowWhenExecutionPayloadContextNotProvided() { + final UInt64 slot = UInt64.ONE; + final BeaconState blockSlotState = dataStructureUtil.randomBeaconState(slot); + when(forkChoiceNotifier.getPayloadId(any(), any())) + .thenReturn(SafeFuture.completedFuture(Optional.empty())); + + assertThatSafeFuture( + factory + .createSelector( + parentRoot, + blockSlotState, + dataStructureUtil.randomSignature(), + Optional.empty(), + Optional.of(false), + Optional.empty(), + BlockProductionPerformance.NOOP) + .apply(bodyBuilder)) + .isCompletedExceptionallyWith(IllegalStateException.class) + .hasMessage( + "ExecutionPayloadContext is not provided for production of post-merge block at slot 1"); + } + private void prepareBlockProductionWithPayload( final ExecutionPayload executionPayload, final ExecutionPayloadContext executionPayloadContext,