From 46b5a5c0a5625cd7e19d1bbab37c47861fba02b4 Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Mon, 11 Sep 2023 12:16:18 +0200 Subject: [PATCH] Layered txpool by default and txpool options hoverhaul (#5772) Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 6 +- .../org/hyperledger/besu/cli/BesuCommand.java | 127 ++------- .../cli/ConfigurationOverviewBuilder.java | 15 +- .../besu/cli/DefaultCommandValues.java | 4 - .../converter/DurationMillisConverter.java | 44 +++ .../besu/cli/converter/FractionConverter.java | 6 +- .../cli/converter/PercentageConverter.java | 6 +- .../besu/cli/converter/TypeFormatter.java | 31 +++ .../DurationConversionException.java | 39 +++ .../besu/cli/options/OptionParser.java | 45 +++ .../stable/TransactionPoolOptions.java | 263 ++++++++++++++++++ .../unstable/TransactionPoolOptions.java | 122 ++------ .../besu/cli/util/CommandLineUtils.java | 104 +++++++ .../util/TomlConfigFileDefaultProvider.java | 3 + .../hyperledger/besu/cli/BesuCommandTest.java | 187 ------------- .../besu/cli/CommandTestAbstract.java | 7 +- .../cli/ConfigurationOverviewBuilderTest.java | 22 +- .../cli/converter/FractionConverterTest.java | 5 +- .../converter/PercentageConverterTest.java | 5 +- .../cli/options/AbstractCLIOptionsTest.java | 27 +- .../cli/options/EthProtocolOptionsTest.java | 9 +- .../cli/options/MetricsCLIOptionsTest.java | 9 +- .../cli/options/NetworkingOptionsTest.java | 8 +- .../besu/cli/options/OptionParserTest.java | 21 ++ .../cli/options/SynchronizerOptionsTest.java | 8 +- .../options/TransactionPoolOptionsTest.java | 155 ----------- .../stable/TransactionPoolOptionsTest.java | 243 ++++++++++++++++ .../unstable/TransactionPoolOptionsTest.java | 105 +++++++ .../controller/BesuControllerBuilderTest.java | 11 +- .../MergeBesuControllerBuilderTest.java | 11 +- .../QbftBesuControllerBuilderTest.java | 11 +- .../src/test/resources/everything_config.toml | 13 +- .../blockcreation/MergeCoordinatorTest.java | 3 +- ...FeeMarketBlockTransactionSelectorTest.java | 3 +- ...FeeMarketBlockTransactionSelectorTest.java | 3 +- ...oledTransactionHashesMessageProcessor.java | 8 +- .../TransactionPoolConfiguration.java | 57 ++-- .../transactions/TransactionPoolFactory.java | 22 +- .../layered/AbstractTransactionsLayer.java | 1 + .../BaseFeePrioritizedTransactions.java | 24 +- ...TransactionHashesMessageProcessorTest.java | 8 +- .../TransactionPoolFactoryTest.java | 53 +++- .../TransactionPoolLegacyTest.java | 3 +- .../TransactionPoolLondonTest.java | 3 +- .../AbstractPendingTransactionsTestBase.java | 12 +- .../ethereum/retesteth/RetestethContext.java | 3 +- 46 files changed, 1184 insertions(+), 691 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java create mode 100644 besu/src/main/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptions.java delete mode 100644 besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java create mode 100644 besu/src/test/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptionsTest.java create mode 100644 besu/src/test/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptionsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1c9f18513..6ed641a1b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,15 @@ ### Breaking Changes - Removed support for Kotti network (ETC) [#5816](https://github.com/hyperledger/besu/pull/5816) +- Layered transaction pool implementation is now stable and enabled by default, so the following changes to experimental options have been done [#5772](https://github.com/hyperledger/besu): + - `--Xlayered-tx-pool` is gone, to select the implementation use the new `--tx-pool` option with values `layered` (default) or `legacy` + - `--Xlayered-tx-pool-layer-max-capacity`, `--Xlayered-tx-pool-max-prioritized` and `--Xlayered-tx-pool-max-future-by-sender` just drop the `X` and keep the same behavior ### Additions and Improvements +- Layered transaction pool implementation is now stable and enabled by default. If you want still to use the legacy implementation, use `--tx-pool=legacy` [#5772](https://github.com/hyperledger/besu) ### Bug Fixes -- do not create ignorable storage on revert storage-variables subcommand [#5830](https://github.com/hyperledger/besu/pull/5830) +- do not create ignorable storage on revert storage-variables subcommand [#5830](https://github.com/hyperledger/besu/pull/5830) ### Download Links 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 9ccf10df966..2538a1d7056 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -46,7 +46,6 @@ import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; -import org.hyperledger.besu.cli.converter.FractionConverter; import org.hyperledger.besu.cli.converter.MetricCategoryConverter; import org.hyperledger.besu.cli.converter.PercentageConverter; import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; @@ -59,6 +58,7 @@ import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption; import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions; +import org.hyperledger.besu.cli.options.stable.TransactionPoolOptions; import org.hyperledger.besu.cli.options.unstable.ChainPruningOptions; import org.hyperledger.besu.cli.options.unstable.DnsOptions; import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions; @@ -73,7 +73,6 @@ import org.hyperledger.besu.cli.options.unstable.PrivacyPluginOptions; import org.hyperledger.besu.cli.options.unstable.RPCOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; -import org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions; import org.hyperledger.besu.cli.presynctasks.PreSynchronizationTaskRunner; import org.hyperledger.besu.cli.presynctasks.PrivateDatabaseMigrationPreSyncTask; import org.hyperledger.besu.cli.subcommands.PasswordSubCommand; @@ -127,6 +126,7 @@ import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.mainnet.FrontierTargetingGasLimitCalculator; import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; @@ -280,7 +280,9 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final SynchronizerOptions unstableSynchronizerOptions = SynchronizerOptions.create(); final EthProtocolOptions unstableEthProtocolOptions = EthProtocolOptions.create(); final MetricsCLIOptions unstableMetricsCLIOptions = MetricsCLIOptions.create(); - final TransactionPoolOptions unstableTransactionPoolOptions = TransactionPoolOptions.create(); + final org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions + unstableTransactionPoolOptions = + org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions.create(); private final DnsOptions unstableDnsOptions = DnsOptions.create(); private final MiningOptions unstableMiningOptions = MiningOptions.create(); private final NatOptions unstableNatOptions = NatOptions.create(); @@ -298,6 +300,10 @@ public class BesuCommand implements DefaultCommandValues, Runnable { NodePrivateKeyFileOption.create(); private final LoggingLevelOption loggingLevelOption = LoggingLevelOption.create(); + @CommandLine.ArgGroup(validate = false, heading = "@|bold Tx Pool Common Options|@%n") + final org.hyperledger.besu.cli.options.stable.TransactionPoolOptions + stableTransactionPoolOptions = TransactionPoolOptions.create(); + private final RunnerBuilder runnerBuilder; private final BesuController.Builder controllerBuilderFactory; private final BesuPluginContextImpl besuPluginContext; @@ -454,10 +460,8 @@ static class P2PDiscoveryOptionGroup { "The maximum percentage of P2P connections that can be initiated remotely. Must be between 0 and 100 inclusive. (default: ${DEFAULT-VALUE})", arity = "1", converter = PercentageConverter.class) - private final Integer maxRemoteConnectionsPercentage = - Fraction.fromFloat(DEFAULT_FRACTION_REMOTE_WIRE_CONNECTIONS_ALLOWED) - .toPercentage() - .getValue(); + private final Percentage maxRemoteConnectionsPercentage = + Fraction.fromFloat(DEFAULT_FRACTION_REMOTE_WIRE_CONNECTIONS_ALLOWED).toPercentage(); @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. @CommandLine.Option( @@ -1111,13 +1115,6 @@ static class MinerOptionGroup { arity = "1") private final Wei minTransactionGasPrice = DEFAULT_MIN_TRANSACTION_GAS_PRICE; - @Option( - names = {"--rpc-tx-feecap"}, - description = - "Maximum transaction fees (in Wei) accepted for transaction submitted through RPC (default: ${DEFAULT-VALUE})", - arity = "1") - private final Wei txFeeCap = DEFAULT_RPC_TX_FEE_CAP; - @Option( names = {"--min-block-occupancy-ratio"}, description = "Minimum occupancy ratio for a mined block (default: ${DEFAULT-VALUE})", @@ -1209,75 +1206,6 @@ static class PermissionsOptionGroup { "Sets target gas limit per block. If set, each block's gas limit will approach this setting over time if the current gas limit is different.") private final Long targetGasLimit = null; - // Tx Pool Option Group - @CommandLine.ArgGroup(validate = false, heading = "@|bold Tx Pool Options|@%n") - TxPoolOptionGroup txPoolOptionGroup = new TxPoolOptionGroup(); - - static class TxPoolOptionGroup { - @CommandLine.Option( - names = {"--tx-pool-disable-locals"}, - paramLabel = "", - description = - "Set to true if transactions sent via RPC should have the same checks and not be prioritized over remote ones (default: ${DEFAULT-VALUE})", - fallbackValue = "true", - arity = "0..1") - private Boolean disableLocalTxs = TransactionPoolConfiguration.DEFAULT_DISABLE_LOCAL_TXS; - - @CommandLine.Option( - names = {"--tx-pool-enable-save-restore"}, - paramLabel = "", - description = - "Set to true to enable saving the txpool content to file on shutdown and reloading it on startup (default: ${DEFAULT-VALUE})", - fallbackValue = "true", - arity = "0..1") - private Boolean saveRestoreEnabled = TransactionPoolConfiguration.DEFAULT_ENABLE_SAVE_RESTORE; - - @CommandLine.Option( - names = {"--tx-pool-limit-by-account-percentage"}, - paramLabel = "", - converter = FractionConverter.class, - description = - "Maximum portion of the transaction pool which a single account may occupy with future transactions (default: ${DEFAULT-VALUE})", - arity = "1") - private Float txPoolLimitByAccountPercentage = - TransactionPoolConfiguration.DEFAULT_LIMIT_TX_POOL_BY_ACCOUNT_PERCENTAGE; - - @CommandLine.Option( - names = {"--tx-pool-save-file"}, - paramLabel = "", - description = - "If saving the txpool content is enabled, define a custom path for the save file (default: ${DEFAULT-VALUE} in the data-dir)", - arity = "1") - private File saveFile = TransactionPoolConfiguration.DEFAULT_SAVE_FILE; - - @Option( - names = {"--tx-pool-max-size"}, - paramLabel = MANDATORY_INTEGER_FORMAT_HELP, - description = - "Maximum number of pending transactions that will be kept in the transaction pool (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer txPoolMaxSize = - TransactionPoolConfiguration.DEFAULT_MAX_PENDING_TRANSACTIONS; - - @Option( - names = {"--tx-pool-retention-hours"}, - paramLabel = MANDATORY_INTEGER_FORMAT_HELP, - description = - "Maximum retention period of pending transactions in hours (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer pendingTxRetentionPeriod = - TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS; - - @Option( - names = {"--tx-pool-price-bump"}, - paramLabel = MANDATORY_INTEGER_FORMAT_HELP, - converter = PercentageConverter.class, - description = - "Price bump percentage to replace an already existing transaction (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer priceBump = TransactionPoolConfiguration.DEFAULT_PRICE_BUMP.getValue(); - } - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. @Option( names = {"--key-value-storage"}, @@ -1906,10 +1834,15 @@ private void validateOptions() { validateRpcOptionsParams(); validateChainDataPruningParams(); validatePostMergeCheckpointBlockRequirements(); + validateTransactionPoolOptions(); p2pTLSConfigOptions.checkP2PTLSOptionsDependencies(logger, commandLine); pkiBlockCreationOptions.checkPkiBlockCreationOptionsDependencies(logger, commandLine); } + private void validateTransactionPoolOptions() { + stableTransactionPoolOptions.validate(commandLine); + } + private void validateRequiredOptions() { commandLine .getCommandSpec() @@ -2987,28 +2920,14 @@ private SynchronizerConfiguration buildSyncConfig() { } private TransactionPoolConfiguration buildTransactionPoolConfiguration() { - return unstableTransactionPoolOptions - .toDomainObject() - .enableSaveRestore(txPoolOptionGroup.saveRestoreEnabled) - .disableLocalTransactions(txPoolOptionGroup.disableLocalTxs) - .txPoolLimitByAccountPercentage(txPoolOptionGroup.txPoolLimitByAccountPercentage) - .txPoolMaxSize(txPoolOptionGroup.txPoolMaxSize) - .pendingTxRetentionPeriod(txPoolOptionGroup.pendingTxRetentionPeriod) - .priceBump(Percentage.fromInt(txPoolOptionGroup.priceBump)) - .txFeeCap(txFeeCap) - .saveFile(dataPath.resolve(txPoolOptionGroup.saveFile.getPath()).toFile()) + final var stableTxPoolOption = stableTransactionPoolOptions.toDomainObject(); + return ImmutableTransactionPoolConfiguration.builder() + .from(stableTxPoolOption) + .unstable(unstableTransactionPoolOptions.toDomainObject()) + .saveFile((dataPath.resolve(stableTxPoolOption.getSaveFile().getPath()).toFile())) .build(); } - /** - * Return the file where to save txpool content if the relative option is enabled. - * - * @return the save file - */ - public File getSaveFile() { - return txPoolOptionGroup.saveFile; - } - private boolean isPruningEnabled() { return pruningEnabled; } @@ -3613,9 +3532,7 @@ private String generateConfigurationOverview() { builder.setHighSpecEnabled(); } - if (buildTransactionPoolConfiguration().getLayeredTxPoolEnabled()) { - builder.setLayeredTxPoolEnabled(); - } + builder.setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation()); return builder.build(); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java index 2cd374a2f57..5bd1d84a298 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.cli; import org.hyperledger.besu.BesuInfo; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.util.log.FramedLogMessage; import org.hyperledger.besu.util.platform.PlatformDetector; @@ -47,7 +48,7 @@ public class ConfigurationOverviewBuilder { private Collection engineApis; private String engineJwtFilePath; private boolean isHighSpec = false; - private boolean isLayeredTxPool = false; + private TransactionPoolConfiguration.Implementation txPoolImplementation; private Map environment; /** @@ -167,12 +168,14 @@ public ConfigurationOverviewBuilder setHighSpecEnabled() { } /** - * Sets experimental layered txpool enabled. + * Sets the txpool implementation in use. * + * @param implementation the txpool implementation * @return the builder */ - public ConfigurationOverviewBuilder setLayeredTxPoolEnabled() { - isLayeredTxPool = true; + public ConfigurationOverviewBuilder setTxPoolImplementation( + final TransactionPoolConfiguration.Implementation implementation) { + txPoolImplementation = implementation; return this; } @@ -251,9 +254,7 @@ public String build() { lines.add("Experimental high spec configuration enabled"); } - if (isLayeredTxPool) { - lines.add("Experimental layered transaction pool configuration enabled"); - } + lines.add("Using " + txPoolImplementation + " transaction pool implementation"); lines.add(""); lines.add("Host:"); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java index 887e6120419..8b837268327 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java @@ -16,7 +16,6 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; import org.hyperledger.besu.nat.NatMethod; @@ -61,9 +60,6 @@ public interface DefaultCommandValues { String MANDATORY_NODE_ID_FORMAT_HELP = ""; /** The constant DEFAULT_MIN_TRANSACTION_GAS_PRICE. */ Wei DEFAULT_MIN_TRANSACTION_GAS_PRICE = Wei.of(1000); - /** The constant DEFAULT_RPC_TX_FEE_CAP. */ - Wei DEFAULT_RPC_TX_FEE_CAP = TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP; - /** The constant DEFAULT_MIN_BLOCK_OCCUPANCY_RATIO. */ Double DEFAULT_MIN_BLOCK_OCCUPANCY_RATIO = 0.8; /** The constant DEFAULT_EXTRA_DATA. */ diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java new file mode 100644 index 00000000000..bfaffa8826a --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java @@ -0,0 +1,44 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter; + +import org.hyperledger.besu.cli.converter.exception.DurationConversionException; + +import java.time.Duration; + +import picocli.CommandLine; + +/** The Duration (milliseconds) Cli type converter. */ +public class DurationMillisConverter + implements CommandLine.ITypeConverter, TypeFormatter { + + @Override + public Duration convert(final String value) throws DurationConversionException { + try { + final long millis = Long.parseLong(value); + if (millis < 0) { + throw new DurationConversionException(millis); + } + return Duration.ofMillis(Long.parseLong(value)); + } catch (NullPointerException | IllegalArgumentException e) { + throw new DurationConversionException(value); + } + } + + @Override + public String format(final Duration value) { + return Long.toString(value.toMillis()); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java index 969a6be8e9e..64edf7cd9ce 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java @@ -20,12 +20,12 @@ import picocli.CommandLine; /** The Fraction converter to convert floats in CLI. */ -public class FractionConverter implements CommandLine.ITypeConverter { +public class FractionConverter implements CommandLine.ITypeConverter { @Override - public Float convert(final String value) throws FractionConversionException { + public Fraction convert(final String value) throws FractionConversionException { try { - return Fraction.fromString(value).getValue(); + return Fraction.fromString(value); } catch (final NullPointerException | IllegalArgumentException e) { throw new FractionConversionException(value); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java index 23a2d94d33d..410ec83a21d 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java @@ -20,12 +20,12 @@ import picocli.CommandLine; /** The Percentage Cli type converter. */ -public class PercentageConverter implements CommandLine.ITypeConverter { +public class PercentageConverter implements CommandLine.ITypeConverter { @Override - public Integer convert(final String value) throws PercentageConversionException { + public Percentage convert(final String value) throws PercentageConversionException { try { - return Percentage.fromString(value).getValue(); + return Percentage.fromString(value); } catch (NullPointerException | IllegalArgumentException e) { throw new PercentageConversionException(value); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java new file mode 100644 index 00000000000..59658d135ac --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java @@ -0,0 +1,31 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter; + +/** + * This interface can be used to give a converter the capability to format the converted value back + * to its CLI form + * + * @param the type of the CLI converted runtime value + */ +public interface TypeFormatter { + /** + * Format a converted value back to its CLI form + * + * @param value the converted value + * @return the textual CLI form of the value + */ + String format(V value); +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java new file mode 100644 index 00000000000..cd56d06188c --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java @@ -0,0 +1,39 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter.exception; + +import static java.lang.String.format; + +/** The custom Duration conversion exception. */ +public final class DurationConversionException extends Exception { + + /** + * Instantiates a new Duration conversion exception for malformed value. + * + * @param value the value + */ + public DurationConversionException(final String value) { + super(format("'%s' is not a long", value)); + } + + /** + * Instantiates a new Duration conversion exception for negative value. + * + * @param value the millis + */ + public DurationConversionException(final long value) { + super(format("negative value '%d' is not allowed", value)); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java b/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java index 73161742f90..003ad8846e8 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java @@ -16,6 +16,11 @@ import static com.google.common.base.Preconditions.checkArgument; +import org.hyperledger.besu.datatypes.Wei; + +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Iterator; import com.google.common.base.Splitter; @@ -96,4 +101,44 @@ public static String format(final float value) { public static String format(final UInt256 value) { return value.toBigInteger().toString(10); } + + /** + * Format Wei to string. + * + * @param value the value + * @return the string + */ + public static String format(final Wei value) { + return format(value.toUInt256()); + } + + /** + * Format any object to string. This implementation tries to find an existing format method, in + * this class, that matches the type of the passed object, and if not found just invoke, to string + * on the passed object + * + * @param value the object + * @return the string + */ + public static String format(final Object value) { + Method formatMethod; + try { + formatMethod = OptionParser.class.getMethod("format", value.getClass()); + } catch (NoSuchMethodException e) { + try { + // maybe a primitive version of the method exists + formatMethod = + OptionParser.class.getMethod( + "format", MethodType.methodType(value.getClass()).unwrap().returnType()); + } catch (NoSuchMethodException ex) { + return value.toString(); + } + } + + try { + return (String) formatMethod.invoke(null, value); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptions.java new file mode 100644 index 00000000000..6353618d24d --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptions.java @@ -0,0 +1,263 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_DOUBLE_FORMAT_HELP; +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP; +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; + +import org.hyperledger.besu.cli.converter.FractionConverter; +import org.hyperledger.besu.cli.converter.PercentageConverter; +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.util.number.Fraction; +import org.hyperledger.besu.util.number.Percentage; + +import java.io.File; +import java.util.List; + +import picocli.CommandLine; + +/** The Transaction pool Cli stable options. */ +public class TransactionPoolOptions implements CLIOptions { + private static final String TX_POOL_IMPLEMENTATION = "--tx-pool"; + private static final String TX_POOL_DISABLE_LOCALS = "--tx-pool-disable-locals"; + private static final String TX_POOL_ENABLE_SAVE_RESTORE = "--tx-pool-enable-save-restore"; + private static final String TX_POOL_SAVE_FILE = "--tx-pool-save-file"; + private static final String TX_POOL_PRICE_BUMP = "--tx-pool-price-bump"; + private static final String RPC_TX_FEECAP = "--rpc-tx-feecap"; + private static final String STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG = + "--strict-tx-replay-protection-enabled"; + + @CommandLine.Option( + names = {TX_POOL_IMPLEMENTATION}, + paramLabel = "", + description = "The Transaction Pool implementation to use(default: ${DEFAULT-VALUE})", + arity = "0..1") + private TransactionPoolConfiguration.Implementation txPoolImplementation = LAYERED; + + @CommandLine.Option( + names = {TX_POOL_DISABLE_LOCALS}, + paramLabel = "", + description = + "Set to true if transactions sent via RPC should have the same checks and not be prioritized over remote ones (default: ${DEFAULT-VALUE})", + fallbackValue = "true", + arity = "0..1") + private Boolean disableLocalTxs = TransactionPoolConfiguration.DEFAULT_DISABLE_LOCAL_TXS; + + @CommandLine.Option( + names = {TX_POOL_ENABLE_SAVE_RESTORE}, + paramLabel = "", + description = + "Set to true to enable saving the txpool content to file on shutdown and reloading it on startup (default: ${DEFAULT-VALUE})", + fallbackValue = "true", + arity = "0..1") + private Boolean saveRestoreEnabled = TransactionPoolConfiguration.DEFAULT_ENABLE_SAVE_RESTORE; + + @CommandLine.Option( + names = {TX_POOL_SAVE_FILE}, + paramLabel = "", + description = + "If saving the txpool content is enabled, define a custom path for the save file (default: ${DEFAULT-VALUE} in the data-dir)", + arity = "1") + private File saveFile = TransactionPoolConfiguration.DEFAULT_SAVE_FILE; + + @CommandLine.Option( + names = {TX_POOL_PRICE_BUMP}, + paramLabel = "", + converter = PercentageConverter.class, + description = + "Price bump percentage to replace an already existing transaction (default: ${DEFAULT-VALUE})", + arity = "1") + private Percentage priceBump = TransactionPoolConfiguration.DEFAULT_PRICE_BUMP; + + @CommandLine.Option( + names = {RPC_TX_FEECAP}, + description = + "Maximum transaction fees (in Wei) accepted for transaction submitted through RPC (default: ${DEFAULT-VALUE})", + arity = "1") + private Wei txFeeCap = TransactionPoolConfiguration.DEFAULT_RPC_TX_FEE_CAP; + + @CommandLine.Option( + names = {STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG}, + paramLabel = "", + description = + "Require transactions submitted via JSON-RPC to use replay protection in accordance with EIP-155 (default: ${DEFAULT-VALUE})", + fallbackValue = "true", + arity = "0..1") + private Boolean strictTxReplayProtectionEnabled = false; + + @CommandLine.ArgGroup( + validate = false, + heading = "@|bold Tx Pool Layered Implementation Options|@%n") + private final Layered layeredOptions = new Layered(); + + static class Layered { + private static final String TX_POOL_LAYER_MAX_CAPACITY = "--tx-pool-layer-max-capacity"; + private static final String TX_POOL_MAX_PRIORITIZED = "--tx-pool-max-prioritized"; + private static final String TX_POOL_MAX_FUTURE_BY_SENDER = "--tx-pool-max-future-by-sender"; + + @CommandLine.Option( + names = {TX_POOL_LAYER_MAX_CAPACITY}, + paramLabel = MANDATORY_LONG_FORMAT_HELP, + description = + "Max amount of memory space, in bytes, that any layer within the transaction pool could occupy (default: ${DEFAULT-VALUE})", + arity = "1") + Long txPoolLayerMaxCapacity = + TransactionPoolConfiguration.DEFAULT_PENDING_TRANSACTIONS_LAYER_MAX_CAPACITY_BYTES; + + @CommandLine.Option( + names = {TX_POOL_MAX_PRIORITIZED}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + description = + "Max number of pending transactions that are prioritized and thus kept sorted (default: ${DEFAULT-VALUE})", + arity = "1") + Integer txPoolMaxPrioritized = + TransactionPoolConfiguration.DEFAULT_MAX_PRIORITIZED_TRANSACTIONS; + + @CommandLine.Option( + names = {TX_POOL_MAX_FUTURE_BY_SENDER}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + description = + "Max number of future pending transactions allowed for a single sender (default: ${DEFAULT-VALUE})", + arity = "1") + Integer txPoolMaxFutureBySender = TransactionPoolConfiguration.DEFAULT_MAX_FUTURE_BY_SENDER; + } + + @CommandLine.ArgGroup( + validate = false, + heading = "@|bold Tx Pool Legacy Implementation Options|@%n") + private final Legacy legacyOptions = new Legacy(); + + static class Legacy { + private static final String TX_POOL_RETENTION_HOURS = "--tx-pool-retention-hours"; + private static final String TX_POOL_LIMIT_BY_ACCOUNT_PERCENTAGE = + "--tx-pool-limit-by-account-percentage"; + private static final String TX_POOL_MAX_SIZE = "--tx-pool-max-size"; + + @CommandLine.Option( + names = {TX_POOL_RETENTION_HOURS}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + description = + "Maximum retention period of pending transactions in hours (default: ${DEFAULT-VALUE})", + arity = "1") + Integer pendingTxRetentionPeriod = TransactionPoolConfiguration.DEFAULT_TX_RETENTION_HOURS; + + @CommandLine.Option( + names = {TX_POOL_LIMIT_BY_ACCOUNT_PERCENTAGE}, + paramLabel = MANDATORY_DOUBLE_FORMAT_HELP, + converter = FractionConverter.class, + description = + "Maximum portion of the transaction pool which a single account may occupy with future transactions (default: ${DEFAULT-VALUE})", + arity = "1") + Fraction txPoolLimitByAccountPercentage = + TransactionPoolConfiguration.DEFAULT_LIMIT_TX_POOL_BY_ACCOUNT_PERCENTAGE; + + @CommandLine.Option( + names = {TX_POOL_MAX_SIZE}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + description = + "Maximum number of pending transactions that will be kept in the transaction pool (default: ${DEFAULT-VALUE})", + arity = "1") + Integer txPoolMaxSize = TransactionPoolConfiguration.DEFAULT_MAX_PENDING_TRANSACTIONS; + } + + private TransactionPoolOptions() {} + + /** + * Create transaction pool options. + * + * @return the transaction pool options + */ + public static TransactionPoolOptions create() { + return new TransactionPoolOptions(); + } + + /** + * Create Transaction Pool Options from Transaction Pool Configuration. + * + * @param config the Transaction Pool Configuration + * @return the transaction pool options + */ + public static TransactionPoolOptions fromConfig(final TransactionPoolConfiguration config) { + final TransactionPoolOptions options = TransactionPoolOptions.create(); + options.txPoolImplementation = config.getTxPoolImplementation(); + options.saveRestoreEnabled = config.getEnableSaveRestore(); + options.disableLocalTxs = config.getDisableLocalTransactions(); + options.priceBump = config.getPriceBump(); + options.txFeeCap = config.getTxFeeCap(); + options.saveFile = config.getSaveFile(); + options.strictTxReplayProtectionEnabled = config.getStrictTransactionReplayProtectionEnabled(); + options.layeredOptions.txPoolLayerMaxCapacity = + config.getPendingTransactionsLayerMaxCapacityBytes(); + options.layeredOptions.txPoolMaxPrioritized = config.getMaxPrioritizedTransactions(); + options.layeredOptions.txPoolMaxFutureBySender = config.getMaxFutureBySender(); + options.legacyOptions.txPoolLimitByAccountPercentage = + config.getTxPoolLimitByAccountPercentage(); + options.legacyOptions.txPoolMaxSize = config.getTxPoolMaxSize(); + options.legacyOptions.pendingTxRetentionPeriod = config.getPendingTxRetentionPeriod(); + + return options; + } + + /** + * Validate that there are no inconsistencies in the specified options. For example that the + * options are valid for the selected implementation. + * + * @param commandLine the full commandLine to check all the options specified by the user + */ + public void validate(final CommandLine commandLine) { + CommandLineUtils.failIfOptionDoesntMeetRequirement( + commandLine, + "Could not use legacy transaction pool options with layered implementation", + !txPoolImplementation.equals(LAYERED), + CommandLineUtils.getCLIOptionNames(Legacy.class)); + + CommandLineUtils.failIfOptionDoesntMeetRequirement( + commandLine, + "Could not use layered transaction pool options with legacy implementation", + !txPoolImplementation.equals(LEGACY), + CommandLineUtils.getCLIOptionNames(Layered.class)); + } + + @Override + public TransactionPoolConfiguration toDomainObject() { + return ImmutableTransactionPoolConfiguration.builder() + .txPoolImplementation(txPoolImplementation) + .enableSaveRestore(saveRestoreEnabled) + .disableLocalTransactions(disableLocalTxs) + .priceBump(priceBump) + .txFeeCap(txFeeCap) + .saveFile(saveFile) + .strictTransactionReplayProtectionEnabled(strictTxReplayProtectionEnabled) + .pendingTransactionsLayerMaxCapacityBytes(layeredOptions.txPoolLayerMaxCapacity) + .maxPrioritizedTransactions(layeredOptions.txPoolMaxPrioritized) + .maxFutureBySender(layeredOptions.txPoolMaxFutureBySender) + .txPoolLimitByAccountPercentage(legacyOptions.txPoolLimitByAccountPercentage) + .txPoolMaxSize(legacyOptions.txPoolMaxSize) + .pendingTxRetentionPeriod(legacyOptions.pendingTxRetentionPeriod) + .build(); + } + + @Override + public List getCLIOptions() { + return CommandLineUtils.getCLIOptions(this, new TransactionPoolOptions()); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptions.java index e199966c25f..cb7994b947f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptions.java @@ -14,50 +14,25 @@ */ package org.hyperledger.besu.cli.options.unstable; +import org.hyperledger.besu.cli.converter.DurationMillisConverter; import org.hyperledger.besu.cli.options.CLIOptions; -import org.hyperledger.besu.cli.options.OptionParser; +import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import java.time.Duration; -import java.util.Arrays; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import picocli.CommandLine; -/** The Transaction pool Cli options. */ -public class TransactionPoolOptions - implements CLIOptions { - private static final Logger LOG = LoggerFactory.getLogger(TransactionPoolOptions.class); - +/** The Transaction pool Cli unstable options. */ +public class TransactionPoolOptions implements CLIOptions { private static final String TX_MESSAGE_KEEP_ALIVE_SEC_FLAG = "--Xincoming-tx-messages-keep-alive-seconds"; private static final String ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG = "--Xeth65-tx-announced-buffering-period-milliseconds"; - private static final String STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG = - "--strict-tx-replay-protection-enabled"; - - private static final String LAYERED_TX_POOL_ENABLED_FLAG = "--Xlayered-tx-pool"; - private static final String LAYERED_TX_POOL_LAYER_MAX_CAPACITY = - "--Xlayered-tx-pool-layer-max-capacity"; - private static final String LAYERED_TX_POOL_MAX_PRIORITIZED = - "--Xlayered-tx-pool-max-prioritized"; - private static final String LAYERED_TX_POOL_MAX_FUTURE_BY_SENDER = - "--Xlayered-tx-pool-max-future-by-sender"; - - @CommandLine.Option( - names = {STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG}, - paramLabel = "", - description = - "Require transactions submitted via JSON-RPC to use replay protection in accordance with EIP-155 (default: ${DEFAULT-VALUE})", - fallbackValue = "true", - arity = "0..1") - private Boolean strictTxReplayProtectionEnabled = false; - @CommandLine.Option( names = {TX_MESSAGE_KEEP_ALIVE_SEC_FLAG}, paramLabel = "", @@ -66,56 +41,18 @@ public class TransactionPoolOptions "Keep alive of incoming transaction messages in seconds (default: ${DEFAULT-VALUE})", arity = "1") private Integer txMessageKeepAliveSeconds = - TransactionPoolConfiguration.DEFAULT_TX_MSG_KEEP_ALIVE; + TransactionPoolConfiguration.Unstable.DEFAULT_TX_MSG_KEEP_ALIVE; @CommandLine.Option( names = {ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG}, paramLabel = "", + converter = DurationMillisConverter.class, hidden = true, description = "The period for which the announced transactions remain in the buffer before being requested from the peers in milliseconds (default: ${DEFAULT-VALUE})", arity = "1") - private long eth65TrxAnnouncedBufferingPeriod = - TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD.toMillis(); - - @CommandLine.Option( - names = {LAYERED_TX_POOL_ENABLED_FLAG}, - paramLabel = "", - hidden = true, - description = "Enable the Layered Transaction Pool (default: ${DEFAULT-VALUE})", - arity = "0..1") - private Boolean layeredTxPoolEnabled = - TransactionPoolConfiguration.DEFAULT_LAYERED_TX_POOL_ENABLED; - - @CommandLine.Option( - names = {LAYERED_TX_POOL_LAYER_MAX_CAPACITY}, - paramLabel = "", - hidden = true, - description = - "Max amount of memory space, in bytes, that any layer within the transaction pool could occupy (default: ${DEFAULT-VALUE})", - arity = "1") - private long layeredTxPoolLayerMaxCapacity = - TransactionPoolConfiguration.DEFAULT_PENDING_TRANSACTIONS_LAYER_MAX_CAPACITY_BYTES; - - @CommandLine.Option( - names = {LAYERED_TX_POOL_MAX_PRIORITIZED}, - paramLabel = "", - hidden = true, - description = - "Max number of pending transactions that are prioritized and thus kept sorted (default: ${DEFAULT-VALUE})", - arity = "1") - private int layeredTxPoolMaxPrioritized = - TransactionPoolConfiguration.DEFAULT_MAX_PRIORITIZED_TRANSACTIONS; - - @CommandLine.Option( - names = {LAYERED_TX_POOL_MAX_FUTURE_BY_SENDER}, - paramLabel = "", - hidden = true, - description = - "Max number of future pending transactions allowed for a single sender (default: ${DEFAULT-VALUE})", - arity = "1") - private int layeredTxPoolMaxFutureBySender = - TransactionPoolConfiguration.DEFAULT_MAX_FUTURE_BY_SENDER; + private Duration eth65TrxAnnouncedBufferingPeriod = + TransactionPoolConfiguration.Unstable.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD; private TransactionPoolOptions() {} @@ -134,51 +71,24 @@ public static TransactionPoolOptions create() { * @param config the Transaction Pool Configuration * @return the transaction pool options */ - public static TransactionPoolOptions fromConfig(final TransactionPoolConfiguration config) { + public static TransactionPoolOptions fromConfig( + final TransactionPoolConfiguration.Unstable config) { final TransactionPoolOptions options = TransactionPoolOptions.create(); options.txMessageKeepAliveSeconds = config.getTxMessageKeepAliveSeconds(); - options.eth65TrxAnnouncedBufferingPeriod = - config.getEth65TrxAnnouncedBufferingPeriod().toMillis(); - options.strictTxReplayProtectionEnabled = config.getStrictTransactionReplayProtectionEnabled(); - options.layeredTxPoolEnabled = config.getLayeredTxPoolEnabled(); - options.layeredTxPoolLayerMaxCapacity = config.getPendingTransactionsLayerMaxCapacityBytes(); - options.layeredTxPoolMaxPrioritized = config.getMaxPrioritizedTransactions(); - options.layeredTxPoolMaxFutureBySender = config.getMaxFutureBySender(); + options.eth65TrxAnnouncedBufferingPeriod = config.getEth65TrxAnnouncedBufferingPeriod(); return options; } @Override - public ImmutableTransactionPoolConfiguration.Builder toDomainObject() { - if (layeredTxPoolEnabled) { - LOG.warn( - "Layered transaction pool enabled, ignoring settings for " - + "--tx-pool-max-size and --tx-pool-limit-by-account-percentage"); - } - - return ImmutableTransactionPoolConfiguration.builder() - .strictTransactionReplayProtectionEnabled(strictTxReplayProtectionEnabled) + public TransactionPoolConfiguration.Unstable toDomainObject() { + return ImmutableTransactionPoolConfiguration.Unstable.builder() .txMessageKeepAliveSeconds(txMessageKeepAliveSeconds) - .eth65TrxAnnouncedBufferingPeriod(Duration.ofMillis(eth65TrxAnnouncedBufferingPeriod)) - .layeredTxPoolEnabled(layeredTxPoolEnabled) - .pendingTransactionsLayerMaxCapacityBytes(layeredTxPoolLayerMaxCapacity) - .maxPrioritizedTransactions(layeredTxPoolMaxPrioritized) - .maxFutureBySender(layeredTxPoolMaxFutureBySender); + .eth65TrxAnnouncedBufferingPeriod(eth65TrxAnnouncedBufferingPeriod) + .build(); } @Override public List getCLIOptions() { - return Arrays.asList( - STRICT_TX_REPLAY_PROTECTION_ENABLED_FLAG + "=" + strictTxReplayProtectionEnabled, - TX_MESSAGE_KEEP_ALIVE_SEC_FLAG, - OptionParser.format(txMessageKeepAliveSeconds), - ETH65_TX_ANNOUNCED_BUFFERING_PERIOD_FLAG, - OptionParser.format(eth65TrxAnnouncedBufferingPeriod), - LAYERED_TX_POOL_ENABLED_FLAG + "=" + layeredTxPoolEnabled, - LAYERED_TX_POOL_LAYER_MAX_CAPACITY, - OptionParser.format(layeredTxPoolLayerMaxCapacity), - LAYERED_TX_POOL_MAX_PRIORITIZED, - OptionParser.format(layeredTxPoolMaxPrioritized), - LAYERED_TX_POOL_MAX_FUTURE_BY_SENDER, - OptionParser.format(layeredTxPoolMaxFutureBySender)); + return CommandLineUtils.getCLIOptions(this, new TransactionPoolOptions()); } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java b/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java index 5c191bb4fce..0115420005e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java @@ -14,10 +14,17 @@ */ package org.hyperledger.besu.cli.util; +import org.hyperledger.besu.cli.converter.TypeFormatter; +import org.hyperledger.besu.cli.options.OptionParser; import org.hyperledger.besu.util.StringUtils; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import com.google.common.base.Strings; @@ -124,6 +131,103 @@ public static void failIfOptionDoesntMeetRequirement( } } + /** + * Return all the option names declared in a class. Note this will recursively check in any inner + * option class if present. + * + * @param optClass the class to look for options + * @return a list of option names found in the class + */ + public static List getCLIOptionNames(final Class optClass) { + final List cliOpts = new ArrayList<>(); + final Field[] fields = optClass.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + Annotation ann = field.getAnnotation(CommandLine.Option.class); + if (ann != null) { + final var optAnn = CommandLine.Option.class.cast(ann); + cliOpts.add(optAnn.names()[0]); + } else { + ann = field.getAnnotation(CommandLine.ArgGroup.class); + if (ann != null) { + cliOpts.addAll(getCLIOptionNames(field.getType())); + } + } + } + return cliOpts; + } + + /** + * Converts the runtime options into their CLI representation. Options with a value equals to its + * default are not included in the result since redundant. Note this will recursively check in any + * inner option class if present. + * + * @param currOptions the actual runtime options + * @param defaults the default option values + * @return a list of CLI arguments + */ + public static List getCLIOptions(final Object currOptions, final Object defaults) { + final List cliOpts = new ArrayList<>(); + final Field[] fields = currOptions.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + Annotation ann = field.getAnnotation(CommandLine.Option.class); + if (ann != null) { + try { + var optVal = field.get(currOptions); + if (!Objects.equals(optVal, field.get(defaults))) { + var optAnn = CommandLine.Option.class.cast(ann); + cliOpts.add(optAnn.names()[0]); + final var optConverter = optAnn.converter(); + cliOpts.add(formatValue(optConverter, optVal)); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } else { + ann = field.getAnnotation(CommandLine.ArgGroup.class); + if (ann != null) { + try { + cliOpts.addAll(getCLIOptions(field.get(currOptions), field.get(defaults))); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + } + return cliOpts; + } + + /** + * There are different ways to format an option value back to its CLI form, the first is to use a + * {@link TypeFormatter} if present, otherwise the formatting it is delegated to {@link + * OptionParser#format(Object)} + * + * @param optConverter the list of converter types for the option + * @param optVal the value of the options + * @return a string with the CLI form of the value + */ + @SuppressWarnings("unchecked") + private static String formatValue( + final Class>[] optConverter, final Object optVal) { + return Arrays.stream(optConverter) + .filter(c -> Arrays.stream(c.getInterfaces()).anyMatch(i -> i.equals(TypeFormatter.class))) + .findFirst() + .map( + ctf -> { + try { + return (TypeFormatter) ctf.getDeclaredConstructor().newInstance(); + } catch (InstantiationException + | IllegalAccessException + | InvocationTargetException + | NoSuchMethodException e) { + throw new RuntimeException(e); + } + }) + .map(tf -> tf.format(optVal)) + .orElseGet(() -> OptionParser.format(optVal)); + } + private static String getAffectedOptions( final CommandLine commandLine, final List dependentOptionsNames) { return commandLine.getCommandSpec().options().stream() diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java b/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java index ec9ee7d593f..9ec3f1ef640 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.cli.util; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.util.number.Percentage; import java.io.File; import java.io.IOException; @@ -89,6 +90,8 @@ private String getConfigurationValue(final OptionSpec optionSpec) { defaultValue = getNumericEntryAsString(optionSpec); } else if (optionSpec.type().equals(Float.class) || optionSpec.type().equals(float.class)) { defaultValue = getNumericEntryAsString(optionSpec); + } else if (optionSpec.type().equals(Percentage.class)) { + defaultValue = getNumericEntryAsString(optionSpec); } else { // else will be treated as String defaultValue = getEntryAsString(optionSpec); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index bfabc53eb6f..8919702f3b1 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -59,7 +59,6 @@ import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.cli.config.EthNetworkConfig; -import org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.MergeConfigOptions; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; @@ -4619,192 +4618,6 @@ public void errorIsRaisedIfStaticNodesAreNotAllowed() throws IOException { .contains(staticNodeURI.toString(), "not in nodes-allowlist"); } - @Test - public void disableLocalsDefault() { - parseCommand(); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getDisableLocalTransactions()).isFalse(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void disableLocalsOn() { - parseCommand("--tx-pool-disable-locals=true"); - - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getDisableLocalTransactions()).isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void disableLocalsOff() { - parseCommand("--tx-pool-disable-locals=false"); - - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getDisableLocalTransactions()).isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void saveToFileDisabledByDefault() { - parseCommand(); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getEnableSaveRestore()).isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void saveToFileEnabledDefaultPath() { - parseCommand("--tx-pool-enable-save-restore=true"); - - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getEnableSaveRestore()).isTrue(); - assertThat(transactionPoolConfigCaptor.getValue().getSaveFile()) - .hasName(TransactionPoolConfiguration.DEFAULT_SAVE_FILE_NAME); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void saveToFileEnabledCustomPath() { - parseCommand("--tx-pool-enable-save-restore=true", "--tx-pool-save-file=my.save.file"); - - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getEnableSaveRestore()).isTrue(); - assertThat(transactionPoolConfigCaptor.getValue().getSaveFile()).hasName("my.save.file"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void senderLimitedTxPool_derived() { - parseCommand("--tx-pool-limit-by-account-percentage=0.002"); - - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getTxPoolMaxFutureTransactionByAccount()) - .isEqualTo(9); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void senderLimitedTxPoolFloor_derived() { - parseCommand("--tx-pool-limit-by-account-percentage=0.0001"); - - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getTxPoolMaxFutureTransactionByAccount()) - .isEqualTo(1); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void senderLimitedTxPoolCeiling_violated() { - TestBesuCommand commandTest = parseCommand("--tx-pool-limit-by-account-percentage=1.00002341"); - - TransactionPoolOptions txPoolOption = commandTest.getTransactionPoolOptions(); - - final TransactionPoolConfiguration config = txPoolOption.toDomainObject().build(); - assertThat(config.getTxPoolLimitByAccountPercentage()) - .isEqualTo(TransactionPoolConfiguration.DEFAULT_LIMIT_TX_POOL_BY_ACCOUNT_PERCENTAGE); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Invalid value for option '--tx-pool-limit-by-account-percentage'"); - } - - @Test - public void pendingTransactionRetentionPeriod() { - final int pendingTxRetentionHours = 999; - parseCommand("--tx-pool-retention-hours", String.valueOf(pendingTxRetentionHours)); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getPendingTxRetentionPeriod()) - .isEqualTo(pendingTxRetentionHours); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void transactionPoolPriceBump() { - final Percentage priceBump = Percentage.fromInt(13); - parseCommand("--tx-pool-price-bump", priceBump.toString()); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getPriceBump()).isEqualTo(priceBump); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void invalidTansactionPoolPriceBumpShouldFail() { - parseCommand("--tx-pool-price-bump", "101"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--tx-pool-price-bump'", - "should be a number between 0 and 100 inclusive"); - } - - @Test - public void transactionPoolTxFeeCap() { - final Wei txFeeCap = Wei.fromEth(2); - parseCommand("--rpc-tx-feecap", txFeeCap.toDecimalString()); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - assertThat(transactionPoolConfigCaptor.getValue().getTxFeeCap()).isEqualTo(txFeeCap); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void invalidTansactionPoolTxFeeCapShouldFail() { - parseCommand("--rpc-tx-feecap", "abcd"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Invalid value for option '--rpc-tx-feecap'", "cannot convert 'abcd' to Wei"); - } - - @Test - public void txMessageKeepAliveSecondsWithInvalidInputShouldFail() { - parseCommand("--Xincoming-tx-messages-keep-alive-seconds", "acbd"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--Xincoming-tx-messages-keep-alive-seconds': 'acbd' is not an int"); - } - - @Test - public void eth65TrxAnnouncedBufferingPeriodWithInvalidInputShouldFail() { - parseCommand("--Xeth65-tx-announced-buffering-period-milliseconds", "acbd"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--Xeth65-tx-announced-buffering-period-milliseconds': 'acbd' is not a long"); - } - @Test public void tomlThatHasInvalidOptions() throws IOException { final URL configFile = this.getClass().getResource("/complete_config.toml"); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index fc6f013b2da..d089ceaceb7 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -519,7 +519,12 @@ public EthProtocolOptions getEthProtocolOptions() { return unstableEthProtocolOptions; } - public TransactionPoolOptions getTransactionPoolOptions() { + public org.hyperledger.besu.cli.options.stable.TransactionPoolOptions + getStableTransactionPoolOptions() { + return stableTransactionPoolOptions; + } + + public TransactionPoolOptions getUnstableTransactionPoolOptions() { return unstableTransactionPoolOptions; } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java index 33ed9bc2d16..4fff2b36f58 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java @@ -15,6 +15,8 @@ package org.hyperledger.besu.cli; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; import static org.mockito.Mockito.mock; import java.math.BigInteger; @@ -145,14 +147,16 @@ void setHighSpecEnabled() { } @Test - void setLayeredTxPoolEnabled() { - final String layeredTxPoolDisabled = builder.build(); - assertThat(layeredTxPoolDisabled) - .doesNotContain("Experimental layered transaction pool configuration enabled"); - - builder.setLayeredTxPoolEnabled(); - final String layeredTxPoolEnabled = builder.build(); - assertThat(layeredTxPoolEnabled) - .contains("Experimental layered transaction pool configuration enabled"); + void setTxPoolImplementationLayered() { + builder.setTxPoolImplementation(LAYERED); + final String layeredTxPoolSelected = builder.build(); + assertThat(layeredTxPoolSelected).contains("Using LAYERED transaction pool implementation"); + } + + @Test + void setTxPoolImplementationLegacy() { + builder.setTxPoolImplementation(LEGACY); + final String legacyTxPoolSelected = builder.build(); + assertThat(legacyTxPoolSelected).contains("Using LEGACY transaction pool implementation"); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java index 022bca1ec27..611109274d5 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.catchThrowable; import org.hyperledger.besu.cli.converter.exception.FractionConversionException; +import org.hyperledger.besu.util.number.Fraction; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,9 +31,9 @@ public class FractionConverterTest { @Test public void assertThatConvertHandlesProperlyAValidString() throws FractionConversionException { - final float fraction = fractionConverter.convert("0.58"); + final Fraction fraction = fractionConverter.convert("0.58"); assertThat(fraction).isNotNull(); - assertThat(fraction).isEqualTo(0.58f); + assertThat(fraction.getValue()).isEqualTo(0.58f); } @Test diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java index 6fc6f9dc7e7..829a4ef35f6 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.catchThrowable; import org.hyperledger.besu.cli.converter.exception.PercentageConversionException; +import org.hyperledger.besu.util.number.Percentage; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,9 +31,9 @@ public class PercentageConverterTest { @Test public void assertThatConvertHandlesProperlyAValidString() throws PercentageConversionException { - final int percentage = percentageConverter.convert("58"); + final Percentage percentage = percentageConverter.convert("58"); assertThat(percentage).isNotNull(); - assertThat(percentage).isEqualTo(58); + assertThat(percentage.getValue()).isEqualTo(58); } @Test diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java index 91d7e35c39c..ef7da0c7669 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import org.junit.Test; @@ -87,9 +88,9 @@ public void defaultValues() { .isEqualTo(defaultOptions); } - abstract D createDefaultDomainObject(); + protected abstract D createDefaultDomainObject(); - abstract D createCustomizedDomainObject(); + protected abstract D createCustomizedDomainObject(); protected List getFieldsWithComputedDefaults() { return Collections.emptyList(); @@ -99,7 +100,25 @@ protected List getFieldsToIgnore() { return Collections.emptyList(); } - abstract T optionsFromDomainObject(D domainObject); + protected abstract T optionsFromDomainObject(D domainObject); - abstract T getOptionsFromBesuCommand(final TestBesuCommand besuCommand); + protected abstract T getOptionsFromBesuCommand(final TestBesuCommand besuCommand); + + protected void internalTestSuccess(final Consumer assertion, final String... args) { + final TestBesuCommand cmd = parseCommand(args); + + final T options = getOptionsFromBesuCommand(cmd); + final D config = options.toDomainObject(); + assertion.accept(config); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + protected void internalTestFailure(final String errorMsg, final String... args) { + parseCommand(args); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).contains(errorMsg); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java index 1dc6f0329fb..9d1d696ab43 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java @@ -165,12 +165,12 @@ public void parsesValidEthMinProtocol() { } @Override - EthProtocolConfiguration createDefaultDomainObject() { + protected EthProtocolConfiguration createDefaultDomainObject() { return EthProtocolConfiguration.builder().build(); } @Override - EthProtocolConfiguration createCustomizedDomainObject() { + protected EthProtocolConfiguration createCustomizedDomainObject() { return EthProtocolConfiguration.builder() .maxMessageSize(EthProtocolConfiguration.DEFAULT_MAX_MESSAGE_SIZE * 2) .maxGetBlockHeaders(EthProtocolConfiguration.DEFAULT_MAX_GET_BLOCK_HEADERS + 2) @@ -184,12 +184,13 @@ EthProtocolConfiguration createCustomizedDomainObject() { } @Override - EthProtocolOptions optionsFromDomainObject(final EthProtocolConfiguration domainObject) { + protected EthProtocolOptions optionsFromDomainObject( + final EthProtocolConfiguration domainObject) { return EthProtocolOptions.fromConfig(domainObject); } @Override - EthProtocolOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { + protected EthProtocolOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { return besuCommand.getEthProtocolOptions(); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java index 5bcaaa553fc..8044770a9b4 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java @@ -25,24 +25,25 @@ public class MetricsCLIOptionsTest extends AbstractCLIOptionsTest { @Override - MetricsConfiguration.Builder createDefaultDomainObject() { + protected MetricsConfiguration.Builder createDefaultDomainObject() { return MetricsConfiguration.builder(); } @Override - MetricsConfiguration.Builder createCustomizedDomainObject() { + protected MetricsConfiguration.Builder createCustomizedDomainObject() { return MetricsConfiguration.builder() .timersEnabled(!MetricsConfiguration.DEFAULT_METRICS_TIMERS_ENABLED) .idleTimeout(MetricsConfiguration.DEFAULT_METRICS_IDLE_TIMEOUT_SECONDS); } @Override - MetricsCLIOptions optionsFromDomainObject(final MetricsConfiguration.Builder domainObject) { + protected MetricsCLIOptions optionsFromDomainObject( + final MetricsConfiguration.Builder domainObject) { return MetricsCLIOptions.fromConfiguration(domainObject.build()); } @Override - MetricsCLIOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { + protected MetricsCLIOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { return besuCommand.getMetricsCLIOptions(); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java index c59747dac73..a2cbdc5942b 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java @@ -165,12 +165,12 @@ public void checkFilterByForkIdSetToFalse() { } @Override - NetworkingConfiguration createDefaultDomainObject() { + protected NetworkingConfiguration createDefaultDomainObject() { return NetworkingConfiguration.create(); } @Override - NetworkingConfiguration createCustomizedDomainObject() { + protected NetworkingConfiguration createCustomizedDomainObject() { final NetworkingConfiguration config = NetworkingConfiguration.create(); config.setInitiateConnectionsFrequency( NetworkingConfiguration.DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC + 10); @@ -181,12 +181,12 @@ NetworkingConfiguration createCustomizedDomainObject() { } @Override - NetworkingOptions optionsFromDomainObject(final NetworkingConfiguration domainObject) { + protected NetworkingOptions optionsFromDomainObject(final NetworkingConfiguration domainObject) { return NetworkingOptions.fromConfig(domainObject); } @Override - NetworkingOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { + protected NetworkingOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { return besuCommand.getNetworkingOptions(); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java index 7dbb3958c2c..db5e69e174b 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java @@ -104,4 +104,25 @@ public void format_negativeLong() { final String expected = "-1233"; assertThat(OptionParser.format(input)).isEqualTo(expected); } + + @Test + public void format_object_int() { + final Object input = 1233; + final String expected = "1233"; + assertThat(OptionParser.format(input)).isEqualTo(expected); + } + + @Test + public void format_object_Integer() { + final Object input = Integer.valueOf(1233); + final String expected = "1233"; + assertThat(OptionParser.format(input)).isEqualTo(expected); + } + + @Test + public void format_object_uint256() { + final Object input = UInt256.valueOf(new BigInteger("123456789", 10)); + final String expected = "123456789"; + assertThat(OptionParser.format(input)).isEqualTo(expected); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java index affb5a9d4d3..0e814a07e26 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java @@ -31,12 +31,12 @@ public class SynchronizerOptionsTest extends AbstractCLIOptionsTest { @Override - SynchronizerConfiguration.Builder createDefaultDomainObject() { + protected SynchronizerConfiguration.Builder createDefaultDomainObject() { return SynchronizerConfiguration.builder(); } @Override - SynchronizerConfiguration.Builder createCustomizedDomainObject() { + protected SynchronizerConfiguration.Builder createCustomizedDomainObject() { return SynchronizerConfiguration.builder() .fastSyncPivotDistance(SynchronizerConfiguration.DEFAULT_PIVOT_DISTANCE_FROM_HEAD + 10) .fastSyncFullValidationRate(SynchronizerConfiguration.DEFAULT_FULL_VALIDATION_RATE / 2) @@ -87,7 +87,7 @@ protected List getFieldsWithComputedDefaults() { } @Override - SynchronizerOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { + protected SynchronizerOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { return besuCommand.getSynchronizerOptions(); } @@ -97,7 +97,7 @@ protected List getFieldsToIgnore() { } @Override - SynchronizerOptions optionsFromDomainObject( + protected SynchronizerOptions optionsFromDomainObject( final SynchronizerConfiguration.Builder domainObject) { return SynchronizerOptions.fromConfig(domainObject.build()); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java deleted file mode 100644 index 5a235524c6a..00000000000 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.cli.options; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.cli.options.unstable.TransactionPoolOptions; -import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; - -import java.time.Duration; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class TransactionPoolOptionsTest - extends AbstractCLIOptionsTest< - ImmutableTransactionPoolConfiguration.Builder, TransactionPoolOptions> { - - @Test - public void strictTxReplayProtection_enabled() { - final TestBesuCommand cmd = parseCommand("--strict-tx-replay-protection-enabled"); - - final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd); - final TransactionPoolConfiguration config = options.toDomainObject().build(); - assertThat(config.getStrictTransactionReplayProtectionEnabled()).isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void strictTxReplayProtection_enabledWithBooleanArg() { - final TestBesuCommand cmd = parseCommand("--strict-tx-replay-protection-enabled=true"); - - final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd); - final TransactionPoolConfiguration config = options.toDomainObject().build(); - assertThat(config.getStrictTransactionReplayProtectionEnabled()).isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void strictTxReplayProtection_disabled() { - final TestBesuCommand cmd = parseCommand("--strict-tx-replay-protection-enabled=false"); - - final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd); - final TransactionPoolConfiguration config = options.toDomainObject().build(); - assertThat(config.getStrictTransactionReplayProtectionEnabled()).isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void strictTxReplayProtection_default() { - final TestBesuCommand cmd = parseCommand(); - - final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd); - final TransactionPoolConfiguration config = options.toDomainObject().build(); - assertThat(config.getStrictTransactionReplayProtectionEnabled()).isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void txMessageKeepAliveSeconds() { - final int txMessageKeepAliveSeconds = 999; - final TestBesuCommand cmd = - parseCommand( - "--Xincoming-tx-messages-keep-alive-seconds", - String.valueOf(txMessageKeepAliveSeconds)); - - final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd); - final TransactionPoolConfiguration config = options.toDomainObject().build(); - assertThat(config.getTxMessageKeepAliveSeconds()).isEqualTo(txMessageKeepAliveSeconds); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void eth65TrxAnnouncedBufferingPeriod() { - final long eth65TrxAnnouncedBufferingPeriod = 999; - final TestBesuCommand cmd = - parseCommand( - "--Xeth65-tx-announced-buffering-period-milliseconds", - String.valueOf(eth65TrxAnnouncedBufferingPeriod)); - - final TransactionPoolOptions options = getOptionsFromBesuCommand(cmd); - final TransactionPoolConfiguration config = options.toDomainObject().build(); - assertThat(config.getEth65TrxAnnouncedBufferingPeriod()) - .hasMillis(eth65TrxAnnouncedBufferingPeriod); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Override - ImmutableTransactionPoolConfiguration.Builder createDefaultDomainObject() { - final ImmutableTransactionPoolConfiguration defaultValue = - ImmutableTransactionPoolConfiguration.builder().build(); - return ImmutableTransactionPoolConfiguration.builder() - .strictTransactionReplayProtectionEnabled(false) - .txMessageKeepAliveSeconds(defaultValue.getTxMessageKeepAliveSeconds()) - .eth65TrxAnnouncedBufferingPeriod(defaultValue.getEth65TrxAnnouncedBufferingPeriod()) - .layeredTxPoolEnabled(defaultValue.getLayeredTxPoolEnabled()) - .pendingTransactionsLayerMaxCapacityBytes( - defaultValue.getPendingTransactionsLayerMaxCapacityBytes()) - .maxPrioritizedTransactions(defaultValue.getMaxPrioritizedTransactions()) - .maxFutureBySender(defaultValue.getMaxFutureBySender()); - } - - @Override - ImmutableTransactionPoolConfiguration.Builder createCustomizedDomainObject() { - return ImmutableTransactionPoolConfiguration.builder() - .strictTransactionReplayProtectionEnabled(true) - .txMessageKeepAliveSeconds(TransactionPoolConfiguration.DEFAULT_TX_MSG_KEEP_ALIVE + 1) - .eth65TrxAnnouncedBufferingPeriod( - TransactionPoolConfiguration.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD.plus( - Duration.ofMillis(100))) - .layeredTxPoolEnabled(true) - .pendingTransactionsLayerMaxCapacityBytes(1_000_000L) - .maxPrioritizedTransactions(1000) - .maxFutureBySender(10); - } - - @Override - TransactionPoolOptions optionsFromDomainObject( - final ImmutableTransactionPoolConfiguration.Builder domainObject) { - return TransactionPoolOptions.fromConfig(domainObject.build()); - } - - @Override - TransactionPoolOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { - return besuCommand.getTransactionPoolOptions(); - } -} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptionsTest.java new file mode 100644 index 00000000000..1f0705e9948 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/TransactionPoolOptionsTest.java @@ -0,0 +1,243 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; + +import org.hyperledger.besu.cli.options.AbstractCLIOptionsTest; +import org.hyperledger.besu.cli.options.OptionParser; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.util.number.Percentage; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TransactionPoolOptionsTest + extends AbstractCLIOptionsTest { + + @Test + public void strictTxReplayProtection_enabled() { + internalTestSuccess( + config -> assertThat(config.getStrictTransactionReplayProtectionEnabled()).isTrue(), + "--strict-tx-replay-protection-enabled"); + } + + @Test + public void strictTxReplayProtection_enabledWithBooleanArg() { + internalTestSuccess( + config -> assertThat(config.getStrictTransactionReplayProtectionEnabled()).isTrue(), + "--strict-tx-replay-protection-enabled=true"); + } + + @Test + public void strictTxReplayProtection_disabled() { + internalTestSuccess( + config -> assertThat(config.getStrictTransactionReplayProtectionEnabled()).isFalse(), + "--strict-tx-replay-protection-enabled=false"); + } + + @Test + public void strictTxReplayProtection_default() { + internalTestSuccess( + config -> assertThat(config.getStrictTransactionReplayProtectionEnabled()).isFalse()); + } + + @Test + public void pendingTransactionRetentionPeriod() { + final int pendingTxRetentionHours = 999; + internalTestSuccess( + config -> + assertThat(config.getPendingTxRetentionPeriod()).isEqualTo(pendingTxRetentionHours), + "--tx-pool-retention-hours", + String.valueOf(pendingTxRetentionHours), + "--tx-pool=legacy"); + } + + @Test + public void disableLocalsDefault() { + internalTestSuccess(config -> assertThat(config.getDisableLocalTransactions()).isFalse()); + } + + @Test + public void disableLocalsOn() { + internalTestSuccess( + config -> assertThat(config.getDisableLocalTransactions()).isTrue(), + "--tx-pool-disable-locals=true"); + } + + @Test + public void disableLocalsOff() { + internalTestSuccess( + config -> assertThat(config.getDisableLocalTransactions()).isFalse(), + "--tx-pool-disable-locals=false"); + } + + @Test + public void saveToFileDisabledByDefault() { + internalTestSuccess(config -> assertThat(config.getEnableSaveRestore()).isFalse()); + } + + @Test + public void saveToFileEnabledDefaultPath() { + internalTestSuccess( + config -> assertThat(config.getEnableSaveRestore()).isTrue(), + "--tx-pool-enable-save-restore=true"); + } + + @Test + public void saveToFileEnabledCustomPath() { + internalTestSuccess( + config -> { + assertThat(config.getEnableSaveRestore()).isTrue(); + assertThat(config.getSaveFile()).hasName("my.save.file"); + }, + "--tx-pool-enable-save-restore=true", + "--tx-pool-save-file=my.save.file"); + } + + @Test + public void senderLimited_derived() { + internalTestSuccess( + config -> assertThat(config.getTxPoolMaxFutureTransactionByAccount()).isEqualTo(9), + "--tx-pool-limit-by-account-percentage=0.002", + "--tx-pool=legacy"); + } + + @Test + public void senderLimitedFloor_derived() { + internalTestSuccess( + config -> assertThat(config.getTxPoolMaxFutureTransactionByAccount()).isEqualTo(1), + "--tx-pool-limit-by-account-percentage=0.0001", + "--tx-pool=legacy"); + } + + @Test + public void senderLimitedCeiling_violated() { + internalTestFailure( + "Invalid value for option '--tx-pool-limit-by-account-percentage'", + "--tx-pool-limit-by-account-percentage=1.00002341", + "--tx-pool=legacy"); + } + + @Test + public void priceBump() { + final Percentage priceBump = Percentage.fromInt(13); + internalTestSuccess( + config -> assertThat(config.getPriceBump()).isEqualTo(priceBump), + "--tx-pool-price-bump", + priceBump.toString()); + } + + @Test + public void invalidPriceBumpShouldFail() { + internalTestFailure( + "Invalid value: 101, should be a number between 0 and 100 inclusive", + "--tx-pool-price-bump", + "101"); + } + + @Test + public void txFeeCap() { + final Wei txFeeCap = Wei.fromEth(2); + internalTestSuccess( + config -> assertThat(config.getTxFeeCap()).isEqualTo(txFeeCap), + "--rpc-tx-feecap", + OptionParser.format(txFeeCap)); + } + + @Test + public void invalidTxFeeCapShouldFail() { + internalTestFailure( + "Invalid value for option '--rpc-tx-feecap'", + "cannot convert 'abcd' to Wei", + "--rpc-tx-feecap", + "abcd"); + } + + @Test + public void selectLayeredImplementationByDefault() { + internalTestSuccess(config -> assertThat(config.getTxPoolImplementation()).isEqualTo(LAYERED)); + } + + @Test + public void selectLayeredImplementationByArg() { + internalTestSuccess( + config -> assertThat(config.getTxPoolImplementation()).isEqualTo(LAYERED), + "--tx-pool=layered"); + } + + @Test + public void selectLegacyImplementationByArg() { + internalTestSuccess( + config -> assertThat(config.getTxPoolImplementation()).isEqualTo(LEGACY), + "--tx-pool=legacy"); + } + + @Test + public void failIfLegacyOptionsWhenLayeredSelectedByDefault() { + internalTestFailure( + "Could not use legacy transaction pool options with layered implementation", + "--tx-pool-max-size=1000"); + } + + @Test + public void failIfLegacyOptionsWhenLayeredSelectedByArg() { + internalTestFailure( + "Could not use legacy transaction pool options with layered implementation", + "--tx-pool=layered", + "--tx-pool-max-size=1000"); + } + + @Test + public void failIfLayeredOptionsWhenLegacySelectedByArg() { + internalTestFailure( + "Could not use layered transaction pool options with legacy implementation", + "--tx-pool=legacy", + "--tx-pool-max-prioritized=1000"); + } + + @Override + protected TransactionPoolConfiguration createDefaultDomainObject() { + return TransactionPoolConfiguration.DEFAULT; + } + + @Override + protected TransactionPoolConfiguration createCustomizedDomainObject() { + return ImmutableTransactionPoolConfiguration.builder() + .strictTransactionReplayProtectionEnabled(true) + .txPoolImplementation(LAYERED) + .pendingTransactionsLayerMaxCapacityBytes(1_000_000L) + .maxPrioritizedTransactions(1000) + .maxFutureBySender(10) + .build(); + } + + @Override + protected TransactionPoolOptions optionsFromDomainObject( + final TransactionPoolConfiguration domainObject) { + return TransactionPoolOptions.fromConfig(domainObject); + } + + @Override + protected TransactionPoolOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { + return besuCommand.getStableTransactionPoolOptions(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptionsTest.java new file mode 100644 index 00000000000..4fc44f05167 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/unstable/TransactionPoolOptionsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.unstable; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.cli.converter.DurationMillisConverter; +import org.hyperledger.besu.cli.options.AbstractCLIOptionsTest; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; + +import java.time.Duration; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TransactionPoolOptionsTest + extends AbstractCLIOptionsTest { + + @Test + public void txMessageKeepAliveSeconds() { + final int txMessageKeepAliveSeconds = 999; + internalTestSuccess( + config -> + assertThat(config.getTxMessageKeepAliveSeconds()).isEqualTo(txMessageKeepAliveSeconds), + "--Xincoming-tx-messages-keep-alive-seconds", + String.valueOf(txMessageKeepAliveSeconds)); + } + + @Test + public void txMessageKeepAliveSecondsWithInvalidInputShouldFail() { + internalTestFailure( + "Invalid value for option '--Xincoming-tx-messages-keep-alive-seconds': 'acbd' is not an int", + "--Xincoming-tx-messages-keep-alive-seconds", + "acbd"); + } + + @Test + public void eth65TrxAnnouncedBufferingPeriod() { + final Duration eth65TrxAnnouncedBufferingPeriod = Duration.ofMillis(999); + internalTestSuccess( + config -> + assertThat(config.getEth65TrxAnnouncedBufferingPeriod()) + .isEqualTo(eth65TrxAnnouncedBufferingPeriod), + "--Xeth65-tx-announced-buffering-period-milliseconds", + new DurationMillisConverter().format(eth65TrxAnnouncedBufferingPeriod)); + } + + @Test + public void eth65TrxAnnouncedBufferingPeriodWithInvalidInputShouldFail() { + internalTestFailure( + "Invalid value for option '--Xeth65-tx-announced-buffering-period-milliseconds': cannot convert 'acbd' to Duration (org.hyperledger.besu.cli.converter.exception.DurationConversionException: 'acbd' is not a long)", + "--Xeth65-tx-announced-buffering-period-milliseconds", + "acbd"); + } + + @Test + public void eth65TrxAnnouncedBufferingPeriodWithInvalidInputShouldFail2() { + internalTestFailure( + "Invalid value for option '--Xeth65-tx-announced-buffering-period-milliseconds': cannot convert '-1' to Duration (org.hyperledger.besu.cli.converter.exception.DurationConversionException: negative value '-1' is not allowed)", + "--Xeth65-tx-announced-buffering-period-milliseconds", + "-1"); + } + + @Override + protected TransactionPoolConfiguration.Unstable createDefaultDomainObject() { + return TransactionPoolConfiguration.Unstable.DEFAULT; + } + + @Override + protected TransactionPoolConfiguration.Unstable createCustomizedDomainObject() { + return ImmutableTransactionPoolConfiguration.Unstable.builder() + .txMessageKeepAliveSeconds( + TransactionPoolConfiguration.Unstable.DEFAULT_TX_MSG_KEEP_ALIVE + 1) + .eth65TrxAnnouncedBufferingPeriod( + TransactionPoolConfiguration.Unstable.ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD.plus( + Duration.ofMillis(100))) + .build(); + } + + @Override + protected TransactionPoolOptions optionsFromDomainObject( + final TransactionPoolConfiguration.Unstable domainObject) { + return TransactionPoolOptions.fromConfig(domainObject); + } + + @Override + protected TransactionPoolOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { + return besuCommand.getUnstableTransactionPoolOptions(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java index fd35430e273..c7c6466e602 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.controller; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -55,6 +54,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.math.BigInteger; @@ -85,16 +85,17 @@ public class BesuControllerBuilderTest { @Mock SynchronizerConfiguration synchronizerConfiguration; @Mock EthProtocolConfiguration ethProtocolConfiguration; @Mock MiningParameters miningParameters; - @Mock ObservableMetricsSystem observableMetricsSystem; @Mock PrivacyParameters privacyParameters; @Mock Clock clock; - @Mock TransactionPoolConfiguration poolConfiguration; @Mock StorageProvider storageProvider; @Mock GasLimitCalculator gasLimitCalculator; @Mock WorldStateStorage worldStateStorage; @Mock WorldStateArchive worldStateArchive; @Mock BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorage; @Mock WorldStatePreimageStorage worldStatePreimageStorage; + private final TransactionPoolConfiguration poolConfiguration = + TransactionPoolConfiguration.DEFAULT; + private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); BigInteger networkId = BigInteger.ONE; @@ -127,10 +128,6 @@ public void setup() { when(synchronizerConfiguration.getBlockPropagationRange()).thenReturn(Range.closed(1L, 2L)); - when(observableMetricsSystem.createLabelledCounter( - any(), anyString(), anyString(), anyString())) - .thenReturn(labels -> null); - when(storageProvider.createWorldStateStorage(DataStorageFormat.FOREST)) .thenReturn(worldStateStorage); when(storageProvider.createWorldStatePreimageStorage()).thenReturn(worldStatePreimageStorage); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java index 15fb3fd3ec2..b1f59554af3 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java @@ -18,7 +18,6 @@ import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -58,6 +57,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.math.BigInteger; @@ -90,10 +90,8 @@ public class MergeBesuControllerBuilderTest { @Mock EthProtocolConfiguration ethProtocolConfiguration; @Mock CheckpointConfigOptions checkpointConfigOptions; @Mock MiningParameters miningParameters; - @Mock ObservableMetricsSystem observableMetricsSystem; @Mock PrivacyParameters privacyParameters; @Mock Clock clock; - @Mock TransactionPoolConfiguration poolConfiguration; @Mock StorageProvider storageProvider; @Mock GasLimitCalculator gasLimitCalculator; @Mock WorldStateStorage worldStateStorage; @@ -102,6 +100,9 @@ public class MergeBesuControllerBuilderTest { BigInteger networkId = BigInteger.ONE; private final BlockHeaderTestFixture headerGenerator = new BlockHeaderTestFixture(); private final BaseFeeMarket feeMarket = new LondonFeeMarket(0, Optional.of(Wei.of(42))); + private final TransactionPoolConfiguration poolConfiguration = + TransactionPoolConfiguration.DEFAULT; + private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); @Rule public final TemporaryFolder tempDirRule = new TemporaryFolder(); @@ -134,10 +135,6 @@ public void setup() { when(synchronizerConfiguration.getBlockPropagationRange()).thenReturn(Range.closed(1L, 2L)); - when(observableMetricsSystem.createLabelledCounter( - any(), anyString(), anyString(), anyString())) - .thenReturn(labels -> null); - when(storageProvider.createWorldStateStorage(DataStorageFormat.FOREST)) .thenReturn(worldStateStorage); when(storageProvider.createWorldStatePreimageStorage()).thenReturn(worldStatePreimageStorage); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java index 395abeb22d6..585f138027e 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java @@ -17,7 +17,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -53,6 +52,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.math.BigInteger; @@ -80,16 +80,17 @@ public class QbftBesuControllerBuilderTest { @Mock private EthProtocolConfiguration ethProtocolConfiguration; @Mock CheckpointConfigOptions checkpointConfigOptions; @Mock private MiningParameters miningParameters; - @Mock private ObservableMetricsSystem observableMetricsSystem; @Mock private PrivacyParameters privacyParameters; @Mock private Clock clock; - @Mock private TransactionPoolConfiguration poolConfiguration; @Mock private StorageProvider storageProvider; @Mock private GasLimitCalculator gasLimitCalculator; @Mock private WorldStateStorage worldStateStorage; @Mock private WorldStatePreimageStorage worldStatePreimageStorage; private static final BigInteger networkId = BigInteger.ONE; private static final NodeKey nodeKey = NodeKeyUtils.generate(); + private final TransactionPoolConfiguration poolConfiguration = + TransactionPoolConfiguration.DEFAULT; + private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); @Rule public final TemporaryFolder tempDirRule = new TemporaryFolder(); @@ -120,9 +121,7 @@ public void setup() { when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); - when(observableMetricsSystem.createLabelledCounter( - any(), anyString(), anyString(), anyString())) - .thenReturn(labels -> null); + when(synchronizerConfiguration.getBlockPropagationRange()).thenReturn(Range.closed(1L, 2L)); // qbft prepForBuild setup diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index 459346dc537..e88acec6dc9 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -170,16 +170,21 @@ privacy-onchain-groups-enabled=false privacy-flexible-groups-enabled=false # Transaction Pool -tx-pool-retention-hours=999 +tx-pool="layered" tx-pool-price-bump=13 -tx-pool-max-size=1234 -tx-pool-limit-by-account-percentage=0.017 -Xincoming-tx-messages-keep-alive-seconds=60 rpc-tx-feecap=2000000000000000000 strict-tx-replay-protection-enabled=true tx-pool-disable-locals=false tx-pool-enable-save-restore=true tx-pool-save-file="txpool.dump" +## Layered +tx-pool-layer-max-capacity=12345678 +tx-pool-max-prioritized=9876 +tx-pool-max-future-by-sender=321 +## Legacy +tx-pool-retention-hours=999 +tx-pool-max-size=1234 +tx-pool-limit-by-account-percentage=0.017 # Revert Reason revert-reason-enabled=false 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 95f1d24f220..d118a186af1 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 @@ -76,6 +76,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.StubMetricsSystem; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; import java.time.ZoneId; import java.util.ArrayList; @@ -161,7 +162,7 @@ public class MergeCoordinatorTest implements MergeGenesisConfigHelper { private final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(10) - .txPoolLimitByAccountPercentage(100.0f) + .txPoolLimitByAccountPercentage(Fraction.fromPercentage(100)) .build(); private final BaseFeePendingTransactionsSorter transactions = new BaseFeePendingTransactionsSorter( 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 c0e68efd706..261c3d5f895 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 @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; import java.time.ZoneId; import java.util.function.Function; @@ -64,7 +65,7 @@ protected TransactionPool createTransactionPool() { final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(5) - .txPoolLimitByAccountPercentage(1) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1f)) .build(); final PendingTransactions pendingTransactions = 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 82cb1b0d89b..252f7f03655 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 @@ -39,6 +39,7 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; import java.time.ZoneId; import java.util.List; @@ -72,7 +73,7 @@ protected TransactionPool createTransactionPool() { final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(5) - .txPoolLimitByAccountPercentage(1) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1f)) .build(); final PendingTransactions pendingTransactions = new BaseFeePendingTransactionsSorter( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java index 4717e2d9e1d..ede1b60beb1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java @@ -101,8 +101,12 @@ private void processNewPooledTransactionHashesMessage( .getScheduler() .scheduleFutureTaskWithFixedDelay( new FetcherCreatorTask(peer), - transactionPoolConfiguration.getEth65TrxAnnouncedBufferingPeriod(), - transactionPoolConfiguration.getEth65TrxAnnouncedBufferingPeriod()); + transactionPoolConfiguration + .getUnstable() + .getEth65TrxAnnouncedBufferingPeriod(), + transactionPoolConfiguration + .getUnstable() + .getEth65TrxAnnouncedBufferingPeriod()); return new BufferedGetPooledTransactionsFromPeerFetcher( ethContext, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java index ddcb06f8a9d..fbab05d2337 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.eth.transactions; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; import java.io.File; @@ -24,16 +25,41 @@ @Value.Immutable @Value.Style(allParameters = true) +@Value.Enclosing public interface TransactionPoolConfiguration { + + @Value.Immutable + interface Unstable { + Duration ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD = Duration.ofMillis(500); + int DEFAULT_TX_MSG_KEEP_ALIVE = 60; + + TransactionPoolConfiguration.Unstable DEFAULT = + ImmutableTransactionPoolConfiguration.Unstable.builder().build(); + + @Value.Default + default Duration getEth65TrxAnnouncedBufferingPeriod() { + return ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD; + } + + @Value.Default + default int getTxMessageKeepAliveSeconds() { + return DEFAULT_TX_MSG_KEEP_ALIVE; + } + } + + enum Implementation { + LEGACY, + LAYERED; + } + String DEFAULT_SAVE_FILE_NAME = "txpool.dump"; - int DEFAULT_TX_MSG_KEEP_ALIVE = 60; + int DEFAULT_MAX_PENDING_TRANSACTIONS = 4096; - float DEFAULT_LIMIT_TX_POOL_BY_ACCOUNT_PERCENTAGE = 0.001f; // 0.1% + Fraction DEFAULT_LIMIT_TX_POOL_BY_ACCOUNT_PERCENTAGE = Fraction.fromFloat(0.001f); // 0.1% int DEFAULT_TX_RETENTION_HOURS = 13; boolean DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED = false; Percentage DEFAULT_PRICE_BUMP = Percentage.fromInt(10); Wei DEFAULT_RPC_TX_FEE_CAP = Wei.fromEth(1); - Duration ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD = Duration.ofMillis(500); boolean DEFAULT_DISABLE_LOCAL_TXS = false; boolean DEFAULT_ENABLE_SAVE_RESTORE = false; @@ -41,7 +67,7 @@ public interface TransactionPoolConfiguration { long DEFAULT_PENDING_TRANSACTIONS_LAYER_MAX_CAPACITY_BYTES = 50_000_000L; int DEFAULT_MAX_PRIORITIZED_TRANSACTIONS = 2000; int DEFAULT_MAX_FUTURE_BY_SENDER = 200; - boolean DEFAULT_LAYERED_TX_POOL_ENABLED = false; + Implementation DEFAULT_TX_POOL_IMPLEMENTATION = Implementation.LAYERED; TransactionPoolConfiguration DEFAULT = ImmutableTransactionPoolConfiguration.builder().build(); @@ -51,13 +77,13 @@ default int getTxPoolMaxSize() { } @Value.Default - default float getTxPoolLimitByAccountPercentage() { + default Fraction getTxPoolLimitByAccountPercentage() { return DEFAULT_LIMIT_TX_POOL_BY_ACCOUNT_PERCENTAGE; } @Value.Derived default int getTxPoolMaxFutureTransactionByAccount() { - return (int) Math.ceil(getTxPoolLimitByAccountPercentage() * getTxPoolMaxSize()); + return (int) Math.ceil(getTxPoolLimitByAccountPercentage().getValue() * getTxPoolMaxSize()); } @Value.Default @@ -65,21 +91,11 @@ default int getPendingTxRetentionPeriod() { return DEFAULT_TX_RETENTION_HOURS; } - @Value.Default - default int getTxMessageKeepAliveSeconds() { - return DEFAULT_TX_MSG_KEEP_ALIVE; - } - @Value.Default default Percentage getPriceBump() { return DEFAULT_PRICE_BUMP; } - @Value.Default - default Duration getEth65TrxAnnouncedBufferingPeriod() { - return ETH65_TRX_ANNOUNCED_BUFFERING_PERIOD; - } - @Value.Default default Wei getTxFeeCap() { return DEFAULT_RPC_TX_FEE_CAP; @@ -106,8 +122,8 @@ default File getSaveFile() { } @Value.Default - default Boolean getLayeredTxPoolEnabled() { - return DEFAULT_LAYERED_TX_POOL_ENABLED; + default Implementation getTxPoolImplementation() { + return DEFAULT_TX_POOL_IMPLEMENTATION; } @Value.Default @@ -124,4 +140,9 @@ default int getMaxPrioritizedTransactions() { default int getMaxFutureBySender() { return DEFAULT_MAX_FUTURE_BY_SENDER; } + + @Value.Default + default Unstable getUnstable() { + return Unstable.DEFAULT; + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java index 16a05922864..08d2aae5169 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.eth.transactions; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; + import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -31,7 +33,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -115,7 +117,7 @@ static TransactionPool createTransactionPool( new TransactionsMessageHandler( ethContext.getScheduler(), new TransactionsMessageProcessor(transactionTracker, transactionPool, metrics), - transactionPoolConfiguration.getTxMessageKeepAliveSeconds()); + transactionPoolConfiguration.getUnstable().getTxMessageKeepAliveSeconds()); final NewPooledTransactionHashesMessageHandler pooledTransactionsMessageHandler = new NewPooledTransactionHashesMessageHandler( @@ -126,7 +128,7 @@ static TransactionPool createTransactionPool( transactionPoolConfiguration, ethContext, metrics), - transactionPoolConfiguration.getTxMessageKeepAliveSeconds()); + transactionPoolConfiguration.getUnstable().getTxMessageKeepAliveSeconds()); subscribeTransactionHandlers( protocolContext, @@ -194,8 +196,7 @@ private static PendingTransactions createPendingTransactions( protocolSchedule.anyMatch( scheduledSpec -> scheduledSpec.spec().getFeeMarket().implementsBaseFee()); - if (transactionPoolConfiguration.getLayeredTxPoolEnabled()) { - LOG.info("Using layered transaction pool"); + if (transactionPoolConfiguration.getTxPoolImplementation().equals(LAYERED)) { return createLayeredPendingTransactions( protocolSchedule, protocolContext, @@ -263,11 +264,10 @@ private static PendingTransactions createLayeredPendingTransactions( final AbstractPrioritizedTransactions pendingTransactionsSorter; if (isFeeMarketImplementBaseFee) { - final BaseFeeMarket baseFeeMarket = - (BaseFeeMarket) - protocolSchedule - .getByBlockHeader(protocolContext.getBlockchain().getChainHeadHeader()) - .getFeeMarket(); + final FeeMarket feeMarket = + protocolSchedule + .getByBlockHeader(protocolContext.getBlockchain().getChainHeadHeader()) + .getFeeMarket(); pendingTransactionsSorter = new BaseFeePrioritizedTransactions( @@ -276,7 +276,7 @@ private static PendingTransactions createLayeredPendingTransactions( readyTransactions, metrics, transactionReplacementTester, - baseFeeMarket); + feeMarket); } else { pendingTransactionsSorter = new GasPricePrioritizedTransactions( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java index b7fea830a7b..0feae3168de 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java @@ -330,6 +330,7 @@ protected void replaced(final PendingTransaction replacedTx) { decreaseSpaceUsed(replacedTx); metrics.incrementRemoved(replacedTx.isReceivedFromLocalSource(), REPLACED.label(), name()); internalReplaced(replacedTx); + notifyTransactionDropped(replacedTx); } protected abstract void internalReplaced(final PendingTransaction replacedTx); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java index e95d431bc6d..b715f4c324c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java @@ -50,10 +50,10 @@ public BaseFeePrioritizedTransactions( final TransactionPoolMetrics metrics, final BiFunction transactionReplacementTester, - final BaseFeeMarket baseFeeMarket) { + final FeeMarket feeMarket) { super(poolConfig, nextLayer, metrics, transactionReplacementTester); this.nextBlockBaseFee = - Optional.of(calculateNextBlockBaseFee(baseFeeMarket, chainHeadHeaderSupplier.get())); + Optional.of(calculateNextBlockBaseFee(feeMarket, chainHeadHeaderSupplier.get())); } @Override @@ -71,8 +71,7 @@ protected int compareByFee(final PendingTransaction pt1, final PendingTransactio @Override protected void internalBlockAdded(final BlockHeader blockHeader, final FeeMarket feeMarket) { - final BaseFeeMarket baseFeeMarket = (BaseFeeMarket) feeMarket; - final Wei newNextBlockBaseFee = calculateNextBlockBaseFee(baseFeeMarket, blockHeader); + final Wei newNextBlockBaseFee = calculateNextBlockBaseFee(feeMarket, blockHeader); LOG.atTrace() .setMessage("Updating base fee from {} to {}") @@ -85,13 +84,16 @@ protected void internalBlockAdded(final BlockHeader blockHeader, final FeeMarket orderByFee.addAll(pendingTransactions.values()); } - private Wei calculateNextBlockBaseFee( - final BaseFeeMarket baseFeeMarket, final BlockHeader blockHeader) { - return baseFeeMarket.computeBaseFee( - blockHeader.getNumber() + 1, - blockHeader.getBaseFee().orElse(Wei.ZERO), - blockHeader.getGasUsed(), - baseFeeMarket.targetGasUsed(blockHeader)); + private Wei calculateNextBlockBaseFee(final FeeMarket feeMarket, final BlockHeader blockHeader) { + if (feeMarket.implementsBaseFee()) { + final var baseFeeMarket = (BaseFeeMarket) feeMarket; + return baseFeeMarket.computeBaseFee( + blockHeader.getNumber() + 1, + blockHeader.getBaseFee().orElse(Wei.ZERO), + blockHeader.getGasUsed(), + baseFeeMarket.targetGasUsed(blockHeader)); + } + return Wei.ZERO; } @Override diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessorTest.java index 4b137ca3937..5c3f08ddfa1 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessorTest.java @@ -21,6 +21,7 @@ import static org.hyperledger.besu.ethereum.eth.encoding.TransactionAnnouncementDecoder.getDecoder; import static org.hyperledger.besu.ethereum.eth.encoding.TransactionAnnouncementEncoder.getEncoder; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -66,7 +67,10 @@ public class NewPooledTransactionHashesMessageProcessorTest { @Mock private TransactionPool transactionPool; - @Mock private TransactionPoolConfiguration transactionPoolConfiguration; + + @Mock(answer = RETURNS_DEEP_STUBS) + private TransactionPoolConfiguration transactionPoolConfiguration; + @Mock private PeerTransactionTracker transactionTracker; @Mock private EthPeer peer1; @Mock private EthContext ethContext; @@ -88,7 +92,7 @@ public class NewPooledTransactionHashesMessageProcessorTest { @BeforeEach public void setup() { metricsSystem = new StubMetricsSystem(); - when(transactionPoolConfiguration.getEth65TrxAnnouncedBufferingPeriod()) + when(transactionPoolConfiguration.getUnstable().getEth65TrxAnnouncedBufferingPeriod()) .thenReturn(Duration.ofMillis(500)); messageHandler = new NewPooledTransactionHashesMessageProcessor( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java index 78641da086d..fb56be15f8a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.eth.transactions; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; import static org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule.DEFAULT_CHAIN_ID; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -41,6 +42,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredPendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; @@ -242,8 +244,11 @@ private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { new MiningParameters.Builder().minTransactionGasPrice(Wei.ONE).build(), ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(1) - .txMessageKeepAliveSeconds(1) .pendingTxRetentionPeriod(1) + .unstable( + ImmutableTransactionPoolConfiguration.Unstable.builder() + .txMessageKeepAliveSeconds(1) + .build()) .build(), peerTransactionTracker, transactionsMessageSender, @@ -267,10 +272,12 @@ private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { } @Test - public void createTransactionPool_shouldUseBaseFeePendingTransactionsSorter_whenLondonEnabled() { + public void + createLegacyTransactionPool_shouldUseBaseFeePendingTransactionsSorter_whenLondonEnabled() { setupScheduleWith(new StubGenesisConfigOptions().londonBlock(0)); - final TransactionPool pool = createTransactionPool(); + final TransactionPool pool = + createTransactionPool(TransactionPoolConfiguration.Implementation.LEGACY); assertThat(pool.pendingTransactionsImplementation()) .isEqualTo(BaseFeePendingTransactionsSorter.class); @@ -278,15 +285,42 @@ public void createTransactionPool_shouldUseBaseFeePendingTransactionsSorter_when @Test public void - createTransactionPool_shouldUseGasPricePendingTransactionsSorter_whenLondonNotEnabled() { + createLegacyTransactionPool_shouldUseGasPricePendingTransactionsSorter_whenLondonNotEnabled() { setupScheduleWith(new StubGenesisConfigOptions().berlinBlock(0)); - final TransactionPool pool = createTransactionPool(); + final TransactionPool pool = + createTransactionPool(TransactionPoolConfiguration.Implementation.LEGACY); assertThat(pool.pendingTransactionsImplementation()) .isEqualTo(GasPricePendingTransactionsSorter.class); } + @Test + public void + createLayeredTransactionPool_shouldUseBaseFeePendingTransactionsSorter_whenLondonEnabled() { + setupScheduleWith(new StubGenesisConfigOptions().londonBlock(0)); + + final TransactionPool pool = createTransactionPool(LAYERED); + + assertThat(pool.pendingTransactionsImplementation()) + .isEqualTo(LayeredPendingTransactions.class); + + assertThat(pool.logStats()).startsWith("Basefee Prioritized"); + } + + @Test + public void + createLayeredTransactionPool_shouldUseGasPricePendingTransactionsSorter_whenLondonNotEnabled() { + setupScheduleWith(new StubGenesisConfigOptions().berlinBlock(0)); + + final TransactionPool pool = createTransactionPool(LAYERED); + + assertThat(pool.pendingTransactionsImplementation()) + .isEqualTo(LayeredPendingTransactions.class); + + assertThat(pool.logStats()).startsWith("GasPrice Prioritized"); + } + private void setupScheduleWith(final StubGenesisConfigOptions config) { schedule = new ProtocolScheduleBuilder( @@ -305,7 +339,8 @@ private void setupScheduleWith(final StubGenesisConfigOptions config) { syncState = new SyncState(blockchain, ethPeers, true, Optional.empty()); } - private TransactionPool createTransactionPool() { + private TransactionPool createTransactionPool( + final TransactionPoolConfiguration.Implementation implementation) { final TransactionPool txPool = TransactionPoolFactory.createTransactionPool( schedule, @@ -316,9 +351,13 @@ private TransactionPool createTransactionPool() { syncState, new MiningParameters.Builder().minTransactionGasPrice(Wei.ONE).build(), ImmutableTransactionPoolConfiguration.builder() + .txPoolImplementation(implementation) .txPoolMaxSize(1) - .txMessageKeepAliveSeconds(1) .pendingTxRetentionPeriod(1) + .unstable( + ImmutableTransactionPoolConfiguration.Unstable.builder() + .txMessageKeepAliveSeconds(1) + .build()) .build()); txPool.setEnabled(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java index e2e2946f3c9..cc40d47aec0 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; import java.math.BigInteger; import java.time.ZoneId; @@ -57,7 +58,7 @@ protected PendingTransactions createPendingTransactionsSorter() { return new GasPricePendingTransactionsSorter( ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(1) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) .build(), TestClock.system(ZoneId.systemDefault()), metricsSystem, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java index fc03f9805ce..e546684ffdc 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; import java.math.BigInteger; import java.time.ZoneId; @@ -63,7 +64,7 @@ protected PendingTransactions createPendingTransactionsSorter() { return new BaseFeePendingTransactionsSorter( ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(1) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) .build(), TestClock.system(ZoneId.systemDefault()), metricsSystem, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java index 6b2318f0086..1aff575f2e5 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java @@ -45,6 +45,7 @@ import org.hyperledger.besu.metrics.StubMetricsSystem; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; import java.time.Clock; import java.time.temporal.ChronoUnit; @@ -80,13 +81,14 @@ public abstract class AbstractPendingTransactionsTestBase { getPendingTransactions( ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(1.0f) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) .build(), Optional.empty()); private final TransactionPoolConfiguration senderLimitedConfig = ImmutableTransactionPoolConfiguration.builder() .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(LIMITED_TRANSACTIONS_BY_SENDER_PERCENTAGE) + .txPoolLimitByAccountPercentage( + Fraction.fromFloat(LIMITED_TRANSACTIONS_BY_SENDER_PERCENTAGE)) .build(); protected PendingTransactions senderLimitedTransactions = getPendingTransactions(senderLimitedConfig, Optional.empty()); @@ -622,7 +624,7 @@ public void shouldEvictMultipleOldTransactions() { ImmutableTransactionPoolConfiguration.builder() .pendingTxRetentionPeriod(maxTransactionRetentionHours) .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(1) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) .build(), Optional.of(clock)); @@ -644,7 +646,7 @@ public void shouldEvictSingleOldTransaction() { ImmutableTransactionPoolConfiguration.builder() .pendingTxRetentionPeriod(1) .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(1.0f) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) .build(), Optional.of(clock)); evictSingleTransactions.addRemoteTransaction(transaction1, Optional.empty()); @@ -662,7 +664,7 @@ public void shouldEvictExclusivelyOldTransactions() { ImmutableTransactionPoolConfiguration.builder() .pendingTxRetentionPeriod(2) .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(1.0f) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) .build(), Optional.of(clock)); 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 52cc2a3ce04..1f2af463a97 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 @@ -65,6 +65,7 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.util.Subscribers; +import org.hyperledger.besu.util.number.Fraction; import java.util.Collections; import java.util.Optional; @@ -227,7 +228,7 @@ private boolean buildContext( final TransactionPoolConfiguration transactionPoolConfiguration = ImmutableTransactionPoolConfiguration.builder() - .txPoolLimitByAccountPercentage(0.004f) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(0.004f)) .build(); transactionPool =