From afeef378ab150db78abdd82f0603d1c5ae63d03c Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Wed, 8 Nov 2023 21:27:31 +0100 Subject: [PATCH] Time limited block creation (#6044) Signed-off-by: Fabio Di Fabio Co-authored-by: Sally MacFarlane --- CHANGELOG.md | 5 +- .../org/hyperledger/besu/cli/BesuCommand.java | 31 +- .../besu/cli/options/MiningOptions.java | 70 ++- .../CliqueBesuControllerBuilder.java | 3 +- .../controller/IbftBesuControllerBuilder.java | 3 +- .../MainnetBesuControllerBuilder.java | 3 +- .../MergeBesuControllerBuilder.java | 18 +- .../controller/QbftBesuControllerBuilder.java | 3 +- .../TransitionBesuControllerBuilder.java | 2 +- .../besu/cli/options/MiningOptionsTest.java | 76 ++++ .../TransitionControllerBuilderTest.java | 9 +- .../blockcreation/CliqueBlockCreator.java | 8 +- .../blockcreation/CliqueMinerExecutor.java | 16 +- .../blockcreation/CliqueBlockCreatorTest.java | 13 +- .../CliqueMinerExecutorTest.java | 12 +- .../bft/blockcreation/BftBlockCreator.java | 8 +- .../blockcreation/BftBlockCreatorFactory.java | 11 +- .../ibft/support/TestContextBuilder.java | 7 +- .../blockcreation/BftBlockCreatorTest.java | 4 +- .../blockcreation/MergeBlockCreator.java | 7 +- .../merge/blockcreation/MergeCoordinator.java | 34 +- .../blockcreation/MergeCoordinatorTest.java | 23 +- .../merge/blockcreation/MergeReorgTest.java | 7 +- .../qbft/support/TestContextBuilder.java | 7 +- .../QbftBlockCreatorFactory.java | 8 +- .../QbftBlockCreatorFactoryTest.java | 4 +- .../blockcreation/AbstractBlockCreator.java | 10 +- .../blockcreation/AbstractMinerExecutor.java | 9 +- .../blockcreation/PoWBlockCreator.java | 7 +- .../blockcreation/PoWMinerExecutor.java | 15 +- .../txselection/BlockTransactionSelector.java | 134 ++++-- .../TransactionSelectionResults.java | 16 +- .../AbstractBlockCreatorTest.java | 12 +- .../AbstractBlockTransactionSelectorTest.java | 426 ++++++++++++------ ...FeeMarketBlockTransactionSelectorTest.java | 3 +- ...FeeMarketBlockTransactionSelectorTest.java | 54 ++- .../blockcreation/PoWBlockCreatorTest.java | 12 +- .../blockcreation/PoWMinerExecutorTest.java | 9 +- .../besu/ethereum/core/MiningParameters.java | 29 ++ .../bonsai/AbstractIsolationTests.java | 18 +- .../ethereum/eth/manager/EthScheduler.java | 13 +- .../eth/manager/EthProtocolManagerTest.java | 3 +- .../manager/EthProtocolManagerTestUtil.java | 3 +- .../eth/manager/EthSchedulerShutdownTest.java | 5 +- .../eth/manager/EthSchedulerTest.java | 2 + .../ethtaskutils/AbstractMessageTaskTest.java | 2 +- .../AbstractPeerBlockValidatorTest.java | 2 +- .../backwardsync/BackwardSyncStepTest.java | 2 +- .../FastWorldStateDownloaderTest.java | 2 +- .../DynamicPivotBlockManagerTest.java | 2 +- .../ethereum/retesteth/RetestethContext.java | 8 + .../retesteth/methods/TestMineBlocks.java | 3 +- plugin-api/build.gradle | 2 +- .../data/TransactionSelectionResult.java | 6 + testutil/build.gradle | 1 + .../testutil}/DeterministicEthScheduler.java | 65 ++- .../besu/testutil}/MockScheduledExecutor.java | 5 +- 57 files changed, 948 insertions(+), 324 deletions(-) rename {ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager => testutil/src/main/java/org/hyperledger/besu/testutil}/DeterministicEthScheduler.java (68%) rename {ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager => testutil/src/main/java/org/hyperledger/besu/testutil}/MockScheduledExecutor.java (96%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69ac2df1d42..a3dbc24f571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Upgrade EVM Reference Tests to v13 (Cancun) [#6114](https://github.com/hyperledger/besu/pull/6114) - Add `yParity` to GraphQL and JSON-RPC for relevant querise. [6119](https://github.com/hyperledger/besu/pull/6119) - Force tx replacement price bump to zero when zero base fee market is configured or `--min-gas-price` is set to 0. This allows for easier tx replacement in networks where there is not gas price. [#6079](https://github.com/hyperledger/besu/pull/6079) +- Introduce the possibility to limit the time spent selecting pending transactions during block creation, using the new experimental option `Xblock-txs-selection-max-time` on PoS and PoW networks (by default set to 5000ms) or `Xpoa-block-txs-selection-max-time` on PoA networks (by default 75% of the min block time) [#6044](https://github.com/hyperledger/besu/pull/6044) ### Bug fixes - Upgrade netty to address CVE-2023-44487, CVE-2023-34462 [#6100](https://github.com/hyperledger/besu/pull/6100) @@ -33,8 +34,8 @@ ### Additions and Improvements - New option `--tx-pool-priority-senders` to specify a list of senders, that has the effect to prioritize any transactions sent by these senders from any source [#5959](https://github.com/hyperledger/besu/pull/5959) - Cache last n blocks by using a new Besu flag `--cache-last-blocks=n` [#6009](https://github.com/hyperledger/besu/pull/6009) -- Optimize performances of RPC method `eth_feeHistory` [#6011](https://github.com/hyperledger/besu/pull/6011) [#6035](https://github.com/hyperledger/besu/pull/6035) -- Logging summary of plugins at Info as part of the config overview [#5964](https://github.com/hyperledger/besu/pull/5964) [#6049](https://github.com/hyperledger/besu/pull/6049) +- Optimize performances of RPC method `eth_feeHistory` [#6011](https://github.com/hyperledger/besu/pull/6011) [#6035](https://github.com/hyperledger/besu/pull/6035) +- Logging summary of plugins at Info as part of the config overview [#5964](https://github.com/hyperledger/besu/pull/5964) [#6049](https://github.com/hyperledger/besu/pull/6049) - Layered tx pool memory improvements [#5985](https://github.com/hyperledger/besu/pull/5985) [#5974](https://github.com/hyperledger/besu/pull/5974) - Update Bouncy Castle to 1.76, and force the use of the `jdk18on` variant [#5748](https://github.com/hyperledger/besu/pull/5748) - Add GraphQL support for new fields in Cancun [#5923](https://github.com/hyperledger/besu/pull/5923) [#5975](https://github.com/hyperledger/besu/pull/5975) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index d1af3efe7f5..470a72c16a0 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -122,6 +122,7 @@ import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration; import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; @@ -1806,8 +1807,7 @@ private void validateRequiredOptions() { } private void validateMiningParams() { - miningOptions.validate( - commandLine, logger, isMergeEnabled(), getActualGenesisConfigOptions().isEthHash()); + miningOptions.validate(commandLine, getActualGenesisConfigOptions(), isMergeEnabled(), logger); } /** @@ -2830,11 +2830,36 @@ private TransactionPoolConfiguration buildTransactionPoolConfiguration() { private MiningParameters getMiningParameters() { if (miningParameters == null) { - miningParameters = miningOptions.toDomainObject(); + final var miningParametersBuilder = + ImmutableMiningParameters.builder().from(miningOptions.toDomainObject()); + final var actualGenesisOptions = getActualGenesisConfigOptions(); + if (actualGenesisOptions.isPoa()) { + miningParametersBuilder.unstable( + ImmutableMiningParameters.Unstable.builder() + .minBlockTime(getMinBlockTime(actualGenesisOptions)) + .build()); + } + miningParameters = miningParametersBuilder.build(); } return miningParameters; } + private int getMinBlockTime(final GenesisConfigOptions genesisConfigOptions) { + if (genesisConfigOptions.isClique()) { + return genesisConfigOptions.getCliqueConfigOptions().getBlockPeriodSeconds(); + } + + if (genesisConfigOptions.isIbft2()) { + return genesisConfigOptions.getBftConfigOptions().getBlockPeriodSeconds(); + } + + if (genesisConfigOptions.isQbft()) { + return genesisConfigOptions.getQbftConfigOptions().getBlockPeriodSeconds(); + } + + throw new IllegalArgumentException("Should only be called for a PoA network"); + } + private boolean isPruningEnabled() { return pruningEnabled; } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java index 8890f295515..78e2032e4f6 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java @@ -15,23 +15,29 @@ package org.hyperledger.besu.cli.options; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_EXTRA_DATA; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_MIN_BLOCK_OCCUPANCY_RATIO; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_MIN_PRIORITY_FEE_PER_GAS; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_MIN_TRANSACTION_GAS_PRICE; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_MAX_OMMERS_DEPTH; +import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POS_BLOCK_CREATION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POW_JOB_TTL; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_REMOTE_SEALERS_LIMIT; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_REMOTE_SEALERS_TTL; +import org.hyperledger.besu.cli.converter.PercentageConverter; import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.util.number.Percentage; import java.util.List; @@ -162,6 +168,25 @@ static class Unstable { + " then it waits before next repetition. Must be positive and ≤ 2000 (default: ${DEFAULT-VALUE} milliseconds)") private Long posBlockCreationRepetitionMinDuration = DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION; + + @CommandLine.Option( + hidden = true, + names = {"--Xblock-txs-selection-max-time"}, + description = + "Specifies the maximum time, in milliseconds, that could be spent selecting transactions to be included in the block." + + " Not compatible with PoA networks, see Xpoa-block-txs-selection-max-time." + + " Must be positive and ≤ (default: ${DEFAULT-VALUE})") + private Long nonPoaBlockTxsSelectionMaxTime = DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; + + @CommandLine.Option( + hidden = true, + names = {"--Xpoa-block-txs-selection-max-time"}, + converter = PercentageConverter.class, + description = + "Specifies the maximum time that could be spent selecting transactions to be included in the block, as a percentage of the fixed block time of the PoA network." + + " To be only used on PoA networks, for other networks see Xblock-txs-selection-max-time." + + " (default: ${DEFAULT-VALUE})") + private Percentage poaBlockTxsSelectionMaxTime = DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; } private MiningOptions() {} @@ -180,15 +205,15 @@ public static MiningOptions create() { * options are valid for the selected implementation. * * @param commandLine the full commandLine to check all the options specified by the user - * @param logger the logger + * @param genesisConfigOptions is EthHash? * @param isMergeEnabled is the Merge enabled? - * @param isEthHash is EthHash? + * @param logger the logger */ public void validate( final CommandLine commandLine, - final Logger logger, + final GenesisConfigOptions genesisConfigOptions, final boolean isMergeEnabled, - final boolean isEthHash) { + final Logger logger) { if (Boolean.TRUE.equals(isMiningEnabled) && coinbase == null) { throw new ParameterException( commandLine, @@ -203,7 +228,7 @@ public void validate( } // Check that block producer options work - if (!isMergeEnabled && isEthHash) { + if (!isMergeEnabled && genesisConfigOptions.isEthHash()) { CommandLineUtils.checkOptionDependencies( logger, commandLine, @@ -231,7 +256,9 @@ public void validate( if (unstableOptions.posBlockCreationMaxTime <= 0 || unstableOptions.posBlockCreationMaxTime > DEFAULT_POS_BLOCK_CREATION_MAX_TIME) { throw new ParameterException( - commandLine, "--Xpos-block-creation-max-time must be positive and ≤ 12000"); + commandLine, + "--Xpos-block-creation-max-time must be positive and ≤ " + + DEFAULT_POS_BLOCK_CREATION_MAX_TIME); } if (unstableOptions.posBlockCreationRepetitionMinDuration <= 0 @@ -239,6 +266,31 @@ public void validate( throw new ParameterException( commandLine, "--Xpos-block-creation-repetition-min-duration must be positive and ≤ 2000"); } + + if (genesisConfigOptions.isPoa()) { + CommandLineUtils.failIfOptionDoesntMeetRequirement( + commandLine, + "--Xblock-txs-selection-max-time can't be used with PoA networks," + + " see Xpoa-block-txs-selection-max-time instead", + false, + singletonList("--Xblock-txs-selection-max-time")); + } else { + CommandLineUtils.failIfOptionDoesntMeetRequirement( + commandLine, + "--Xpoa-block-txs-selection-max-time can be only used with PoA networks," + + " see --Xblock-txs-selection-max-time instead", + false, + singletonList("--Xpoa-block-txs-selection-max-time")); + + if (unstableOptions.nonPoaBlockTxsSelectionMaxTime <= 0 + || unstableOptions.nonPoaBlockTxsSelectionMaxTime + > DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME) { + throw new ParameterException( + commandLine, + "--Xblock-txs-selection-max-time must be positive and ≤ " + + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME); + } + } } static MiningOptions fromConfig(final MiningParameters miningParameters) { @@ -265,6 +317,10 @@ static MiningOptions fromConfig(final MiningParameters miningParameters) { miningParameters.getUnstable().getPosBlockCreationMaxTime(); miningOptions.unstableOptions.posBlockCreationRepetitionMinDuration = miningParameters.getUnstable().getPosBlockCreationRepetitionMinDuration(); + miningOptions.unstableOptions.nonPoaBlockTxsSelectionMaxTime = + miningParameters.getUnstable().getBlockTxsSelectionMaxTime(); + miningOptions.unstableOptions.poaBlockTxsSelectionMaxTime = + miningParameters.getUnstable().getPoaBlockTxsSelectionMaxTime(); miningParameters.getCoinbase().ifPresent(coinbase -> miningOptions.coinbase = coinbase); miningParameters.getTargetGasLimit().ifPresent(tgl -> miningOptions.targetGasLimit = tgl); @@ -304,6 +360,8 @@ public MiningParameters toDomainObject() { .posBlockCreationMaxTime(unstableOptions.posBlockCreationMaxTime) .posBlockCreationRepetitionMinDuration( unstableOptions.posBlockCreationRepetitionMinDuration) + .nonPoaBlockTxsSelectionMaxTime(unstableOptions.nonPoaBlockTxsSelectionMaxTime) + .poaBlockTxsSelectionMaxTime(unstableOptions.poaBlockTxsSelectionMaxTime) .build()); return miningParametersBuilder.build(); diff --git a/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java index 555b3c8a2fb..ba6a43906e8 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java @@ -94,7 +94,8 @@ protected MiningCoordinator createMiningCoordinator( localAddress, secondsBetweenBlocks), epochManager, - createEmptyBlocks); + createEmptyBlocks, + ethProtocolManager.ethContext().getScheduler()); final CliqueMiningCoordinator miningCoordinator = new CliqueMiningCoordinator( protocolContext.getBlockchain(), diff --git a/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java index 40765769e6e..6075afc386d 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java @@ -153,7 +153,8 @@ protected MiningCoordinator createMiningCoordinator( forksSchedule, miningParameters, localAddress, - bftExtraDataCodec().get()); + bftExtraDataCodec().get(), + ethProtocolManager.ethContext().getScheduler()); final ValidatorProvider validatorProvider = protocolContext.getConsensusContext(BftContext.class).getValidatorProvider(); diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java index 6f034e6f759..8942f337e3b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java @@ -55,7 +55,8 @@ protected MiningCoordinator createMiningCoordinator( MainnetBlockHeaderValidator.MINIMUM_SECONDS_SINCE_PARENT, MainnetBlockHeaderValidator.TIMESTAMP_TOLERANCE_S, clock), - epochCalculator); + epochCalculator, + ethProtocolManager.ethContext().getScheduler()); final PoWMiningCoordinator miningCoordinator = new PoWMiningCoordinator( diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java index 8bdbee2d9b2..b9a7b6ff5a6 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java @@ -35,7 +35,6 @@ import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.MergePeerFilter; -import org.hyperledger.besu.ethereum.eth.manager.MonitoredExecutors; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; import org.hyperledger.besu.ethereum.eth.peervalidation.RequiredBlocksPeerValidator; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; @@ -46,13 +45,10 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.List; import java.util.Optional; import java.util.OptionalLong; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; @@ -85,7 +81,7 @@ protected MiningCoordinator createMiningCoordinator( syncState, BackwardChain.from( storageProvider, ScheduleBasedBlockHeaderFunctions.create(protocolSchedule))), - metricsSystem); + ethProtocolManager.ethContext().getScheduler()); } @Override @@ -146,7 +142,7 @@ protected EthProtocolManager createEthProtocolManager( * @param miningParameters the mining parameters * @param syncState the sync state * @param backwardSyncContext the backward sync context - * @param metricsSystem the metrics system + * @param ethScheduler the scheduler * @return the mining coordinator */ protected MiningCoordinator createTransitionMiningCoordinator( @@ -156,13 +152,10 @@ protected MiningCoordinator createTransitionMiningCoordinator( final MiningParameters miningParameters, final SyncState syncState, final BackwardSyncContext backwardSyncContext, - final MetricsSystem metricsSystem) { + final EthScheduler ethScheduler) { this.syncState.set(syncState); - final ExecutorService blockBuilderExecutor = - MonitoredExecutors.newSingleThreadExecutor("PoS-Block-Builder", metricsSystem); - final GenesisConfigOptions genesisConfigOptions = configOptionsSupplier.get(); final Optional
depositContractAddress = genesisConfigOptions.getDepositContractAddress(); @@ -170,10 +163,7 @@ protected MiningCoordinator createTransitionMiningCoordinator( return new MergeCoordinator( protocolContext, protocolSchedule, - task -> { - LOG.debug("Block builder executor status {}", blockBuilderExecutor); - return CompletableFuture.runAsync(task, blockBuilderExecutor); - }, + ethScheduler, transactionPool, miningParameters, backwardSyncContext, diff --git a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java index 11049e7f3f4..38e14eec0db 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -191,7 +191,8 @@ protected MiningCoordinator createMiningCoordinator( qbftForksSchedule, miningParameters, localAddress, - bftExtraDataCodec().get()); + bftExtraDataCodec().get(), + ethProtocolManager.ethContext().getScheduler()); final ValidatorProvider validatorProvider = protocolContext.getConsensusContext(BftContext.class).getValidatorProvider(); diff --git a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java index d4541d91b9c..8231f8e26a5 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java @@ -144,7 +144,7 @@ protected MiningCoordinator createMiningCoordinator( transitionMiningParameters, syncState, transitionBackwardsSyncContext, - metricsSystem)); + ethProtocolManager.ethContext().getScheduler())); initTransitionWatcher(protocolContext, composedCoordinator); return composedCoordinator; } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java index 7d6d3cfe06e..0b7cc5e1224 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java @@ -15,6 +15,8 @@ package org.hyperledger.besu.cli.options; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POS_BLOCK_CREATION_MAX_TIME; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.verify; @@ -26,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.Unstable; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.util.number.Percentage; import java.io.IOException; import java.nio.file.Path; @@ -308,6 +311,79 @@ public void posBlockCreationMaxTimeOutOfAllowedRange() { "17000"); } + @Test + public void blockTxsSelectionMaxTimeDefaultValue() { + internalTestSuccess( + miningParams -> + assertThat(miningParams.getUnstable().getBlockTxsSelectionMaxTime()) + .isEqualTo(DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME)); + } + + @Test + public void blockTxsSelectionMaxTimeOption() { + internalTestSuccess( + miningParams -> + assertThat(miningParams.getUnstable().getBlockTxsSelectionMaxTime()).isEqualTo(1700L), + "--Xblock-txs-selection-max-time", + "1700"); + } + + @Test + public void blockTxsSelectionMaxTimeOutOfAllowedRange() { + internalTestFailure( + "--Xblock-txs-selection-max-time must be positive and ≤ 5000", + "--Xblock-txs-selection-max-time", + "6000"); + } + + @Test + public void blockTxsSelectionMaxTimeIncompatibleWithPoaNetworks() throws IOException { + final Path genesisFileIBFT2 = createFakeGenesisFile(VALID_GENESIS_IBFT2_POST_LONDON); + internalTestFailure( + "--Xblock-txs-selection-max-time can't be used with PoA networks, see Xpoa-block-txs-selection-max-time instead", + "--genesis-file", + genesisFileIBFT2.toString(), + "--Xblock-txs-selection-max-time", + "2"); + } + + @Test + public void poaBlockTxsSelectionMaxTimeDefaultValue() { + internalTestSuccess( + miningParams -> + assertThat(miningParams.getUnstable().getPoaBlockTxsSelectionMaxTime()) + .isEqualTo(DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME)); + } + + @Test + public void poaBlockTxsSelectionMaxTimeOption() throws IOException { + final Path genesisFileIBFT2 = createFakeGenesisFile(VALID_GENESIS_IBFT2_POST_LONDON); + internalTestSuccess( + miningParams -> + assertThat(miningParams.getUnstable().getPoaBlockTxsSelectionMaxTime()) + .isEqualTo(Percentage.fromInt(80)), + "--genesis-file", + genesisFileIBFT2.toString(), + "--Xpoa-block-txs-selection-max-time", + "80"); + } + + @Test + public void poaBlockTxsSelectionMaxTimeOutOfAllowedRange() { + internalTestFailure( + "Invalid value for option '--Xpoa-block-txs-selection-max-time': cannot convert '110' to Percentage", + "--Xpoa-block-txs-selection-max-time", + "110"); + } + + @Test + public void poaBlockTxsSelectionMaxTimeOnlyCompatibleWithPoaNetworks() { + internalTestFailure( + "--Xpoa-block-txs-selection-max-time can be only used with PoA networks, see --Xblock-txs-selection-max-time instead", + "--Xpoa-block-txs-selection-max-time", + "90"); + } + @Override protected MiningParameters createDefaultDomainObject() { return MiningParameters.newDefault(); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java index 20a182e6ed6..ab914ad4f5f 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java @@ -49,12 +49,14 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -71,7 +73,10 @@ public class TransitionControllerBuilderTest { @Mock MutableBlockchain mockBlockchain; @Mock TransactionPool transactionPool; @Mock SyncState syncState; - @Mock EthProtocolManager ethProtocolManager; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + EthProtocolManager ethProtocolManager; + @Mock PostMergeContext mergeContext; StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); @@ -101,6 +106,8 @@ public void setup() { .thenReturn(mock(CliqueContext.class)); when(protocolContext.getConsensusContext(PostMergeContext.class)).thenReturn(mergeContext); when(protocolContext.getConsensusContext(MergeContext.class)).thenReturn(mergeContext); + when(ethProtocolManager.ethContext().getScheduler()) + .thenReturn(new DeterministicEthScheduler()); miningParameters = MiningParameters.newDefault(); } diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java index d991fb9db4c..e703aeb48f1 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; @@ -55,6 +56,7 @@ public class CliqueBlockCreator extends AbstractBlockCreator { * @param nodeKey the node key * @param parentHeader the parent header * @param epochManager the epoch manager + * @param ethScheduler the scheduler for asynchronous block creation tasks */ public CliqueBlockCreator( final MiningParameters miningParameters, @@ -64,7 +66,8 @@ public CliqueBlockCreator( final ProtocolSchedule protocolSchedule, final NodeKey nodeKey, final BlockHeader parentHeader, - final EpochManager epochManager) { + final EpochManager epochManager, + final EthScheduler ethScheduler) { super( miningParameters, __ -> Util.publicKeyToAddress(nodeKey.getPublicKey()), @@ -73,7 +76,8 @@ public CliqueBlockCreator( protocolContext, protocolSchedule, parentHeader, - Optional.empty()); + Optional.empty(), + ethScheduler); this.nodeKey = nodeKey; this.epochManager = epochManager; } diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java index 060e262dd22..10c4cf4ef9b 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.util.Subscribers; @@ -60,6 +61,7 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor * @param blockScheduler the block scheduler * @param epochManager the epoch manager * @param createEmptyBlocks whether clique should allow the creation of empty blocks. + * @param ethScheduler the scheduler for asynchronous block creation tasks */ public CliqueMinerExecutor( final ProtocolContext protocolContext, @@ -69,8 +71,15 @@ public CliqueMinerExecutor( final MiningParameters miningParams, final AbstractBlockScheduler blockScheduler, final EpochManager epochManager, - final boolean createEmptyBlocks) { - super(protocolContext, protocolSchedule, transactionPool, miningParams, blockScheduler); + final boolean createEmptyBlocks, + final EthScheduler ethScheduler) { + super( + protocolContext, + protocolSchedule, + transactionPool, + miningParams, + blockScheduler, + ethScheduler); this.nodeKey = nodeKey; this.localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); this.epochManager = epochManager; @@ -93,7 +102,8 @@ public CliqueBlockMiner createMiner( protocolSchedule, nodeKey, header, - epochManager); + epochManager, + ethScheduler); return new CliqueBlockMiner( blockCreator, diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java index 3ddfdb1149d..631cad835f4 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java @@ -52,6 +52,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -63,6 +64,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import java.time.ZoneId; @@ -83,7 +85,7 @@ public class CliqueBlockCreatorTest { private final List
validatorList = Lists.newArrayList(); private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final CliqueBlockInterface blockInterface = new CliqueBlockInterface(); - + private final EthScheduler ethScheduler = new DeterministicEthScheduler(); private ProtocolSchedule protocolSchedule; private final WorldStateArchive stateArchive = createInMemoryWorldStateArchive(); @@ -148,7 +150,8 @@ public void proposerAddressCanBeExtractFromAConstructedBlock() { protocolSchedule, proposerNodeKey, blockchain.getChainHeadHeader(), - epochManager); + epochManager, + ethScheduler); final Block createdBlock = blockCreator.createBlock(5L).getBlock(); @@ -176,7 +179,8 @@ public void insertsValidVoteIntoConstructedBlock() { protocolSchedule, proposerNodeKey, blockchain.getChainHeadHeader(), - epochManager); + epochManager, + ethScheduler); final Block createdBlock = blockCreator.createBlock(0L).getBlock(); assertThat(createdBlock.getHeader().getNonce()).isEqualTo(CliqueBlockInterface.ADD_NONCE); @@ -209,7 +213,8 @@ public void insertsNoVoteWhenAtEpoch() { protocolSchedule, proposerNodeKey, blockchain.getChainHeadHeader(), - epochManager); + epochManager, + ethScheduler); final Block createdBlock = blockCreator.createBlock(0L).getBlock(); assertThat(createdBlock.getHeader().getNonce()).isEqualTo(CliqueBlockInterface.DROP_NONCE); diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index 740962e2a41..ab2545f8b3a 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -51,6 +52,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import java.time.ZoneId; @@ -78,6 +80,7 @@ public class CliqueMinerExecutorTest { private BlockHeaderTestFixture blockHeaderBuilder; private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final CliqueBlockInterface blockInterface = new CliqueBlockInterface(); + private final EthScheduler ethScheduler = new DeterministicEthScheduler(); @BeforeEach public void setup() { @@ -114,7 +117,8 @@ public void extraDataCreatedOnEpochBlocksContainsValidators() { miningParameters, mock(CliqueBlockScheduler.class), new EpochManager(EPOCH_LENGTH), - true); + true, + ethScheduler); // NOTE: Passing in the *parent* block, so must be 1 less than EPOCH final BlockHeader header = blockHeaderBuilder.number(EPOCH_LENGTH - 1).buildHeader(); @@ -149,7 +153,8 @@ public void extraDataForNonEpochBlocksDoesNotContainValidaors() { miningParameters, mock(CliqueBlockScheduler.class), new EpochManager(EPOCH_LENGTH), - true); + true, + ethScheduler); // Parent block was epoch, so the next block should contain no validators. final BlockHeader header = blockHeaderBuilder.number(EPOCH_LENGTH).buildHeader(); @@ -184,7 +189,8 @@ public void shouldUseLatestVanityData() { miningParameters, mock(CliqueBlockScheduler.class), new EpochManager(EPOCH_LENGTH), - true); + true, + ethScheduler); executor.setExtraData(modifiedVanityData); final Bytes extraDataBytes = executor.calculateExtraData(blockHeaderBuilder.buildHeader()); diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java index 4c5e773966f..e6a981cec78 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -50,6 +51,7 @@ public class BftBlockCreator extends AbstractBlockCreator { * @param protocolSchedule the protocol schedule * @param parentHeader the parent header * @param bftExtraDataCodec the bft extra data codec + * @param ethScheduler the scheduler for asynchronous block creation tasks */ public BftBlockCreator( final MiningParameters miningParameters, @@ -60,7 +62,8 @@ public BftBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final BlockHeader parentHeader, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final EthScheduler ethScheduler) { super( miningParameters.setCoinbase(localAddress), miningBeneficiaryCalculator(localAddress, forksSchedule), @@ -69,7 +72,8 @@ public BftBlockCreator( protocolContext, protocolSchedule, parentHeader, - Optional.empty()); + Optional.empty(), + ethScheduler); this.bftExtraDataCodec = bftExtraDataCodec; } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java index fab04b1a770..437c5dc65ef 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.blockcreation.BlockCreator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.AbstractGasLimitSpecification; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -61,6 +62,8 @@ public class BftBlockCreatorFactory { protected final ProtocolSchedule protocolSchedule; /** The Bft extra data codec. */ protected final BftExtraDataCodec bftExtraDataCodec; + /** The scheduler for asynchronous block creation tasks */ + protected final EthScheduler ethScheduler; private final Address localAddress; @@ -74,6 +77,7 @@ public class BftBlockCreatorFactory { * @param miningParams the mining params * @param localAddress the local address * @param bftExtraDataCodec the bft extra data codec + * @param ethScheduler the scheduler for asynchronous block creation tasks */ public BftBlockCreatorFactory( final TransactionPool transactionPool, @@ -82,7 +86,8 @@ public BftBlockCreatorFactory( final ForksSchedule forksSchedule, final MiningParameters miningParams, final Address localAddress, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final EthScheduler ethScheduler) { this.transactionPool = transactionPool; this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; @@ -90,6 +95,7 @@ public BftBlockCreatorFactory( this.localAddress = localAddress; this.miningParameters = miningParams; this.bftExtraDataCodec = bftExtraDataCodec; + this.ethScheduler = ethScheduler; } /** @@ -109,7 +115,8 @@ public BlockCreator create(final BlockHeader parentHeader, final int round) { protocolContext, protocolSchedule, parentHeader, - bftExtraDataCodec); + bftExtraDataCodec, + ethScheduler); } /** diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java index 9b0d9f65ad9..6223ebdf026 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java @@ -82,6 +82,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -92,6 +93,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import org.hyperledger.besu.util.Subscribers; @@ -367,6 +369,8 @@ private static ControllerAndState createControllerAndFinalState( transactionPool.setEnabled(); + final EthScheduler ethScheduler = new DeterministicEthScheduler(); + final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); final BftBlockCreatorFactory blockCreatorFactory = new BftBlockCreatorFactory<>( @@ -376,7 +380,8 @@ private static ControllerAndState createControllerAndFinalState( forksSchedule, miningParams, localAddress, - IBFT_EXTRA_DATA_ENCODER); + IBFT_EXTRA_DATA_ENCODER, + ethScheduler); final ProposerSelector proposerSelector = new ProposerSelector(blockChain, blockInterface, true, validatorProvider); diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java index 6065c269be7..9ef7c4c3a91 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java @@ -59,6 +59,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import java.time.ZoneId; @@ -186,7 +187,8 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( protContext, protocolSchedule, parentHeader, - bftExtraDataEncoder); + bftExtraDataEncoder, + new DeterministicEthScheduler()); final int secondsBetweenBlocks = 1; final Block block = blockCreator.createBlock(parentHeader.getTimestamp() + 1).getBlock(); diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java index b81a941816a..fa786d3c00d 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -53,7 +54,8 @@ public MergeBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final BlockHeader parentHeader, - final Optional
depositContractAddress) { + final Optional
depositContractAddress, + final EthScheduler ethScheduler) { super( miningParameters, __ -> miningParameters.getCoinbase().orElseThrow(), @@ -62,7 +64,8 @@ public MergeBlockCreator( protocolContext, protocolSchedule, parentHeader, - depositContractAddress); + depositContractAddress, + ethScheduler); } /** diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java index 9a5d26f1c63..aa3910d2b5b 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BadChainListener; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -86,7 +87,7 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene /** The Protocol context. */ protected final ProtocolContext protocolContext; /** The Block builder executor. */ - protected final ProposalBuilderExecutor blockBuilderExecutor; + protected final EthScheduler ethScheduler; /** The Backward sync context. */ protected final BackwardSyncContext backwardSyncContext; /** The Protocol schedule. */ @@ -100,7 +101,7 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene * * @param protocolContext the protocol context * @param protocolSchedule the protocol schedule - * @param blockBuilderExecutor the block builder executor + * @param ethScheduler the block builder executor * @param transactionPool the pending transactions * @param miningParams the mining params * @param backwardSyncContext the backward sync context @@ -109,14 +110,14 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene public MergeCoordinator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final ProposalBuilderExecutor blockBuilderExecutor, + final EthScheduler ethScheduler, final TransactionPool transactionPool, final MiningParameters miningParams, final BackwardSyncContext backwardSyncContext, final Optional
depositContractAddress) { this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; - this.blockBuilderExecutor = blockBuilderExecutor; + this.ethScheduler = ethScheduler; this.mergeContext = protocolContext.getConsensusContext(MergeContext.class); this.backwardSyncContext = backwardSyncContext; @@ -140,7 +141,8 @@ public MergeCoordinator( protocolContext, protocolSchedule, parentHeader, - depositContractAddress); + depositContractAddress, + ethScheduler); }; this.backwardSyncContext.subscribeBadChainListener(this); @@ -151,7 +153,7 @@ public MergeCoordinator( * * @param protocolContext the protocol context * @param protocolSchedule the protocol schedule - * @param blockBuilderExecutor the block builder executor + * @param ethScheduler the block builder executor * @param miningParams the mining params * @param backwardSyncContext the backward sync context * @param mergeBlockCreatorFactory the merge block creator factory @@ -159,14 +161,14 @@ public MergeCoordinator( public MergeCoordinator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final ProposalBuilderExecutor blockBuilderExecutor, + final EthScheduler ethScheduler, final MiningParameters miningParams, final BackwardSyncContext backwardSyncContext, final MergeBlockCreatorFactory mergeBlockCreatorFactory) { this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; - this.blockBuilderExecutor = blockBuilderExecutor; + this.ethScheduler = ethScheduler; this.mergeContext = protocolContext.getConsensusContext(MergeContext.class); this.backwardSyncContext = backwardSyncContext; if (miningParams.getTargetGasLimit().isEmpty()) { @@ -366,8 +368,9 @@ private void tryToBuildBetterBlock( payloadIdentifier, miningParameters.getUnstable().getPosBlockCreationMaxTime()); - blockBuilderExecutor - .buildProposal(() -> retryBlockCreationUntilUseful(payloadIdentifier, blockCreator)) + ethScheduler + .scheduleBlockCreationTask( + () -> retryBlockCreationUntilUseful(payloadIdentifier, blockCreator)) .orTimeout( miningParameters.getUnstable().getPosBlockCreationMaxTime(), TimeUnit.MILLISECONDS) .whenComplete( @@ -895,15 +898,4 @@ public void cancel() { blockCreator.cancel(); } } - - /** The interface Proposal builder executor. */ - public interface ProposalBuilderExecutor { - /** - * Build proposal and return completable future. - * - * @param task the task - * @return the completable future - */ - CompletableFuture buildProposal(final Runnable task); - } } diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java index 7c54b9260f9..cad6e05d60c 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java @@ -35,7 +35,6 @@ import org.hyperledger.besu.config.MergeConfigOptions; import org.hyperledger.besu.consensus.merge.MergeContext; import org.hyperledger.besu.consensus.merge.PayloadWrapper; -import org.hyperledger.besu.consensus.merge.blockcreation.MergeCoordinator.ProposalBuilderExecutor; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator.ForkchoiceResult; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SECPPrivateKey; @@ -62,6 +61,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; @@ -130,10 +130,11 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper { @Mock(answer = Answers.RETURNS_DEEP_STUBS) EthContext ethContext; - @Mock ProposalBuilderExecutor proposalBuilderExecutor; + @Mock EthScheduler ethScheduler; + private final Address coinbase = genesisAllocations(getPosGenesisConfigFile()).findFirst().get(); - MiningParameters miningParameters = + private MiningParameters miningParameters = ImmutableMiningParameters.builder() .mutableInitValues(MutableInitValues.builder().coinbase(coinbase).build()) .unstable( @@ -205,10 +206,13 @@ public void setUp() { genesisState.writeStateTo(mutable); mutable.persist(null); - when(proposalBuilderExecutor.buildProposal(any())) + when(ethScheduler.scheduleBlockCreationTask(any())) .thenAnswer( invocation -> { final Runnable runnable = invocation.getArgument(0); + if (!invocation.toString().contains("MergeCoordinator")) { + return CompletableFuture.runAsync(runnable); + } blockCreationTask = CompletableFuture.runAsync(runnable); return blockCreationTask; }); @@ -234,7 +238,7 @@ public void setUp() { new MergeCoordinator( protocolContext, protocolSchedule, - proposalBuilderExecutor, + ethScheduler, transactionPool, miningParameters, backwardSyncContext, @@ -287,7 +291,8 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() protocolContext, protocolSchedule, parentHeader, - Optional.empty())); + Optional.empty(), + ethScheduler)); doCallRealMethod() .doCallRealMethod() @@ -304,7 +309,7 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() new MergeCoordinator( protocolContext, protocolSchedule, - proposalBuilderExecutor, + ethScheduler, miningParameters, backwardSyncContext, mergeBlockCreatorFactory)); @@ -646,7 +651,7 @@ public void shouldNotStartAnotherBlockCreationJobIfCalledAgainWithTheSamePayload doAnswer( invocation -> { if (retries.getAndIncrement() < 5) { - // a new transaction every time a block is built + // add a new transaction every time a block is built transactions.addTransaction( createLocalTransaction(retries.get() - 1), Optional.empty()); } else { @@ -751,7 +756,7 @@ public void shouldUseExtraDataFromMiningParameters() { new MergeCoordinator( protocolContext, protocolSchedule, - proposalBuilderExecutor, + ethScheduler, transactionPool, miningParameters, backwardSyncContext, diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java index cd96d9cbdbb..1b2bd2bffd2 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; @@ -42,12 +43,12 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.LondonFeeMarket; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.util.LogConfigurator; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -72,7 +73,7 @@ public class MergeReorgTest implements MergeGenesisConfigHelper { private final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); - + private final EthScheduler ethScheduler = new DeterministicEthScheduler(); private final ProtocolContext protocolContext = new ProtocolContext(blockchain, worldStateArchive, mergeContext, Optional.empty()); @@ -92,7 +93,7 @@ public void setUp() { new MergeCoordinator( protocolContext, mockProtocolSchedule, - CompletableFuture::runAsync, + ethScheduler, mockTransactionPool, ImmutableMiningParameters.builder() .mutableInitValues(MutableInitValues.builder().coinbase(coinbase).build()) diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index 2b0386d9066..b2ee8421146 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -96,6 +96,7 @@ import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -108,6 +109,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import org.hyperledger.besu.util.Subscribers; @@ -455,6 +457,8 @@ private static ControllerAndState createControllerAndFinalState( transactionPool.setEnabled(); + final EthScheduler ethScheduler = new DeterministicEthScheduler(); + final Address localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); final BftBlockCreatorFactory blockCreatorFactory = new QbftBlockCreatorFactory( @@ -464,7 +468,8 @@ private static ControllerAndState createControllerAndFinalState( forksSchedule, miningParams, localAddress, - BFT_EXTRA_DATA_ENCODER); + BFT_EXTRA_DATA_ENCODER, + ethScheduler); final ProposerSelector proposerSelector = new ProposerSelector(blockChain, blockInterface, true, validatorProvider); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java index 2f993c4d1bb..5bb87363b39 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.blockcreation.BlockCreator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -46,6 +47,7 @@ public class QbftBlockCreatorFactory extends BftBlockCreatorFactory forksSchedule, final MiningParameters miningParams, final Address localAddress, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final EthScheduler ethScheduler) { super( transactionPool, protocolContext, @@ -62,7 +65,8 @@ public QbftBlockCreatorFactory( forksSchedule, miningParams, localAddress, - bftExtraDataCodec); + bftExtraDataCodec, + ethScheduler); } @Override diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java index d7b63bc215c..27c50144531 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactoryTest.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.Optional; @@ -72,7 +73,8 @@ public void setUp() { forksSchedule, miningParams, mock(Address.class), - extraDataCodec); + extraDataCodec, + new DeterministicEthScheduler()); } @Test diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index 668a875ff34..deac72bc5da 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -39,6 +39,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.DepositDecoder; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; @@ -92,7 +93,7 @@ public interface ExtraDataCalculator { protected final BlockHeaderFunctions blockHeaderFunctions; protected final BlockHeader parentHeader; private final Optional
depositContractAddress; - + private final EthScheduler ethScheduler; private final AtomicBoolean isCancelled = new AtomicBoolean(false); protected AbstractBlockCreator( @@ -103,7 +104,8 @@ protected AbstractBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final BlockHeader parentHeader, - final Optional
depositContractAddress) { + final Optional
depositContractAddress, + final EthScheduler ethScheduler) { this.miningParameters = miningParameters; this.miningBeneficiaryCalculator = miningBeneficiaryCalculator; this.extraDataCalculator = extraDataCalculator; @@ -112,6 +114,7 @@ protected AbstractBlockCreator( this.protocolSchedule = protocolSchedule; this.parentHeader = parentHeader; this.depositContractAddress = depositContractAddress; + this.ethScheduler = ethScheduler; blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); } @@ -360,7 +363,8 @@ private TransactionSelectionResults selectTransactions( protocolSpec.getFeeMarket(), protocolSpec.getGasCalculator(), protocolSpec.getGasLimitCalculator(), - pluginTransactionSelector); + pluginTransactionSelector, + ethScheduler); if (transactions.isPresent()) { return selector.evaluateTransactions(transactions.get()); diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java index 1432afb34a7..451af2df12b 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.chain.PoWObserver; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.AbstractGasLimitSpecification; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -41,12 +42,14 @@ public abstract class AbstractMinerExecutor new Thread(r, "MinerExecutor")); protected final ProtocolContext protocolContext; protected final ProtocolSchedule protocolSchedule; protected final TransactionPool transactionPool; protected final AbstractBlockScheduler blockScheduler; protected final MiningParameters miningParameters; + protected final EthScheduler ethScheduler; private final AtomicBoolean stopped = new AtomicBoolean(false); protected AbstractMinerExecutor( @@ -54,12 +57,14 @@ protected AbstractMinerExecutor( final ProtocolSchedule protocolSchedule, final TransactionPool transactionPool, final MiningParameters miningParams, - final AbstractBlockScheduler blockScheduler) { + final AbstractBlockScheduler blockScheduler, + final EthScheduler ethScheduler) { this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; this.transactionPool = transactionPool; this.blockScheduler = blockScheduler; this.miningParameters = miningParams; + this.ethScheduler = ethScheduler; } public Optional startAsyncMining( diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java index 66c57379ccb..9cf12920701 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.EthHash; import org.hyperledger.besu.ethereum.mainnet.PoWSolution; @@ -44,7 +45,8 @@ public PoWBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final PoWSolver nonceSolver, - final BlockHeader parentHeader) { + final BlockHeader parentHeader, + final EthScheduler ethScheduler) { super( miningParameters, __ -> miningParameters.getCoinbase().orElseThrow(), @@ -53,7 +55,8 @@ public PoWBlockCreator( protocolContext, protocolSchedule, parentHeader, - Optional.empty()); + Optional.empty(), + ethScheduler); this.nonceSolver = nonceSolver; } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java index cd44318e960..78524e3035e 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.chain.PoWObserver; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.EpochCalculator; import org.hyperledger.besu.ethereum.mainnet.PoWSolver; @@ -41,8 +42,15 @@ public PoWMinerExecutor( final TransactionPool transactionPool, final MiningParameters miningParams, final AbstractBlockScheduler blockScheduler, - final EpochCalculator epochCalculator) { - super(protocolContext, protocolSchedule, transactionPool, miningParams, blockScheduler); + final EpochCalculator epochCalculator, + final EthScheduler ethScheduler) { + super( + protocolContext, + protocolSchedule, + transactionPool, + miningParams, + blockScheduler, + ethScheduler); if (miningParams.getNonceGenerator().isEmpty()) { miningParams.setNonceGenerator(new RandomNonceGenerator()); } @@ -85,7 +93,8 @@ public PoWBlockMiner createMiner( protocolContext, protocolSchedule, solver, - parentHeader); + parentHeader, + ethScheduler); return new PoWBlockMiner( blockCreator, protocolSchedule, protocolContext, observers, blockScheduler, parentHeader); diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java index 7b1ad92ef89..595bcb74332 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.ethereum.blockcreation.txselection; +import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT; +import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.GasLimitCalculator; @@ -29,6 +32,7 @@ import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; @@ -46,6 +50,10 @@ import java.util.List; import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.slf4j.Logger; @@ -85,6 +93,9 @@ public class BlockTransactionSelector { private final List transactionSelectors; private final PluginTransactionSelector pluginTransactionSelector; private final BlockAwareOperationTracer pluginOperationTracer; + private final EthScheduler ethScheduler; + private final AtomicBoolean isTimeout = new AtomicBoolean(false); + private WorldUpdater blockWorldStateUpdater; public BlockTransactionSelector( final MiningParameters miningParameters, @@ -100,12 +111,14 @@ public BlockTransactionSelector( final FeeMarket feeMarket, final GasCalculator gasCalculator, final GasLimitCalculator gasLimitCalculator, - final PluginTransactionSelector pluginTransactionSelector) { + final PluginTransactionSelector pluginTransactionSelector, + final EthScheduler ethScheduler) { this.transactionProcessor = transactionProcessor; this.blockchain = blockchain; this.worldState = worldState; this.transactionReceiptFactory = transactionReceiptFactory; this.isCancelled = isCancelled; + this.ethScheduler = ethScheduler; this.blockSelectionContext = new BlockSelectionContext( miningParameters, @@ -119,6 +132,7 @@ public BlockTransactionSelector( transactionSelectors = createTransactionSelectors(blockSelectionContext); this.pluginTransactionSelector = pluginTransactionSelector; this.pluginOperationTracer = pluginTransactionSelector.getOperationTracer(); + blockWorldStateUpdater = worldState.updater(); } private List createTransactionSelectors( @@ -145,9 +159,7 @@ public TransactionSelectionResults buildTransactionListForBlock() { .setMessage("Transaction pool stats {}") .addArgument(blockSelectionContext.transactionPool().logStats()) .log(); - - blockSelectionContext.transactionPool().selectTransactions(this::evaluateTransaction); - + timeLimitedSelection(); LOG.atTrace() .setMessage("Transaction selection result {}") .addArgument(transactionSelectionResults::toTraceLog) @@ -155,6 +167,36 @@ public TransactionSelectionResults buildTransactionListForBlock() { return transactionSelectionResults; } + private void timeLimitedSelection() { + final long blockTxsSelectionMaxTime = + blockSelectionContext.miningParameters().getUnstable().getBlockTxsSelectionMaxTime(); + final var txSelection = + ethScheduler.scheduleBlockCreationTask( + () -> + blockSelectionContext + .transactionPool() + .selectTransactions(this::evaluateTransaction)); + + try { + txSelection.get(blockTxsSelectionMaxTime, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException e) { + if (isCancelled.get()) { + throw new CancellationException("Cancelled during transaction selection"); + } + LOG.warn("Error during block transaction selection", e); + } catch (TimeoutException e) { + // synchronize since we want to be sure that there is no concurrent state update + synchronized (isTimeout) { + isTimeout.set(true); + } + LOG.warn( + "Interrupting transaction selection since it is taking more than the max configured time of " + + blockTxsSelectionMaxTime + + "ms", + e); + } + } + /** * Evaluates a list of transactions and updates the selection results accordingly. If a * transaction is not selected during the evaluation, it is updated as not selected in the @@ -189,17 +231,18 @@ private TransactionSelectionResult evaluateTransaction( return handleTransactionNotSelected(pendingTransaction, selectionResult); } - final WorldUpdater worldStateUpdater = worldState.updater(); + final WorldUpdater txWorldStateUpdater = blockWorldStateUpdater.updater(); final TransactionProcessingResult processingResult = - processTransaction(pendingTransaction, worldStateUpdater); + processTransaction(pendingTransaction, txWorldStateUpdater); var postProcessingSelectionResult = evaluatePostProcessing(pendingTransaction, processingResult); - if (!postProcessingSelectionResult.selected()) { - return handleTransactionNotSelected(pendingTransaction, postProcessingSelectionResult); - } - return handleTransactionSelected(pendingTransaction, processingResult, worldStateUpdater); + if (postProcessingSelectionResult.selected()) { + return handleTransactionSelected(pendingTransaction, processingResult, txWorldStateUpdater); + } + return handleTransactionNotSelected( + pendingTransaction, postProcessingSelectionResult, txWorldStateUpdater); } /** @@ -218,7 +261,7 @@ private TransactionSelectionResult evaluatePreProcessing( TransactionSelectionResult result = selector.evaluateTransactionPreProcessing( pendingTransaction, transactionSelectionResults); - if (!result.equals(TransactionSelectionResult.SELECTED)) { + if (!result.equals(SELECTED)) { return result; } } @@ -243,7 +286,7 @@ private TransactionSelectionResult evaluatePostProcessing( TransactionSelectionResult result = selector.evaluateTransactionPostProcessing( pendingTransaction, transactionSelectionResults, processingResult); - if (!result.equals(TransactionSelectionResult.SELECTED)) { + if (!result.equals(SELECTED)) { return result; } } @@ -282,14 +325,13 @@ private TransactionProcessingResult processTransaction( * * @param pendingTransaction The pending transaction. * @param processingResult The result of the transaction processing. - * @param worldStateUpdater The world state updater. + * @param txWorldStateUpdater The world state updater. * @return The result of the transaction selection process. */ private TransactionSelectionResult handleTransactionSelected( final PendingTransaction pendingTransaction, final TransactionProcessingResult processingResult, - final WorldUpdater worldStateUpdater) { - worldStateUpdater.commit(); + final WorldUpdater txWorldStateUpdater) { final Transaction transaction = pendingTransaction.getTransaction(); final long gasUsedByTransaction = @@ -299,17 +341,47 @@ private TransactionSelectionResult handleTransactionSelected( final long blobGasUsed = blockSelectionContext.gasCalculator().blobGasCost(transaction.getBlobCount()); - final TransactionReceipt receipt = - transactionReceiptFactory.create( - transaction.getType(), processingResult, worldState, cumulativeGasUsed); + final boolean tooLate; - logTransactionSelection(pendingTransaction.getTransaction()); + // only add this tx to the selected set if it is not too late, + // this need to be done synchronously to avoid that a concurrent timeout + // could start packing a block while we are updating the state here + synchronized (isTimeout) { + if (!isTimeout.get()) { + txWorldStateUpdater.commit(); + blockWorldStateUpdater.commit(); + final TransactionReceipt receipt = + transactionReceiptFactory.create( + transaction.getType(), processingResult, worldState, cumulativeGasUsed); - transactionSelectionResults.updateSelected( - pendingTransaction.getTransaction(), receipt, gasUsedByTransaction, blobGasUsed); - pluginTransactionSelector.onTransactionSelected(pendingTransaction, processingResult); + transactionSelectionResults.updateSelected( + pendingTransaction.getTransaction(), receipt, gasUsedByTransaction, blobGasUsed); + tooLate = false; + } else { + tooLate = true; + } + } + + if (tooLate) { + // even if this tx passed all the checks, it is too late to include it in this block, + // so we need to treat it as not selected + LOG.atTrace() + .setMessage("{} processed too late for block creation") + .addArgument(transaction::toTraceLog) + .log(); + // do not rely on the presence of this result, since by the time it is added, the code + // reading it could have been already executed by another thread + return handleTransactionNotSelected( + pendingTransaction, BLOCK_SELECTION_TIMEOUT, txWorldStateUpdater); + } - return TransactionSelectionResult.SELECTED; + pluginTransactionSelector.onTransactionSelected(pendingTransaction, processingResult); + blockWorldStateUpdater = worldState.updater(); + LOG.atTrace() + .setMessage("Selected {} for block creation") + .addArgument(transaction::toTraceLog) + .log(); + return SELECTED; } /** @@ -324,22 +396,24 @@ private TransactionSelectionResult handleTransactionSelected( private TransactionSelectionResult handleTransactionNotSelected( final PendingTransaction pendingTransaction, final TransactionSelectionResult selectionResult) { + transactionSelectionResults.updateNotSelected( pendingTransaction.getTransaction(), selectionResult); pluginTransactionSelector.onTransactionNotSelected(pendingTransaction, selectionResult); return selectionResult; } + private TransactionSelectionResult handleTransactionNotSelected( + final PendingTransaction pendingTransaction, + final TransactionSelectionResult selectionResult, + final WorldUpdater txWorldStateUpdater) { + txWorldStateUpdater.revert(); + return handleTransactionNotSelected(pendingTransaction, selectionResult); + } + private void checkCancellation() { if (isCancelled.get()) { throw new CancellationException("Cancelled during transaction selection."); } } - - private void logTransactionSelection(final Transaction transaction) { - LOG.atTrace() - .setMessage("Selected {} for block creation") - .addArgument(transaction::toTraceLog) - .log(); - } } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java index b28640bae40..848789d9968 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java @@ -21,10 +21,10 @@ import java.util.ArrayList; import java.util.EnumMap; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; @@ -39,8 +39,13 @@ public class TransactionSelectionResults { private final Map> transactionsByType = new EnumMap<>(TransactionType.class); private final List receipts = Lists.newArrayList(); + /** + * Access to this field needs to be guarded, since it is possible to read it while another + * processing thread is writing, when the selection time is over. + */ private final Map notSelectedTransactions = - new HashMap<>(); + new ConcurrentHashMap<>(); + private long cumulativeGasUsed = 0; private long cumulativeBlobGasUsed = 0; @@ -92,18 +97,19 @@ public long getCumulativeBlobGasUsed() { } public Map getNotSelectedTransactions() { - return notSelectedTransactions; + return Map.copyOf(notSelectedTransactions); } public void logSelectionStats() { if (LOG.isDebugEnabled()) { + final var notSelectedTxs = getNotSelectedTransactions(); final Map notSelectedStats = - notSelectedTransactions.values().stream() + notSelectedTxs.values().stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); LOG.debug( "Selection stats: Totals[Evaluated={}, Selected={}, NotSelected={}, Discarded={}]; Detailed[{}]", - selectedTransactions.size() + notSelectedTransactions.size(), + selectedTransactions.size() + notSelectedTxs.size(), selectedTransactions.size(), notSelectedStats.size(), notSelectedStats.entrySet().stream() diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java index 413f9bb9ce6..a317c12f669 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java @@ -59,6 +59,7 @@ import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -77,6 +78,7 @@ import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.log.LogTopic; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.math.BigInteger; import java.time.Clock; @@ -97,6 +99,7 @@ abstract class AbstractBlockCreatorTest { private static final Optional
EMPTY_DEPOSIT_CONTRACT_ADDRESS = Optional.empty(); @Mock private WithdrawalsProcessor withdrawalsProcessor; + protected EthScheduler ethScheduler = new DeterministicEthScheduler(); @Test void findDepositsFromReceipts() { @@ -405,7 +408,8 @@ private AbstractBlockCreator createBlockCreator( executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), blockchain.getChainHeadHeader(), - depositContractAddress); + depositContractAddress, + ethScheduler); } static class TestBlockCreator extends AbstractBlockCreator { @@ -418,7 +422,8 @@ protected TestBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final BlockHeader parentHeader, - final Optional
depositContractAddress) { + final Optional
depositContractAddress, + final EthScheduler ethScheduler) { super( miningParameters, miningBeneficiaryCalculator, @@ -427,7 +432,8 @@ protected TestBlockCreator( protocolContext, protocolSchedule, parentHeader, - depositContractAddress); + depositContractAddress, + ethScheduler); } @Override diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java index ea138ab2993..ae16f34d078 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java @@ -16,6 +16,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.awaitility.Awaitility.await; +import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -48,6 +50,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; +import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.Unstable; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MutableWorldState; @@ -56,6 +59,7 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; @@ -76,6 +80,7 @@ import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import org.hyperledger.besu.util.number.Percentage; import java.math.BigInteger; import java.time.Instant; @@ -83,24 +88,34 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import java.util.stream.Stream; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public abstract class AbstractBlockTransactionSelectorTest { protected static final double MIN_OCCUPANCY_80_PERCENT = 0.8; protected static final double MIN_OCCUPANCY_100_PERCENT = 1; + protected static final PluginTransactionSelectorFactory NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY = + () -> AllAcceptingTransactionSelector.INSTANCE; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(42L); protected static final KeyPair keyPair = SignatureAlgorithmFactory.getInstance().generateKeyPair(); @@ -113,7 +128,11 @@ public abstract class AbstractBlockTransactionSelectorTest { protected TransactionPool transactionPool; protected MutableWorldState worldState; protected ProtocolSchedule protocolSchedule; - protected MiningParameters miningParameters; + protected final MiningParameters defaultTestMiningParameters = + createMiningParameters( + Wei.ZERO, MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME); + + @Mock protected EthScheduler ethScheduler; @Mock(answer = Answers.RETURNS_DEEP_STUBS) protected ProtocolContext protocolContext; @@ -150,19 +169,15 @@ public void setup() { when(protocolContext.getWorldStateArchive().getMutable(any(), anyBoolean())) .thenReturn(Optional.of(worldState)); when(ethContext.getEthPeers().subscribeConnect(any())).thenReturn(1L); - miningParameters = - ImmutableMiningParameters.builder() - .mutableInitValues(MutableInitValues.builder().minTransactionGasPrice(Wei.ONE).build()) - .build(); - - transactionPool = createTransactionPool(); + when(ethScheduler.scheduleBlockCreationTask(any(Runnable.class))) + .thenAnswer(invocation -> CompletableFuture.runAsync(invocation.getArgument(0))); } protected abstract GenesisConfigFile getGenesisConfigFile(); protected abstract ProtocolSchedule createProtocolSchedule(); - protected abstract TransactionPool createTransactionPool(); + protected abstract TransactionPool createTransactionPool(final MiningParameters miningParameters); private Boolean isCancelled() { return false; @@ -198,13 +213,13 @@ public void emptyPendingTransactionsResultsInEmptyVettingResult() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, mainnetTransactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -216,23 +231,21 @@ public void emptyPendingTransactionsResultsInEmptyVettingResult() { @Test public void validPendingTransactionIsIncludedInTheBlock() { - final Transaction transaction = createTransaction(1, Wei.of(7L), 100_000); - transactionPool.addRemoteTransactions(List.of(transaction)); - - ensureTransactionIsValid(transaction, 0, 5); - final ProcessableBlockHeader blockHeader = createBlock(500_000); - final Address miningBeneficiary = AddressHelpers.ofValue(1); - final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + final Transaction transaction = createTransaction(1, Wei.of(7L), 100_000); + transactionPool.addRemoteTransactions(List.of(transaction)); + + ensureTransactionIsValid(transaction, 0, 5); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -244,6 +257,18 @@ public void validPendingTransactionIsIncludedInTheBlock() { @Test public void invalidTransactionsAreSkippedButBlockStillFills() { + // The block should fit 4 transactions only + final ProcessableBlockHeader blockHeader = createBlock(400_000); + final Address miningBeneficiary = AddressHelpers.ofValue(1); + final BlockTransactionSelector selector = + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, + transactionProcessor, + blockHeader, + miningBeneficiary, + Wei.ZERO, + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + final List transactionsToInject = Lists.newArrayList(); for (int i = 0; i < 5; i++) { final Transaction tx = createTransaction(i, Wei.of(7), 100_000); @@ -256,20 +281,6 @@ public void invalidTransactionsAreSkippedButBlockStillFills() { } transactionPool.addRemoteTransactions(transactionsToInject); - // The block should fit 4 transactions only - final ProcessableBlockHeader blockHeader = createBlock(400_000); - - final Address miningBeneficiary = AddressHelpers.ofValue(1); - - final BlockTransactionSelector selector = - createBlockSelector( - transactionProcessor, - blockHeader, - Wei.ZERO, - miningBeneficiary, - Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); - final TransactionSelectionResults results = selector.buildTransactionListForBlock(); final Transaction invalidTx = transactionsToInject.get(1); @@ -288,26 +299,24 @@ public void invalidTransactionsAreSkippedButBlockStillFills() { @Test public void subsetOfPendingTransactionsIncludedWhenBlockGasLimitHit() { - final List transactionsToInject = Lists.newArrayList(); - for (int i = 0; i < 5; i++) { - final Transaction tx = createTransaction(i, Wei.of(7), 100_000); - transactionsToInject.add(tx); - ensureTransactionIsValid(tx); - } - transactionPool.addRemoteTransactions(transactionsToInject); - final ProcessableBlockHeader blockHeader = createBlock(301_000); - final Address miningBeneficiary = AddressHelpers.ofValue(1); - final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + final List transactionsToInject = Lists.newArrayList(); + for (int i = 0; i < 5; i++) { + final Transaction tx = createTransaction(i, Wei.of(7), 100_000); + transactionsToInject.add(tx); + ensureTransactionIsValid(tx); + } + transactionPool.addRemoteTransactions(transactionsToInject); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -332,16 +341,15 @@ public void subsetOfPendingTransactionsIncludedWhenBlockGasLimitHit() { @Test public void transactionTooLargeForBlockDoesNotPreventMoreBeingAddedIfBlockOccupancyNotReached() { final ProcessableBlockHeader blockHeader = createBlock(300_000); - final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); // Add 3 transactions to the Pending Transactions, 79% of block, 100% of block and 10% of block // should end up selecting the first and third only. @@ -368,16 +376,15 @@ public void transactionTooLargeForBlockDoesNotPreventMoreBeingAddedIfBlockOccupa @Test public void transactionSelectionStopsWhenSufficientBlockOccupancyIsReached() { final ProcessableBlockHeader blockHeader = createBlock(300_000); - final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); // Add 4 transactions to the Pending Transactions 15% (ok), 79% (ok), 25% (too large), 10% // (not included, it would fit, however previous transaction was too large and block was @@ -409,13 +416,14 @@ public void transactionSelectionStopsWhenBlockIsFull() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + createMiningParameters( + Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_100_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); final long minTxGasCost = getGasCalculator().getMinimumTransactionCost(); @@ -467,13 +475,14 @@ public void transactionSelectionStopsWhenRemainingGasIsNotEnoughForAnyMoreTransa final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + createMiningParameters( + Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_100_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); final long minTxGasCost = getGasCalculator().getMinimumTransactionCost(); @@ -519,13 +528,13 @@ public void shouldDiscardTransactionsThatFailValidation() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); final Transaction validTransaction = createTransaction(0, Wei.of(10), 21_000); @@ -552,6 +561,7 @@ public void shouldDiscardTransactionsThatFailValidation() { @Test public void transactionSelectionPluginShouldWork_PreProcessing() { final ProcessableBlockHeader blockHeader = createBlock(300_000); + final Address miningBeneficiary = AddressHelpers.ofValue(1); final Transaction selected = createTransaction(0, Wei.of(10), 21_000); ensureTransactionIsValid(selected, 21_000, 0); @@ -584,9 +594,9 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( } }; - final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelectorWithTxSelPlugin( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, miningBeneficiary, @@ -648,7 +658,9 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelectorWithTxSelPlugin( + createBlockSelectorAndSetupTxPool( + createMiningParameters( + Wei.ZERO, MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, @@ -678,15 +690,19 @@ public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCo final TransactionInvalidReason invalidReason = TransactionInvalidReason.PLUGIN_TX_VALIDATOR; final Transaction invalidTransaction = createTransaction(1, Wei.of(10), 21_000); ensureTransactionIsInvalid(invalidTransaction, TransactionInvalidReason.PLUGIN_TX_VALIDATOR); - transactionPool.addRemoteTransactions(List.of(transaction, invalidTransaction)); - createBlockSelectorWithTxSelPlugin( + final BlockTransactionSelector selector = + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, createBlock(300_000), AddressHelpers.ofValue(1), Wei.ZERO, - transactionSelectorFactory) - .buildTransactionListForBlock(); + transactionSelectorFactory); + + transactionPool.addRemoteTransactions(List.of(transaction, invalidTransaction)); + + selector.buildTransactionListForBlock(); ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(PendingTransaction.class); @@ -709,21 +725,20 @@ public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCo @Test public void transactionWithIncorrectNonceRemainsInPoolAndNotSelected() { final ProcessableBlockHeader blockHeader = createBlock(5_000_000); - - final Transaction futureTransaction = createTransaction(4, Wei.of(10), 100_000); - - transactionPool.addRemoteTransactions(List.of(futureTransaction)); - ensureTransactionIsInvalid(futureTransaction, TransactionInvalidReason.NONCE_TOO_HIGH); - final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + final Transaction futureTransaction = createTransaction(4, Wei.of(10), 100_000); + + transactionPool.addRemoteTransactions(List.of(futureTransaction)); + ensureTransactionIsInvalid(futureTransaction, TransactionInvalidReason.NONCE_TOO_HIGH); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -740,22 +755,25 @@ public void transactionWithIncorrectNonceRemainsInPoolAndNotSelected() { @Test public void increaseOfMinGasPriceAtRuntimeExcludeTxFromBeingSelected() { final Transaction transaction = createTransaction(0, Wei.of(7L), 100_000); - transactionPool.addRemoteTransactions(List.of(transaction)); - - ensureTransactionIsValid(transaction, 0, 5); - final ProcessableBlockHeader blockHeader = createBlock(500_000); final Address miningBeneficiary = AddressHelpers.ofValue(1); + final MiningParameters miningParameters = + ImmutableMiningParameters.builder().from(defaultTestMiningParameters).build(); + final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + miningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + transactionPool.addRemoteTransactions(List.of(transaction)); + + ensureTransactionIsValid(transaction, 0, 5); // raise the minGasPrice at runtime from 1 wei to 10 wei miningParameters.setMinTransactionGasPrice(Wei.of(10)); @@ -774,22 +792,23 @@ public void increaseOfMinGasPriceAtRuntimeExcludeTxFromBeingSelected() { @Test public void decreaseOfMinGasPriceAtRuntimeIncludeTxThatWasPreviouslyNotSelected() { final Transaction transaction = createTransaction(0, Wei.of(7L), 100_000); - transactionPool.addRemoteTransactions(List.of(transaction)); - - ensureTransactionIsValid(transaction, 0, 5); - + final MiningParameters miningParameters = + ImmutableMiningParameters.builder().from(defaultTestMiningParameters).build(); final ProcessableBlockHeader blockHeader = createBlock(500_000); final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector1 = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + miningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionPool.addRemoteTransactions(List.of(transaction)); + + ensureTransactionIsValid(transaction, 0, 5); // raise the minGasPrice at runtime from 1 wei to 10 wei miningParameters.setMinTransactionGasPrice(Wei.of(10)); @@ -809,12 +828,12 @@ public void decreaseOfMinGasPriceAtRuntimeIncludeTxThatWasPreviouslyNotSelected( final BlockTransactionSelector selector2 = createBlockSelector( + miningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); final TransactionSelectionResults results2 = selector2.buildTransactionListForBlock(); @@ -826,22 +845,25 @@ public void decreaseOfMinGasPriceAtRuntimeIncludeTxThatWasPreviouslyNotSelected( @Test public void shouldNotSelectTransactionsWithPriorityFeeLessThanConfig() { ProcessableBlockHeader blockHeader = createBlock(5_000_000, Wei.ONE); + final MiningParameters miningParameters = + ImmutableMiningParameters.builder().from(defaultTestMiningParameters).build(); miningParameters.setMinPriorityFeePerGas(Wei.of(7)); final Transaction txSelected = createTransaction(1, Wei.of(8), 100_000); ensureTransactionIsValid(txSelected); // transaction txNotSelected should not be selected final Transaction txNotSelected = createTransaction(2, Wei.of(7), 100_000); ensureTransactionIsValid(txNotSelected); - transactionPool.addRemoteTransactions(List.of(txSelected, txNotSelected)); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + miningParameters, transactionProcessor, blockHeader, - Wei.ZERO, AddressHelpers.ofValue(1), Wei.ZERO, - MIN_OCCUPANCY_100_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + transactionPool.addRemoteTransactions(List.of(txSelected, txNotSelected)); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -852,41 +874,136 @@ public void shouldNotSelectTransactionsWithPriorityFeeLessThanConfig() { txNotSelected, TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN)); } - protected BlockTransactionSelector createBlockSelector( - final MainnetTransactionProcessor transactionProcessor, - final ProcessableBlockHeader blockHeader, - final Wei minGasPrice, - final Address miningBeneficiary, - final Wei blobGasPrice, - final double minBlockOccupancyRatio) { + @ParameterizedTest + @MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver") + public void subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver( + final boolean isPoa, + final boolean preProcessingTooLate, + final boolean processingTooLate, + final boolean postProcessingTooLate) { + + final Supplier> inTime = + () -> invocation -> TransactionSelectionResult.SELECTED; + final BiFunction> tooLate = + (p, t) -> + invocation -> { + if (((PendingTransaction) invocation.getArgument(0)).getTransaction().equals(p)) { + Thread.sleep(t); + } + return TransactionSelectionResult.SELECTED; + }; + + final ProcessableBlockHeader blockHeader = createBlock(301_000); + final Address miningBeneficiary = AddressHelpers.ofValue(1); + final int poaMinBlockTime = 1; + final long blockTxsSelectionMaxTime = 750; + final long longProcessingTxTime = 500; + + final List transactionsToInject = new ArrayList<>(3); + for (int i = 0; i < 2; i++) { + final Transaction tx = createTransaction(i, Wei.of(7), 100_000); + transactionsToInject.add(tx); + ensureTransactionIsValid(tx); + } + + final Transaction lateTx = createTransaction(2, Wei.of(7), 100_000); + transactionsToInject.add(lateTx); + ensureTransactionIsValid( + lateTx, 0, 0, processingTooLate ? blockTxsSelectionMaxTime + longProcessingTxTime : 0); + + PluginTransactionSelector transactionSelector = mock(PluginTransactionSelector.class); + when(transactionSelector.evaluateTransactionPreProcessing(any())) + .thenAnswer( + preProcessingTooLate + ? inTime.get() + : tooLate.apply(lateTx, blockTxsSelectionMaxTime + longProcessingTxTime)); + + when(transactionSelector.evaluateTransactionPostProcessing(any(), any())) + .thenAnswer( + postProcessingTooLate + ? inTime.get() + : tooLate.apply(lateTx, blockTxsSelectionMaxTime + longProcessingTxTime)); + + final PluginTransactionSelectorFactory transactionSelectorFactory = + mock(PluginTransactionSelectorFactory.class); + when(transactionSelectorFactory.create()).thenReturn(transactionSelector); + final BlockTransactionSelector selector = - new BlockTransactionSelector( - miningParameters - .setMinTransactionGasPrice(minGasPrice) - .setMinBlockOccupancyRatio(minBlockOccupancyRatio), + createBlockSelectorAndSetupTxPool( + isPoa + ? createMiningParameters( + Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, poaMinBlockTime, Percentage.fromInt(75)) + : createMiningParameters( + Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, blockTxsSelectionMaxTime), transactionProcessor, - blockchain, - worldState, - transactionPool, blockHeader, - this::createReceipt, - this::isCancelled, miningBeneficiary, - blobGasPrice, - getFeeMarket(), - new LondonGasCalculator(), - GasLimitCalculator.constant(), - AllAcceptingTransactionSelector.INSTANCE); + Wei.ZERO, + transactionSelectorFactory); - return selector; + transactionPool.addRemoteTransactions(transactionsToInject); + + final TransactionSelectionResults results = selector.buildTransactionListForBlock(); + + // third tx is not selected, even if it could fit in the block, + // since the selection time was over + assertThat(results.getSelectedTransactions().size()).isEqualTo(2); + + assertThat(results.getSelectedTransactions().containsAll(transactionsToInject.subList(0, 2))) + .isTrue(); + + assertThat(results.getReceipts().size()).isEqualTo(2); + assertThat(results.getCumulativeGasUsed()).isEqualTo(200_000); + + // Ensure receipts have the correct cumulative gas + assertThat(results.getReceipts().get(0).getCumulativeGasUsed()).isEqualTo(100_000); + assertThat(results.getReceipts().get(1).getCumulativeGasUsed()).isEqualTo(200_000); + + // given enough time we can check the not selected tx + await().until(() -> !results.getNotSelectedTransactions().isEmpty()); + assertThat(results.getNotSelectedTransactions()) + .containsOnly(entry(lateTx, TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT)); + } + + private static Stream + subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver() { + + return Stream.of( + Arguments.of(false, true, false, false), + Arguments.of(false, false, true, false), + Arguments.of(false, false, false, true), + Arguments.of(true, true, false, false), + Arguments.of(true, false, true, false), + Arguments.of(true, false, false, true)); + } + + protected BlockTransactionSelector createBlockSelectorAndSetupTxPool( + final MiningParameters miningParameters, + final MainnetTransactionProcessor transactionProcessor, + final ProcessableBlockHeader blockHeader, + final Address miningBeneficiary, + final Wei blobGasPrice, + final PluginTransactionSelectorFactory transactionSelectorFactory) { + + transactionPool = createTransactionPool(miningParameters); + + return createBlockSelector( + miningParameters, + transactionProcessor, + blockHeader, + miningBeneficiary, + blobGasPrice, + transactionSelectorFactory); } - protected BlockTransactionSelector createBlockSelectorWithTxSelPlugin( + protected BlockTransactionSelector createBlockSelector( + final MiningParameters miningParameters, final MainnetTransactionProcessor transactionProcessor, final ProcessableBlockHeader blockHeader, final Address miningBeneficiary, final Wei blobGasPrice, final PluginTransactionSelectorFactory transactionSelectorFactory) { + final BlockTransactionSelector selector = new BlockTransactionSelector( miningParameters, @@ -902,7 +1019,8 @@ protected BlockTransactionSelector createBlockSelectorWithTxSelPlugin( getFeeMarket(), new LondonGasCalculator(), GasLimitCalculator.constant(), - transactionSelectorFactory.create()); + transactionSelectorFactory.create(), + ethScheduler); return selector; } @@ -965,15 +1083,28 @@ protected void ensureTransactionIsValid(final Transaction tx) { protected void ensureTransactionIsValid( final Transaction tx, final long gasUsedByTransaction, final long gasRemaining) { + ensureTransactionIsValid(tx, gasUsedByTransaction, gasRemaining, 0); + } + + protected void ensureTransactionIsValid( + final Transaction tx, + final long gasUsedByTransaction, + final long gasRemaining, + final long processingTime) { when(transactionProcessor.processTransaction( any(), any(), any(), eq(tx), any(), any(), any(), anyBoolean(), any(), any())) - .thenReturn( - TransactionProcessingResult.successful( - new ArrayList<>(), - gasUsedByTransaction, - gasRemaining, - Bytes.EMPTY, - ValidationResult.valid())); + .thenAnswer( + invocation -> { + if (processingTime > 0) { + Thread.sleep(processingTime); + } + return TransactionProcessingResult.successful( + new ArrayList<>(), + gasUsedByTransaction, + gasRemaining, + Bytes.EMPTY, + ValidationResult.valid()); + }); } protected void ensureTransactionIsInvalid( @@ -986,4 +1117,35 @@ protected void ensureTransactionIsInvalid( private BlockHeader blockHeader(final long number) { return new BlockHeaderTestFixture().number(number).buildHeader(); } + + protected MiningParameters createMiningParameters( + final Wei minGasPrice, final double minBlockOccupancyRatio, final long txsSelectionMaxTime) { + return ImmutableMiningParameters.builder() + .mutableInitValues( + MutableInitValues.builder() + .minTransactionGasPrice(minGasPrice) + .minBlockOccupancyRatio(minBlockOccupancyRatio) + .build()) + .unstable(Unstable.builder().nonPoaBlockTxsSelectionMaxTime(txsSelectionMaxTime).build()) + .build(); + } + + protected MiningParameters createMiningParameters( + final Wei minGasPrice, + final double minBlockOccupancyRatio, + final int minBlockTime, + final Percentage minBlockTimePercentage) { + return ImmutableMiningParameters.builder() + .mutableInitValues( + MutableInitValues.builder() + .minTransactionGasPrice(minGasPrice) + .minBlockOccupancyRatio(minBlockOccupancyRatio) + .build()) + .unstable( + Unstable.builder() + .minBlockTime(minBlockTime) + .poaBlockTxsSelectionMaxTime(minBlockTimePercentage) + .build()) + .build(); + } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java index 9b50bfadb8a..ba719700852 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; @@ -61,7 +62,7 @@ protected ProtocolSchedule createProtocolSchedule() { } @Override - protected TransactionPool createTransactionPool() { + protected TransactionPool createTransactionPool(final MiningParameters miningParameters) { final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(5) diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java index 1ef0fa8f2ff..3ff84afa56a 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.mockito.Mockito.mock; import org.hyperledger.besu.config.GenesisConfigFile; @@ -24,6 +25,8 @@ import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; import org.hyperledger.besu.ethereum.core.AddressHelpers; +import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; @@ -71,7 +74,7 @@ protected ProtocolSchedule createProtocolSchedule() { } @Override - protected TransactionPool createTransactionPool() { + protected TransactionPool createTransactionPool(final MiningParameters miningParameters) { final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(5) @@ -105,13 +108,14 @@ public void eip1559TransactionCurrentGasPriceLessThanMinimumIsSkippedAndKeptInTh final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + createMiningParameters( + Wei.of(6), MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, - Wei.of(6), miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); // tx is willing to pay max 7 wei for gas, but current network condition (baseFee == 1) // result in it paying 2 wei, that is below the minimum accepted by the node, so it is skipped @@ -133,13 +137,14 @@ public void eip1559TransactionCurrentGasPriceGreaterThanMinimumIsSelected() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + createMiningParameters( + Wei.of(6), MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, - Wei.of(6), miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); // tx is willing to pay max 7 wei for gas, and current network condition (baseFee == 5) // result in it paying the max, that is >= the minimum accepted by the node, so it is selected @@ -160,13 +165,14 @@ public void eip1559PriorityTransactionCurrentGasPriceLessThanMinimumIsSelected() final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + createMiningParameters( + Wei.of(6), MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, - Wei.of(6), miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); // tx is willing to pay max 7 wei for gas, but current network condition (baseFee == 1) // result in it paying 2 wei, that is below the minimum accepted by the node, but since it is @@ -192,8 +198,6 @@ public void transactionFromSameSenderWithMixedTypes() { final Transaction txFrontier2 = createTransaction(2, Wei.of(7L), 100_000); final Transaction txLondon2 = createEIP1559Transaction(3, Wei.ONE, Wei.ONE, 100_000); - transactionPool.addRemoteTransactions(List.of(txFrontier1, txLondon1, txFrontier2, txLondon2)); - ensureTransactionIsValid(txFrontier1); ensureTransactionIsValid(txLondon1); ensureTransactionIsValid(txFrontier2); @@ -201,13 +205,15 @@ public void transactionFromSameSenderWithMixedTypes() { final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + defaultTestMiningParameters, transactionProcessor, blockHeader, - Wei.ZERO, miningBeneficiary, Wei.ZERO, - MIN_OCCUPANCY_80_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + transactionPool.addRemoteTransactions(List.of(txFrontier1, txLondon1, txFrontier2, txLondon2)); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -220,6 +226,8 @@ public void transactionFromSameSenderWithMixedTypes() { @Override public void shouldNotSelectTransactionsWithPriorityFeeLessThanConfig() { ProcessableBlockHeader blockHeader = createBlock(5_000_000, Wei.ONE); + final MiningParameters miningParameters = + ImmutableMiningParameters.builder().from(defaultTestMiningParameters).build(); miningParameters.setMinPriorityFeePerGas(Wei.of(7)); final Transaction txSelected1 = createEIP1559Transaction(1, Wei.of(8), Wei.of(8), 100_000); @@ -237,19 +245,19 @@ public void shouldNotSelectTransactionsWithPriorityFeeLessThanConfig() { final Transaction txNotSelected2 = createEIP1559Transaction(4, Wei.of(8), Wei.of(6), 100_000); ensureTransactionIsValid(txNotSelected2); - transactionPool.addRemoteTransactions( - List.of(txSelected1, txNotSelected1, txSelected2, txNotSelected2)); - - assertThat(transactionPool.getPendingTransactions().size()).isEqualTo(4); - final BlockTransactionSelector selector = - createBlockSelector( + createBlockSelectorAndSetupTxPool( + miningParameters, transactionProcessor, blockHeader, - Wei.ZERO, AddressHelpers.ofValue(1), Wei.ZERO, - MIN_OCCUPANCY_100_PERCENT); + NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + + transactionPool.addRemoteTransactions( + List.of(txSelected1, txNotSelected1, txSelected2, txNotSelected2)); + + assertThat(transactionPool.getPendingTransactions().size()).isEqualTo(4); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java index 80c0a3d4426..2aef4a15a7f 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java @@ -115,7 +115,8 @@ void createMainnetBlock1() throws IOException { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader()); + executionContextTestFixture.getBlockchain().getChainHeadHeader(), + ethScheduler); // A Hashrate should not exist in the block creator prior to creating a block assertThat(blockCreator.getHashesPerSecond()).isNotPresent(); @@ -168,7 +169,8 @@ void createMainnetBlock1_fixedDifficulty1() { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader()); + executionContextTestFixture.getBlockchain().getChainHeadHeader(), + ethScheduler); assertThat(blockCreator.createBlock(BLOCK_1_TIMESTAMP)).isNotNull(); // If we weren't setting difficulty to 2^256-1 a difficulty of 1 would have caused a @@ -212,7 +214,8 @@ void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader()); + executionContextTestFixture.getBlockchain().getChainHeadHeader(), + ethScheduler); final MutableWorldState mutableWorldState = executionContextTestFixture.getStateArchive().getMutable(); @@ -278,7 +281,8 @@ void rewardBeneficiary_zeroReward_skipZeroRewardsTrue() { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader()); + executionContextTestFixture.getBlockchain().getChainHeadHeader(), + ethScheduler); final MutableWorldState mutableWorldState = executionContextTestFixture.getStateArchive().getMutable(); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java index b074e2146a8..d6b54606e5f 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -34,6 +35,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import org.hyperledger.besu.util.Subscribers; @@ -44,6 +46,7 @@ public class PoWMinerExecutorTest { private final MetricsSystem metricsSystem = new NoOpMetricsSystem(); + private final EthScheduler ethScheduler = new DeterministicEthScheduler(); @Test public void startingMiningWithoutCoinbaseThrowsException() { @@ -58,7 +61,8 @@ public void startingMiningWithoutCoinbaseThrowsException() { transactionPool, miningParameters, new DefaultBlockScheduler(1, 10, TestClock.fixed()), - new EpochCalculator.DefaultEpochCalculator()); + new EpochCalculator.DefaultEpochCalculator(), + ethScheduler); assertThatExceptionOfType(CoinbaseNotSetException.class) .isThrownBy(() -> executor.startAsyncMining(Subscribers.create(), Subscribers.none(), null)) @@ -78,7 +82,8 @@ public void settingCoinbaseToNullThrowsException() { transactionPool, miningParameters, new DefaultBlockScheduler(1, 10, TestClock.fixed()), - new EpochCalculator.DefaultEpochCalculator()); + new EpochCalculator.DefaultEpochCalculator(), + ethScheduler); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> executor.setCoinbase(null)) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java index 2f273f265ea..32ac5ee926a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java @@ -16,12 +16,16 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.util.number.Percentage; import java.time.Duration; import java.util.Objects; import java.util.Optional; +import java.util.OptionalInt; import java.util.OptionalLong; +import java.util.concurrent.TimeUnit; +import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.immutables.value.Value; @@ -34,6 +38,7 @@ public abstract class MiningParameters { ImmutableMiningParameters.MutableInitValues.builder().isMiningEnabled(false).build()) .build(); + @VisibleForTesting public static final MiningParameters newDefault() { return ImmutableMiningParameters.builder().build(); } @@ -261,6 +266,8 @@ public interface Unstable { int DEFAULT_MAX_OMMERS_DEPTH = 8; long DEFAULT_POS_BLOCK_CREATION_MAX_TIME = Duration.ofSeconds(12).toMillis(); long DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION = Duration.ofMillis(500).toMillis(); + long DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME = Duration.ofSeconds(5).toMillis(); + Percentage DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME = Percentage.fromInt(75); MiningParameters.Unstable DEFAULT = ImmutableMiningParameters.Unstable.builder().build(); @@ -298,5 +305,27 @@ default long getPosBlockCreationRepetitionMinDuration() { default String getStratumExtranonce() { return "080c"; } + + @Value.Default + default long getNonPoaBlockTxsSelectionMaxTime() { + return DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; + } + + @Value.Default + default Percentage getPoaBlockTxsSelectionMaxTime() { + return DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; + } + + OptionalInt getMinBlockTime(); + + @Value.Derived + default long getBlockTxsSelectionMaxTime() { + if (getMinBlockTime().isPresent()) { + return (TimeUnit.SECONDS.toMillis(getMinBlockTime().getAsInt()) + * getPoaBlockTxsSelectionMaxTime().getValue()) + / 100; + } + return getNonPoaBlockTxsSelectionMaxTime(); + } } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java index 9da6cbc7fcc..62ce7a56d79 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java @@ -49,6 +49,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; @@ -72,6 +73,7 @@ import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.nio.file.Path; import java.util.Arrays; @@ -92,6 +94,7 @@ public abstract class AbstractIsolationTests { protected BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorage; protected ProtocolContext protocolContext; protected EthContext ethContext; + protected EthScheduler ethScheduler = new DeterministicEthScheduler(); final Function asKeyPair = key -> SignatureAlgorithmFactory.getInstance() @@ -214,7 +217,8 @@ private TestBlockCreator( final TransactionPool transactionPool, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final BlockHeader parentHeader) { + final BlockHeader parentHeader, + final EthScheduler ethScheduler) { super( miningParameters, miningBeneficiaryCalculator, @@ -223,14 +227,16 @@ private TestBlockCreator( protocolContext, protocolSchedule, parentHeader, - Optional.empty()); + Optional.empty(), + ethScheduler); } static TestBlockCreator forHeader( final BlockHeader parentHeader, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final TransactionPool transactionPool) { + final TransactionPool transactionPool, + final EthScheduler ethScheduler) { final MiningParameters miningParameters = ImmutableMiningParameters.builder() @@ -251,7 +257,8 @@ static TestBlockCreator forHeader( transactionPool, protocolContext, protocolSchedule, - parentHeader); + parentHeader, + ethScheduler); } @Override @@ -282,7 +289,8 @@ protected Block forTransactions(final List transactions) { protected Block forTransactions( final List transactions, final BlockHeader forHeader) { - return TestBlockCreator.forHeader(forHeader, protocolContext, protocolSchedule, transactionPool) + return TestBlockCreator.forHeader( + forHeader, protocolContext, protocolSchedule, transactionPool, ethScheduler) .createBlock(transactions, Collections.emptyList(), System.currentTimeMillis()) .getBlock(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java index 9df6edb2c9d..43f8d6a4c35 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java @@ -53,6 +53,7 @@ public class EthScheduler { protected final ExecutorService txWorkerExecutor; protected final ExecutorService servicesExecutor; protected final ExecutorService computationExecutor; + protected final ExecutorService blockCreationExecutor; private final Collection> pendingFutures = new ConcurrentLinkedDeque<>(); @@ -87,7 +88,9 @@ public EthScheduler( EthScheduler.class.getSimpleName() + "-Computation", 1, computationWorkerCount, - metricsSystem)); + metricsSystem), + MonitoredExecutors.newCachedThreadPool( + EthScheduler.class.getSimpleName() + "-BlockCreation", metricsSystem)); } protected EthScheduler( @@ -95,12 +98,14 @@ protected EthScheduler( final ScheduledExecutorService scheduler, final ExecutorService txWorkerExecutor, final ExecutorService servicesExecutor, - final ExecutorService computationExecutor) { + final ExecutorService computationExecutor, + final ExecutorService blockCreationExecutor) { this.syncWorkerExecutor = syncWorkerExecutor; this.scheduler = scheduler; this.txWorkerExecutor = txWorkerExecutor; this.servicesExecutor = servicesExecutor; this.computationExecutor = computationExecutor; + this.blockCreationExecutor = blockCreationExecutor; } public CompletableFuture scheduleSyncWorkerTask( @@ -202,6 +207,10 @@ public CompletableFuture scheduleFutureTask( return promise; } + public CompletableFuture scheduleBlockCreationTask(final Runnable task) { + return CompletableFuture.runAsync(task, blockCreationExecutor); + } + public CompletableFuture timeout(final EthTask task) { return timeout(task, defaultTimeout); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java index 8884eefdd6e..a218f3483c0 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java @@ -1088,8 +1088,9 @@ public void transactionMessagesGoToTheCorrectExecutor() { final ExecutorService transactions = mock(ExecutorService.class); final ExecutorService services = mock(ExecutorService.class); final ExecutorService computations = mock(ExecutorService.class); + final ExecutorService blockCreation = mock(ExecutorService.class); final EthScheduler ethScheduler = - new EthScheduler(worker, scheduled, transactions, services, computations); + new EthScheduler(worker, scheduled, transactions, services, computations, blockCreation); // Create the fake TransactionMessage to feed to the EthManager. final BlockDataGenerator gen = new BlockDataGenerator(1); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java index 8bfb9db92e4..5f970e44dc5 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -27,7 +27,6 @@ import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; -import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler.TimeoutPolicy; import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; @@ -39,6 +38,8 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; +import org.hyperledger.besu.testutil.DeterministicEthScheduler.TimeoutPolicy; import org.hyperledger.besu.testutil.TestClock; import java.math.BigInteger; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerShutdownTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerShutdownTest.java index 714021b17fb..649121b3d00 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerShutdownTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerShutdownTest.java @@ -32,6 +32,7 @@ public class EthSchedulerShutdownTest { private ExecutorService txWorkerExecutor; private ExecutorService servicesExecutor; private ExecutorService computationExecutor; + private ExecutorService blockCreationExecutor; @BeforeEach public void setup() { @@ -40,13 +41,15 @@ public void setup() { txWorkerExecutor = Executors.newSingleThreadExecutor(); servicesExecutor = Executors.newSingleThreadExecutor(); computationExecutor = Executors.newSingleThreadExecutor(); + blockCreationExecutor = Executors.newSingleThreadExecutor(); ethScheduler = new EthScheduler( syncWorkerExecutor, scheduledExecutor, txWorkerExecutor, servicesExecutor, - computationExecutor); + computationExecutor, + blockCreationExecutor); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java index 7ce43a0c5f8..d09f02c6b76 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java @@ -21,7 +21,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.MockExecutorService; +import org.hyperledger.besu.testutil.MockScheduledExecutor; import java.time.Duration; import java.util.concurrent.CompletableFuture; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java index 88f5457a121..93614405549 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java @@ -28,7 +28,6 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; -import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; @@ -46,6 +45,7 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; import java.time.ZoneId; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java index 34bb284c164..1781428531a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java @@ -22,7 +22,6 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; -import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -30,6 +29,7 @@ import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.GetBlockHeadersMessage; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.List; import java.util.concurrent.CompletableFuture; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java index 8b77d1ac2a4..19c4a1b4e2e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java @@ -29,7 +29,6 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -39,6 +38,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.nio.charset.StandardCharsets; import java.util.List; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java index e69a56e498a..6d352abb7a8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java @@ -34,7 +34,6 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; -import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; @@ -68,6 +67,7 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.MockExecutorService; import org.hyperledger.besu.testutil.TestClock; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java index c4adf1267f7..2fa778ad5d4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java @@ -22,11 +22,11 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.eth.manager.DeterministicEthScheduler; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncActions; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.OptionalLong; diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java index a467248940f..dfe26f5c3ce 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java @@ -290,6 +290,14 @@ public ProtocolContext getProtocolContext() { return protocolContext; } + public EthScheduler getEthScheduler() { + return ethScheduler; + } + + public void setEthScheduler(final EthScheduler ethScheduler) { + this.ethScheduler = ethScheduler; + } + public long getBlockHeight() { return blockchain.getChainHeadBlockNumber(); } diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java index 6c3fddae1c7..3060b326bb8 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java @@ -69,7 +69,8 @@ private boolean mineNewBlock() { protocolContext, protocolSchedule, context.getEthHashSolver(), - blockchain.getChainHeadHeader()); + blockchain.getChainHeadHeader(), + context.getEthScheduler()); final Block block = blockCreator.createBlock(retesethClock.instant().getEpochSecond()).getBlock(); diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 94b2ad1461f..6db2af92a14 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'n4WfeMvltN4XWGtztd8ABjSU2TLiI3tk5yABivkgsFA=' + knownHash = '7Aj0APsKs1wBVqaWQFdEs85/MNKxTiVzyjIeZ+zCWlw=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java index 0cdaea997e2..1a397259e6e 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java @@ -28,6 +28,7 @@ private enum Status { SELECTED, BLOCK_FULL(true, false), BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false), + BLOCK_SELECTION_TIMEOUT(true, false), INVALID_TRANSIENT(false, false), INVALID(false, true); @@ -56,6 +57,11 @@ public String toString() { /** The transaction has not been selected since the block is full. */ public static final TransactionSelectionResult BLOCK_FULL = new TransactionSelectionResult(Status.BLOCK_FULL); + /** There was no more time to add transaction to the block */ + public static final TransactionSelectionResult BLOCK_SELECTION_TIMEOUT = + new TransactionSelectionResult(Status.BLOCK_SELECTION_TIMEOUT); + ; + /** * The transaction has not been selected since too large and the occupancy of the block is enough * to stop the selection. diff --git a/testutil/build.gradle b/testutil/build.gradle index 78cf879aca4..491c213207a 100644 --- a/testutil/build.gradle +++ b/testutil/build.gradle @@ -28,6 +28,7 @@ jar { } dependencies { + implementation project(':ethereum:eth') implementation project(':plugin-api') implementation project(':util') diff --git a/ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/DeterministicEthScheduler.java b/testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java similarity index 68% rename from ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/DeterministicEthScheduler.java rename to testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java index 839bb9b2a1d..6959816a128 100644 --- a/ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/DeterministicEthScheduler.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java @@ -12,9 +12,9 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.ethereum.eth.manager; +package org.hyperledger.besu.testutil; -import org.hyperledger.besu.testutil.MockExecutorService; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import java.time.Duration; import java.util.ArrayList; @@ -32,16 +32,23 @@ public class DeterministicEthScheduler extends EthScheduler { private final List executors; private final List> pendingTimeouts = new ArrayList<>(); + /** Create a new deterministic scheduler that never timeouts */ public DeterministicEthScheduler() { this(TimeoutPolicy.NEVER_TIMEOUT); } + /** + * Create a new deterministic scheduler with the provided timeout policy + * + * @param timeoutPolicy the timeout policy + */ public DeterministicEthScheduler(final TimeoutPolicy timeoutPolicy) { super( new MockExecutorService(), new MockScheduledExecutor(), new MockExecutorService(), new MockExecutorService(), + new MockExecutorService(), new MockExecutorService()); this.timeoutPolicy = timeoutPolicy; @@ -51,40 +58,72 @@ public DeterministicEthScheduler(final TimeoutPolicy timeoutPolicy) { (MockExecutorService) this.scheduler, (MockExecutorService) this.txWorkerExecutor, (MockExecutorService) this.servicesExecutor, - (MockExecutorService) this.computationExecutor); + (MockExecutorService) this.computationExecutor, + (MockExecutorService) this.blockCreationExecutor); } - // Test utility for running pending futures + /** Test utility for manually running pending futures, when autorun is disabled */ public void runPendingFutures() { executors.forEach(MockExecutorService::runPendingFutures); } + /** + * Get the count of pending tasks + * + * @return the count of pending tasks + */ public long getPendingFuturesCount() { return executors.stream().mapToLong(MockExecutorService::getPendingFuturesCount).sum(); } + /** Expire all pending timeouts */ public void expirePendingTimeouts() { final List> toExpire = new ArrayList<>(pendingTimeouts); pendingTimeouts.clear(); toExpire.forEach(PendingTimeout::expire); } + /** Do not automatically run submitted tasks. Tasks can be later run using runPendingFutures */ public void disableAutoRun() { executors.forEach(e -> e.setAutoRun(false)); } - MockExecutorService mockSyncWorkerExecutor() { + /** + * Get the sync worker mock executor + * + * @return the mock executor + */ + public MockExecutorService mockSyncWorkerExecutor() { return (MockExecutorService) syncWorkerExecutor; } - MockScheduledExecutor mockScheduledExecutor() { + /** + * Get the scheduled mock executor + * + * @return the mock executor + */ + public MockScheduledExecutor mockScheduledExecutor() { return (MockScheduledExecutor) scheduler; } + /** + * Get the service mock executor + * + * @return the mock executor + */ public MockExecutorService mockServiceExecutor() { return (MockExecutorService) servicesExecutor; } + /** + * Get the block creation mock executor + * + * @return the mock executor + */ + public MockExecutorService mockBlockCreationExecutor() { + return (MockExecutorService) blockCreationExecutor; + } + @Override public void failAfterTimeout(final CompletableFuture promise, final Duration timeout) { final PendingTimeout pendingTimeout = new PendingTimeout<>(promise, timeout); @@ -95,13 +134,27 @@ public void failAfterTimeout(final CompletableFuture promise, final Durat } } + /** Used to define the timeout behavior of the scheduler */ @FunctionalInterface public interface TimeoutPolicy { + /** A policy that never timeouts */ TimeoutPolicy NEVER_TIMEOUT = () -> false; + /** A policy that timeouts on every task */ TimeoutPolicy ALWAYS_TIMEOUT = () -> true; + /** + * If it should simulate a timeout when called + * + * @return true if the scheduler should timeouts + */ boolean shouldTimeout(); + /** + * Create a timeout policy that timeouts x times + * + * @param times the number of timeouts + * @return the timeout policy + */ static TimeoutPolicy timeoutXTimes(final int times) { final AtomicInteger timeouts = new AtomicInteger(times); return () -> { diff --git a/ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/MockScheduledExecutor.java b/testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java similarity index 96% rename from ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/MockScheduledExecutor.java rename to testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java index 4ae15152397..fbcb5f1a065 100644 --- a/ethereum/eth/src/test-support/java/org/hyperledger/besu/ethereum/eth/manager/MockScheduledExecutor.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java @@ -12,9 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.ethereum.eth.manager; - -import org.hyperledger.besu.testutil.MockExecutorService; +package org.hyperledger.besu.testutil; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; @@ -25,6 +23,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +/** The mock scheduled executor */ public class MockScheduledExecutor extends MockExecutorService implements ScheduledExecutorService { @Override