diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java index d63a2e33728..9b48210c7af 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java @@ -127,7 +127,7 @@ void engineNewPayload_shouldCallNewPayloadV4() { } @Test - void engineForkChoiceUpdated_shouldCallEngineForkChoiceUpdatedV3() { + void engineForkChoiceUpdated_shouldCallEngineForkChoiceUpdatedV4() { final ExecutionClientHandler handler = getHandler(); final ForkChoiceState forkChoiceState = dataStructureUtil.randomForkChoiceState(false); final ForkChoiceStateV1 forkChoiceStateV1 = @@ -142,7 +142,7 @@ void engineForkChoiceUpdated_shouldCallEngineForkChoiceUpdatedV3() { Optional.empty(), Optional.of(List.of()), dataStructureUtil.randomBytes32(), - spec.getMaxBlobsPerBlock().map(max -> UInt64.valueOf(max / 2)), + spec.getTargetBlobsPerBlock(dataStructureUtil.randomSlot()).map(UInt64::valueOf), spec.getMaxBlobsPerBlock().map(UInt64::valueOf)); final Optional payloadAttributes = PayloadAttributesV4.fromInternalPayloadBuildingAttributesV4(Optional.of(attributes)); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 29d1f68f6a3..30d42bf19e9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -950,11 +950,17 @@ public boolean isAvailabilityOfBlobSidecarsRequiredAtEpoch( } public Optional getMaxBlobsPerBlock() { - return getSpecConfigDeneb().map(SpecConfigDeneb::getMaxBlobsPerBlock); + final SpecMilestone highestSupportedMilestone = + getForkSchedule().getHighestSupportedMilestone(); + return forMilestone(highestSupportedMilestone).miscHelpers().getMaxBlobsPerBlock(); } public Optional getMaxBlobsPerBlock(final UInt64 slot) { - return getSpecConfigDeneb(slot).map(SpecConfigDeneb::getMaxBlobsPerBlock); + return atSlot(slot).miscHelpers().getMaxBlobsPerBlock(); + } + + public Optional getTargetBlobsPerBlock(final UInt64 slot) { + return atSlot(slot).miscHelpers().getTargetBlobsPerBlock(); } public UInt64 computeSubnetForBlobSidecar(final BlobSidecar blobSidecar) { @@ -983,10 +989,6 @@ private Optional getSpecConfigDeneb() { .flatMap(SpecConfig::toVersionDeneb); } - private Optional getSpecConfigDeneb(final UInt64 slot) { - return atSlot(slot).getConfig().toVersionDeneb(); - } - // Private helpers private SpecVersion atState(final BeaconState state) { return atSlot(state.getSlot()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index 1e411f8591f..3862bdd6254 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -392,8 +392,12 @@ public int getMaxRequestBlobSidecars() { throw new UnsupportedOperationException("No Blob Sidecars before Deneb"); } - public int getMaxBlobsPerBlock() { - throw new UnsupportedOperationException("No Blob Sidecars before Deneb"); + public Optional getMaxBlobsPerBlock() { + return Optional.empty(); + } + + public Optional getTargetBlobsPerBlock() { + return Optional.empty(); } public Optional getBlobSidecarSubnetCount() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java index 85f63b57a5a..129d699d098 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java @@ -217,8 +217,8 @@ public int getMaxRequestBlobSidecars() { } @Override - public int getMaxBlobsPerBlock() { - return SpecConfigDeneb.required(specConfig).getMaxBlobsPerBlock(); + public Optional getMaxBlobsPerBlock() { + return Optional.of(SpecConfigDeneb.required(specConfig).getMaxBlobsPerBlock()); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index b8f56afcb71..726f51bcdd0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -92,8 +92,12 @@ public int getMaxRequestBlobSidecars() { } @Override - public int getMaxBlobsPerBlock() { - return SpecConfigElectra.required(specConfig).getMaxBlobsPerBlockElectra(); + public Optional getMaxBlobsPerBlock() { + return Optional.of(SpecConfigElectra.required(specConfig).getMaxBlobsPerBlockElectra()); + } + + public Optional getTargetBlobsPerBlock() { + return Optional.of(SpecConfigElectra.required(specConfig).getTargetBlobsPerBlockElectra()); } @Override diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java index 34d57c0764d..b8f7cad2923 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManager.java @@ -195,11 +195,10 @@ public SafeFuture> calculatePayloadBuildingA final ForkChoiceState forkChoiceState = forkChoiceUpdateData.getForkChoiceState(); final Bytes32 currentHeadBlockRoot = forkChoiceState.getHeadBlockRoot(); return getStateInEpoch(epoch) - .thenApplyAsync( + .thenApply( maybeState -> calculatePayloadBuildingAttributes( - currentHeadBlockRoot, blockSlot, epoch, maybeState, mandatory), - eventThread); + currentHeadBlockRoot, blockSlot, epoch, maybeState, mandatory)); } /** @@ -237,7 +236,10 @@ private Optional calculatePayloadBuildingAttributes( .map(RegisteredValidatorInfo::getSignedValidatorRegistration); final Eth1Address feeRecipient = getFeeRecipient(proposerInfo, blockSlot); - final Optional maxBlobsPerBlock = spec.getMaxBlobsPerBlock().map(UInt64::valueOf); + final Optional maxBlobsPerBlock = + spec.getMaxBlobsPerBlock(blockSlot).map(UInt64::valueOf); + final Optional targetBlobsPerBlock = + spec.getTargetBlobsPerBlock(blockSlot).map(UInt64::valueOf); return Optional.of( new PayloadBuildingAttributes( @@ -249,7 +251,7 @@ private Optional calculatePayloadBuildingAttributes( validatorRegistration, spec.getExpectedWithdrawals(state), currentHeadBlockRoot, - maxBlobsPerBlock.map(maxBlobs -> maxBlobs.dividedBy(2)), + targetBlobsPerBlock, maxBlobsPerBlock)); } diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManagerTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManagerTest.java index 6c249d1e0cf..4dc41127628 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManagerTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/forkchoice/ProposersDataManagerTest.java @@ -14,70 +14,147 @@ package tech.pegasys.teku.statetransition.forkchoice; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; +import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestTemplate; import tech.pegasys.teku.ethereum.execution.types.Eth1Address; +import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.async.eventthread.EventThread; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecContext; import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.TestSpecInvocationContextProvider; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.validator.BeaconPreparableProposer; import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel; +import tech.pegasys.teku.spec.executionlayer.ForkChoiceState; +import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes; import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.teku.storage.client.ChainHead; import tech.pegasys.teku.storage.client.RecentChainData; +@TestSpecContext(allMilestones = true) class ProposersDataManagerTest { - private final Spec spec = TestSpecFactory.createMinimalCapella(); - - private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); - + private final UInt64 currentForkEpoch = UInt64.valueOf(1); private final RecentChainData recentChainData = mock(RecentChainData.class); - private final ExecutionLayerChannel channel = ExecutionLayerChannel.NOOP; private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); + private List proposers; - private final Eth1Address defaultAddress = dataStructureUtil.randomEth1Address(); - private final ProposersDataManager manager = - new ProposersDataManager( - mock(EventThread.class), - spec, - metricsSystem, - channel, - recentChainData, - Optional.of(defaultAddress), - false); + private Spec spec; + private SpecMilestone specMilestone; + private DataStructureUtil dataStructureUtil; + private ProposersDataManager manager; + private Eth1Address defaultAddress; + private UInt64 currentForkFirstSlot; - final List proposers = - List.of( - new BeaconPreparableProposer(UInt64.ONE, dataStructureUtil.randomEth1Address()), - new BeaconPreparableProposer(UInt64.ZERO, defaultAddress)); + @BeforeEach + public void setUp(final TestSpecInvocationContextProvider.SpecContext specContext) { + specMilestone = specContext.getSpecMilestone(); + spec = + switch (specContext.getSpecMilestone()) { + case PHASE0 -> TestSpecFactory.createMinimalPhase0(); + case ALTAIR -> TestSpecFactory.createMinimalWithAltairForkEpoch(currentForkEpoch); + case BELLATRIX -> TestSpecFactory.createMinimalWithBellatrixForkEpoch(currentForkEpoch); + case CAPELLA -> TestSpecFactory.createMinimalWithCapellaForkEpoch(currentForkEpoch); + case DENEB -> TestSpecFactory.createMinimalWithDenebForkEpoch(currentForkEpoch); + case ELECTRA -> TestSpecFactory.createMinimalWithElectraForkEpoch(currentForkEpoch); + }; + currentForkFirstSlot = spec.computeStartSlotAtEpoch(currentForkEpoch); + dataStructureUtil = specContext.getDataStructureUtil(); + defaultAddress = dataStructureUtil.randomEth1Address(); + manager = + new ProposersDataManager( + mock(EventThread.class), + spec, + metricsSystem, + channel, + recentChainData, + Optional.of(defaultAddress), + false); + proposers = + List.of( + new BeaconPreparableProposer(UInt64.ONE, dataStructureUtil.randomEth1Address()), + new BeaconPreparableProposer(UInt64.ZERO, defaultAddress)); + } - @Test + @TestTemplate void validatorIsConnected_notFound_withEmptyPreparedList() { assertThat(manager.validatorIsConnected(UInt64.ZERO, UInt64.ZERO)).isFalse(); } - @Test + @TestTemplate void validatorIsConnected_found_withPreparedProposer() { manager.updatePreparedProposers(proposers, UInt64.ONE); assertThat(manager.validatorIsConnected(UInt64.ONE, UInt64.valueOf(1))).isTrue(); } - @Test + @TestTemplate void validatorIsConnected_notFound_withDifferentPreparedProposer() { manager.updatePreparedProposers(proposers, UInt64.ONE); assertThat(manager.validatorIsConnected(UInt64.valueOf(2), UInt64.valueOf(2))).isFalse(); } - @Test + @TestTemplate void validatorIsConnected_notFound_withExpiredPreparedProposer() { manager.updatePreparedProposers(proposers, UInt64.ONE); assertThat(manager.validatorIsConnected(UInt64.ONE, UInt64.valueOf(26))).isFalse(); } + + @TestTemplate + void shouldSetMaxAndTargetBlobCount() throws ExecutionException, InterruptedException { + final Spec specMock = mock(Spec.class); + when(specMock.computeEpochAtSlot(any())).thenReturn(currentForkEpoch); + final UInt64 timestamp = dataStructureUtil.randomUInt64(); + when(specMock.computeTimeAtSlot(any(), any())).thenReturn(timestamp); + final Bytes32 random = dataStructureUtil.randomBytes32(); + when(specMock.getRandaoMix(any(), any())).thenReturn(random); + when(specMock.getMaxBlobsPerBlock(currentForkFirstSlot)).thenReturn(spec.getMaxBlobsPerBlock()); + when(specMock.getTargetBlobsPerBlock(currentForkFirstSlot)) + .thenReturn(spec.getTargetBlobsPerBlock(currentForkFirstSlot)); + manager = + new ProposersDataManager( + mock(EventThread.class), + specMock, + metricsSystem, + channel, + recentChainData, + Optional.of(defaultAddress), + true); + final ForkChoiceUpdateData forkChoiceUpdateDataMock = mock(ForkChoiceUpdateData.class); + when(forkChoiceUpdateDataMock.hasHeadBlockHash()).thenReturn(true); + final ForkChoiceState forkChoiceStateMock = mock(ForkChoiceState.class); + when(forkChoiceStateMock.getHeadBlockRoot()).thenReturn(dataStructureUtil.randomBytes32()); + when(forkChoiceUpdateDataMock.getForkChoiceState()).thenReturn(forkChoiceStateMock); + when(recentChainData.isJustifiedCheckpointFullyValidated()).thenReturn(true); + final ChainHead chainHeadMock = mock(ChainHead.class); + when(chainHeadMock.getSlot()).thenReturn(UInt64.ZERO); + when(chainHeadMock.getRoot()).thenReturn(dataStructureUtil.randomBytes32()); + when(recentChainData.getChainHead()).thenReturn(Optional.of(chainHeadMock)); + final BeaconState beaconStateMock = mock(BeaconState.class); + when(chainHeadMock.getState()).thenReturn(SafeFuture.completedFuture(beaconStateMock)); + + final SafeFuture> payloadBuildingAttributesFuture = + manager.calculatePayloadBuildingAttributes( + currentForkFirstSlot, true, forkChoiceUpdateDataMock, false); + final Optional maybePayloadBuildingAttributes = + payloadBuildingAttributesFuture.get(); + assertThat(maybePayloadBuildingAttributes).isPresent(); + assertThat(maybePayloadBuildingAttributes.get().getTargetBlobCount()) + .isEqualTo(spec.getTargetBlobsPerBlock(currentForkFirstSlot).map(UInt64::valueOf)); + assertThat(maybePayloadBuildingAttributes.get().getMaximumBlobCount()) + .isEqualTo(spec.getMaxBlobsPerBlock(currentForkFirstSlot).map(UInt64::valueOf)); + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandler.java index ca4a7b7c0c7..a8830638e1f 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandler.java @@ -91,7 +91,7 @@ public Optional validateRequest( final MiscHelpers miscHelpers = spec.forMilestone(latestMilestoneRequested).miscHelpers(); final int maxRequestBlobSidecars = miscHelpers.getMaxRequestBlobSidecars(); - final int maxBlobsPerBlock = miscHelpers.getMaxBlobsPerBlock(); + final int maxBlobsPerBlock = miscHelpers.getMaxBlobsPerBlock().orElseThrow(); final long requestedCount = calculateRequestedCount(request, maxBlobsPerBlock); @@ -126,7 +126,8 @@ public void onIncomingMessage( final SpecMilestone latestMilestoneRequested = spec.getForkSchedule().getSpecMilestoneAtSlot(endSlot); final MiscHelpers miscHelpers = spec.forMilestone(latestMilestoneRequested).miscHelpers(); - final long requestedCount = calculateRequestedCount(message, miscHelpers.getMaxBlobsPerBlock()); + final long requestedCount = + calculateRequestedCount(message, miscHelpers.getMaxBlobsPerBlock().orElseThrow()); final Optional blobSidecarsRequestApproval = peer.approveBlobSidecarsRequest(callback, requestedCount); diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandlerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandlerTest.java index 752ee33c16b..5cb8a067e50 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandlerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/BlobSidecarsByRangeMessageHandlerTest.java @@ -112,7 +112,8 @@ public void setUp(final TestSpecInvocationContextProvider.SpecContext specContex }; dataStructureUtil = new DataStructureUtil(spec); - maxBlobsPerBlock = spec.forMilestone(specMilestone).miscHelpers().getMaxBlobsPerBlock(); + maxBlobsPerBlock = + spec.forMilestone(specMilestone).miscHelpers().getMaxBlobsPerBlock().orElseThrow(); slotsPerEpoch = spec.getSlotsPerEpoch(ZERO); startSlot = currentForkEpoch.increment().times(slotsPerEpoch); handler = new BlobSidecarsByRangeMessageHandler(spec, metricsSystem, combinedChainDataClient);