From 4cc6b744cf2cc13d0db22a8e7e47614fc2cfc5aa Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Wed, 13 Mar 2024 16:54:38 +0100 Subject: [PATCH] Transaction simulation service (#6686) Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 1 + .../tests/acceptance/dsl/node/BesuNode.java | 1 + .../dsl/node/ThreadBesuNodeRunner.java | 55 +++++++++++- .../node/configuration/BesuNodeFactory.java | 19 ++++- .../NodeConfigurationFactory.java | 8 +- .../dsl/transaction/NodeRequests.java | 9 +- besu/build.gradle | 2 + .../org/hyperledger/besu/cli/BesuCommand.java | 17 ++++ .../TransactionSimulationServiceImpl.java | 84 +++++++++++++++++++ .../java/org/hyperledger/besu/RunnerTest.java | 11 ++- .../chainimport/JsonBlockImporterTest.java | 3 +- .../besu/cli/CommandTestAbstract.java | 2 + .../ValidatorContractController.java | 2 +- .../internal/methods/AbstractEstimateGas.java | 4 +- .../internal/methods/DebugTraceCall.java | 2 +- .../api/jsonrpc/internal/methods/EthCall.java | 2 +- .../internal/methods/EthEstimateGas.java | 2 +- .../jsonrpc/internal/methods/TraceCall.java | 2 +- .../internal/methods/TraceCallMany.java | 2 +- .../internal/methods/TraceRawTransaction.java | 2 +- .../jsonrpc/internal/methods/EthCallTest.java | 6 +- .../methods/EthCreateAccessListTest.java | 2 +- .../internal/methods/EthEstimateGasTest.java | 2 +- .../AbstractBlockTransactionSelectorTest.java | 4 + .../TransactionProcessingResult.java | 27 ++++++ .../ethereum/transaction/CallParameter.java | 51 ++++++++++- .../transaction/TransactionSimulator.java | 19 ++++- .../TransactionSimulatorResult.java | 52 +----------- .../MainnetTransactionValidatorTest.java | 4 +- .../PermissionTransactionValidatorTest.java | 3 - ...eSmartContractPermissioningController.java | 2 +- ...martContractV2PermissioningController.java | 2 +- ...nSmartContractPermissioningController.java | 2 +- plugin-api/build.gradle | 2 +- .../data/TransactionProcessingResult.java | 7 ++ .../data/TransactionSimulationResult.java | 54 ++++++++++++ .../TransactionSimulationService.java | 42 ++++++++++ .../besu/testutil/JsonTestParameters.java | 8 +- 38 files changed, 424 insertions(+), 95 deletions(-) create mode 100644 besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java create mode 100644 plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e974eec71..33d5d44785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Update Vert.x to 4.5.4 [#6666](https://github.com/hyperledger/besu/pull/6666) - Refactor and extend `TransactionPoolValidatorService` [#6636](https://github.com/hyperledger/besu/pull/6636) - Transaction call object to accept both `input` and `data` field simultaneously if they are set to equal values [#6702](https://github.com/hyperledger/besu/pull/6702) +- Introduce `TransactionSimulationService` [#6686](https://github.com/hyperledger/besu/pull/6686) ### Bug fixes - Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java index ec9d7b1173..c822ce899c 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java @@ -436,6 +436,7 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable nodeRequests = new NodeRequests( + web3jService, new JsonRpc2_0Web3j(web3jService, 2000, Async.defaultExecutorService()), new CliqueRequestFactory(web3jService), new BftRequestFactory(web3jService, bftType), diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index 87246069e6..896a615dee 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.crypto.KeyPairUtil; import org.hyperledger.besu.cryptoservices.KeyPairSecurityModule; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; @@ -37,6 +38,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfigurati import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.MetricsSystemFactory; @@ -44,6 +46,7 @@ import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.data.EnodeURL; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BesuEvents; +import org.hyperledger.besu.plugin.services.BlockchainService; import org.hyperledger.besu.plugin.services.PermissioningService; import org.hyperledger.besu.plugin.services.PicoCLIOptions; import org.hyperledger.besu.plugin.services.PrivacyPluginService; @@ -52,10 +55,12 @@ import org.hyperledger.besu.plugin.services.SecurityModuleService; import org.hyperledger.besu.plugin.services.StorageService; import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBPlugin; import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.services.BesuEventsImpl; import org.hyperledger.besu.services.BesuPluginContextImpl; +import org.hyperledger.besu.services.BlockchainServiceImpl; import org.hyperledger.besu.services.PermissioningServiceImpl; import org.hyperledger.besu.services.PicoCLIOptionsImpl; import org.hyperledger.besu.services.PrivacyPluginServiceImpl; @@ -64,6 +69,7 @@ import org.hyperledger.besu.services.SecurityModuleServiceImpl; import org.hyperledger.besu.services.StorageServiceImpl; import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; +import org.hyperledger.besu.services.TransactionSimulationServiceImpl; import java.io.File; import java.nio.file.Path; @@ -95,18 +101,27 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { final BesuNode node, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, + final TransactionSimulationServiceImpl transactionSimulationServiceImpl, final TransactionSelectionServiceImpl transactionSelectionServiceImpl, + final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl, + final BlockchainServiceImpl blockchainServiceImpl, + final RpcEndpointServiceImpl rpcEndpointServiceImpl, final BesuConfiguration commonPluginConfiguration) { final CommandLine commandLine = new CommandLine(CommandSpec.create()); final BesuPluginContextImpl besuPluginContext = new BesuPluginContextImpl(); besuPluginContext.addService(StorageService.class, storageService); besuPluginContext.addService(SecurityModuleService.class, securityModuleService); besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); - besuPluginContext.addService(RpcEndpointService.class, new RpcEndpointServiceImpl()); + besuPluginContext.addService(RpcEndpointService.class, rpcEndpointServiceImpl); besuPluginContext.addService( TransactionSelectionService.class, transactionSelectionServiceImpl); besuPluginContext.addService( - TransactionPoolValidatorService.class, new TransactionPoolValidatorServiceImpl()); + TransactionPoolValidatorService.class, transactionPoolValidatorServiceImpl); + besuPluginContext.addService( + TransactionSimulationService.class, transactionSimulationServiceImpl); + besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); + besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); + final Path pluginsPath; final String pluginDir = System.getProperty("besu.plugins.dir"); if (pluginDir == null || pluginDir.isEmpty()) { @@ -147,8 +162,14 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { final StorageServiceImpl storageService = new StorageServiceImpl(); final SecurityModuleServiceImpl securityModuleService = new SecurityModuleServiceImpl(); + final TransactionSimulationServiceImpl transactionSimulationServiceImpl = + new TransactionSimulationServiceImpl(); final TransactionSelectionServiceImpl transactionSelectionServiceImpl = new TransactionSelectionServiceImpl(); + final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl = + new TransactionPoolValidatorServiceImpl(); + final BlockchainServiceImpl blockchainServiceImpl = new BlockchainServiceImpl(); + final RpcEndpointServiceImpl rpcEndpointServiceImpl = new RpcEndpointServiceImpl(); final Path dataDir = node.homeDirectory(); final BesuConfigurationImpl commonPluginConfiguration = new BesuConfigurationImpl(); final var miningParameters = @@ -169,7 +190,11 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { node, storageService, securityModuleService, + transactionSimulationServiceImpl, transactionSelectionServiceImpl, + transactionPoolValidatorServiceImpl, + blockchainServiceImpl, + rpcEndpointServiceImpl, commonPluginConfiguration)); GlobalOpenTelemetry.resetForTest(); @@ -203,6 +228,7 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { ImmutableTransactionPoolConfiguration.builder() .from(node.getTransactionPoolConfiguration()) .strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled()) + .transactionPoolValidatorService(transactionPoolValidatorServiceImpl) .build(); final int maxPeers = 25; @@ -236,6 +262,10 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { final BesuController besuController = builder.build(); + initTransactionSimulationService( + transactionSimulationServiceImpl, besuController, node.getApiConfiguration()); + initBlockchainService(blockchainServiceImpl, besuController); + final RunnerBuilder runnerBuilder = new RunnerBuilder(); runnerBuilder.permissioningConfiguration(node.getPermissioningConfiguration()); runnerBuilder.apiConfiguration(node.getApiConfiguration()); @@ -265,7 +295,7 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { .besuPluginContext(new BesuPluginContextImpl()) .autoLogBloomCaching(false) .storageProvider(storageProvider) - .rpcEndpointService(new RpcEndpointServiceImpl()); + .rpcEndpointService(rpcEndpointServiceImpl); node.engineRpcConfiguration().ifPresent(runnerBuilder::engineJsonRpcConfiguration); final Runner runner = runnerBuilder.build(); @@ -289,6 +319,25 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { MDC.remove("node"); } + private void initBlockchainService( + final BlockchainServiceImpl blockchainServiceImpl, final BesuController besuController) { + blockchainServiceImpl.init( + besuController.getProtocolContext(), besuController.getProtocolSchedule()); + } + + private void initTransactionSimulationService( + final TransactionSimulationServiceImpl transactionSimulationService, + final BesuController besuController, + final ApiConfiguration apiConfiguration) { + transactionSimulationService.init( + besuController.getProtocolContext().getBlockchain(), + new TransactionSimulator( + besuController.getProtocolContext().getBlockchain(), + besuController.getProtocolContext().getWorldStateArchive(), + besuController.getProtocolSchedule(), + apiConfiguration.getGasCap())); + } + @Override public void stopNode(final BesuNode node) { final BesuPluginContextImpl pluginContext = besuPluginContextMap.remove(node); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index 65d257fbfb..ff6dc2ac9b 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -49,6 +49,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.UnaryOperator; import io.vertx.core.Vertx; @@ -376,17 +377,27 @@ public class BesuNodeFactory { public BesuNode createCliqueNode(final String name, final CliqueOptions cliqueOptions) throws IOException { - return createCliqueNodeWithExtraCliOptions(name, cliqueOptions, List.of()); + return createCliqueNodeWithExtraCliOptionsAndRpcApis(name, cliqueOptions, List.of()); } - public BesuNode createCliqueNodeWithExtraCliOptions( + public BesuNode createCliqueNodeWithExtraCliOptionsAndRpcApis( final String name, final CliqueOptions cliqueOptions, final List extraCliOptions) throws IOException { + return createCliqueNodeWithExtraCliOptionsAndRpcApis( + name, cliqueOptions, extraCliOptions, Set.of()); + } + + public BesuNode createCliqueNodeWithExtraCliOptionsAndRpcApis( + final String name, + final CliqueOptions cliqueOptions, + final List extraCliOptions, + final Set extraRpcApis) + throws IOException { return create( new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig()) + .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig(extraRpcApis)) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .devMode(false) .jsonRpcTxPool() @@ -584,7 +595,7 @@ public class BesuNodeFactory { new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig()) + .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig(Set.of())) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .jsonRpcTxPool() .devMode(false) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java index f2682993f8..219d15d1ad 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java @@ -30,8 +30,10 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.Gene import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; public class NodeConfigurationFactory { @@ -44,8 +46,10 @@ public class NodeConfigurationFactory { return genesisConfigProvider.create(nodes); } - public JsonRpcConfiguration createJsonRpcWithCliqueEnabledConfig() { - return createJsonRpcWithRpcApiEnabledConfig(CLIQUE.name()); + public JsonRpcConfiguration createJsonRpcWithCliqueEnabledConfig(final Set extraRpcApis) { + final var enabledApis = new HashSet<>(extraRpcApis); + enabledApis.add(CLIQUE.name()); + return createJsonRpcWithRpcApiEnabledConfig(enabledApis.toArray(String[]::new)); } public JsonRpcConfiguration createJsonRpcWithIbft2EnabledConfig(final boolean minerEnabled) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java index 4741e397dd..1151100065 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java @@ -27,10 +27,11 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolReques import java.util.Optional; import org.web3j.protocol.Web3j; +import org.web3j.protocol.Web3jService; import org.web3j.protocol.websocket.WebSocketService; public class NodeRequests { - + private final Web3jService web3jService; private final Web3j netEth; private final CliqueRequestFactory clique; private final BftRequestFactory bft; @@ -44,6 +45,7 @@ public class NodeRequests { private final TxPoolRequestFactory txPool; public NodeRequests( + final Web3jService web3jService, final Web3j netEth, final CliqueRequestFactory clique, final BftRequestFactory bft, @@ -55,6 +57,7 @@ public class NodeRequests { final TxPoolRequestFactory txPool, final Optional websocketService, final LoginRequestFactory login) { + this.web3jService = web3jService; this.netEth = netEth; this.clique = clique; this.bft = bft; @@ -116,4 +119,8 @@ public class NodeRequests { netEth.shutdown(); websocketService.ifPresent(WebSocketService::close); } + + public Web3jService getWeb3jService() { + return web3jService; + } } diff --git a/besu/build.gradle b/besu/build.gradle index 0b31c4a43c..85c17e7ff3 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -28,6 +28,8 @@ jar { } dependencies { + api project(':datatypes') + api 'org.slf4j:slf4j-api' implementation project(':config') 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 6709608834..d5b6468a90 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -139,6 +139,7 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.trie.forest.pruner.PrunerConfiguration; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.precompile.AbstractAltBnPrecompiledContract; @@ -167,6 +168,7 @@ import org.hyperledger.besu.plugin.services.StorageService; import org.hyperledger.besu.plugin.services.TraceService; import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; @@ -187,6 +189,7 @@ import org.hyperledger.besu.services.StorageServiceImpl; import org.hyperledger.besu.services.TraceServiceImpl; import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; +import org.hyperledger.besu.services.TransactionSimulationServiceImpl; import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin; import org.hyperledger.besu.util.InvalidConfigurationException; import org.hyperledger.besu.util.LogConfigurator; @@ -370,6 +373,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private final TransactionSelectionServiceImpl transactionSelectionServiceImpl; private final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl; + private final TransactionSimulationServiceImpl transactionSimulationServiceImpl; private final BlockchainServiceImpl blockchainServiceImpl; static class P2PDiscoveryOptionGroup { @@ -956,6 +960,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { new RpcEndpointServiceImpl(), new TransactionSelectionServiceImpl(), new TransactionPoolValidatorServiceImpl(), + new TransactionSimulationServiceImpl(), new BlockchainServiceImpl()); } @@ -978,6 +983,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { * @param rpcEndpointServiceImpl instance of RpcEndpointServiceImpl * @param transactionSelectionServiceImpl instance of TransactionSelectionServiceImpl * @param transactionValidatorServiceImpl instance of TransactionValidatorServiceImpl + * @param transactionSimulationServiceImpl instance of TransactionSimulationServiceImpl * @param blockchainServiceImpl instance of BlockchainServiceImpl */ @VisibleForTesting @@ -998,6 +1004,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final RpcEndpointServiceImpl rpcEndpointServiceImpl, final TransactionSelectionServiceImpl transactionSelectionServiceImpl, final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl, + final TransactionSimulationServiceImpl transactionSimulationServiceImpl, final BlockchainServiceImpl blockchainServiceImpl) { this.besuComponent = besuComponent; this.logger = besuComponent.getBesuCommandLogger(); @@ -1018,6 +1025,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { this.rpcEndpointServiceImpl = rpcEndpointServiceImpl; this.transactionSelectionServiceImpl = transactionSelectionServiceImpl; this.transactionValidatorServiceImpl = transactionValidatorServiceImpl; + this.transactionSimulationServiceImpl = transactionSimulationServiceImpl; this.blockchainServiceImpl = blockchainServiceImpl; } @@ -1210,6 +1218,8 @@ public class BesuCommand implements DefaultCommandValues, Runnable { TransactionSelectionService.class, transactionSelectionServiceImpl); besuPluginContext.addService( TransactionPoolValidatorService.class, transactionValidatorServiceImpl); + besuPluginContext.addService( + TransactionSimulationService.class, transactionSimulationServiceImpl); besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); // register built-in plugins @@ -1293,6 +1303,13 @@ public class BesuCommand implements DefaultCommandValues, Runnable { private void startPlugins() { blockchainServiceImpl.init( besuController.getProtocolContext(), besuController.getProtocolSchedule()); + transactionSimulationServiceImpl.init( + besuController.getProtocolContext().getBlockchain(), + new TransactionSimulator( + besuController.getProtocolContext().getBlockchain(), + besuController.getProtocolContext().getWorldStateArchive(), + besuController.getProtocolSchedule(), + apiConfiguration.getGasCap())); besuPluginContext.addService( BesuEvents.class, diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java new file mode 100644 index 0000000000..5ebf48f0ce --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java @@ -0,0 +1,84 @@ +/* + * 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.services; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.transaction.CallParameter; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.TransactionSimulationResult; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; + +import java.util.Optional; + +/** TransactionSimulationServiceImpl */ +@Unstable +public class TransactionSimulationServiceImpl implements TransactionSimulationService { + private static final TransactionValidationParams SIMULATOR_ALLOWING_EXCEEDING_BALANCE = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(true) + .build(); + private Blockchain blockchain; + private TransactionSimulator transactionSimulator; + + /** Create an instance to be configured */ + public TransactionSimulationServiceImpl() {} + + /** + * Configure the service + * + * @param blockchain the blockchain + * @param transactionSimulator transaction simulator + */ + public void init(final Blockchain blockchain, final TransactionSimulator transactionSimulator) { + this.blockchain = blockchain; + this.transactionSimulator = transactionSimulator; + } + + @Override + public Optional simulate( + final Transaction transaction, + final Hash blockHash, + final OperationTracer operationTracer, + final boolean isAllowExceedingBalance) { + + final CallParameter callParameter = CallParameter.fromTransaction(transaction); + + final var blockHeader = + blockchain + .getBlockHeader(blockHash) + .or(() -> blockchain.getBlockHeaderSafe(blockHash)) + .orElseThrow( + () -> + new IllegalStateException( + "Block header not yet present for chain head hash: " + blockHash)); + + return transactionSimulator + .process( + callParameter, + isAllowExceedingBalance + ? SIMULATOR_ALLOWING_EXCEEDING_BALANCE + : TransactionValidationParams.transactionSimulator(), + operationTracer, + blockHeader) + .map(res -> new TransactionSimulationResult(transaction, res.result())); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java index d624064e5c..55cdee554b 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java @@ -180,7 +180,8 @@ public final class RunnerTest { aheadDbNodeKey, createKeyValueStorageProvider( dataDirAhead, dbAhead, dataStorageConfiguration, miningParameters), - noOpMetricsSystem); + noOpMetricsSystem, + miningParameters); setupState( blockCount, controllerAhead.getProtocolSchedule(), controllerAhead.getProtocolContext()); @@ -235,7 +236,8 @@ public final class RunnerTest { dataDirBehind, behindDbNodeKey, new InMemoryKeyValueStorageProvider(), - noOpMetricsSystem); + noOpMetricsSystem, + miningParameters); final EnodeURL aheadEnode = runnerAhead.getLocalEnode().get(); final EthNetworkConfig behindEthNetworkConfiguration = @@ -452,14 +454,15 @@ public final class RunnerTest { final Path dataDir, final NodeKey nodeKey, final StorageProvider storageProvider, - final ObservableMetricsSystem metricsSystem) { + final ObservableMetricsSystem metricsSystem, + final MiningParameters miningParameters) { return new MainnetBesuControllerBuilder() .genesisConfigFile(genesisConfig) .synchronizerConfiguration(syncConfig) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .dataDirectory(dataDir) .networkId(NETWORK_ID) - .miningParameters(MiningParameters.newDefault()) + .miningParameters(miningParameters) .nodeKey(nodeKey) .storageProvider(storageProvider) .metricsSystem(metricsSystem) diff --git a/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java index 4b59491dc1..dff42ef654 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java @@ -432,8 +432,7 @@ public abstract class JsonBlockImporterTest { return createController(genesisConfigFile); } - protected BesuController createController(final GenesisConfigFile genesisConfigFile) - throws IOException { + protected BesuController createController(final GenesisConfigFile genesisConfigFile) { return new BesuController.Builder() .fromGenesisConfig(genesisConfigFile, SyncMode.FAST) .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) 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 d784733608..63f664d746 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -92,6 +92,7 @@ import org.hyperledger.besu.services.SecurityModuleServiceImpl; import org.hyperledger.besu.services.StorageServiceImpl; import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; +import org.hyperledger.besu.services.TransactionSimulationServiceImpl; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.io.ByteArrayOutputStream; @@ -573,6 +574,7 @@ public abstract class CommandTestAbstract { rpcEndpointServiceImpl, new TransactionSelectionServiceImpl(), new TransactionPoolValidatorServiceImpl(), + new TransactionSimulationServiceImpl(), new BlockchainServiceImpl()); } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java index a7b705e4a9..e6786a92ef 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java @@ -107,7 +107,7 @@ public class ValidatorContractController { if (result.isSuccessful()) { final List decodedList = FunctionReturnDecoder.decode( - result.getResult().getOutput().toHexString(), function.getOutputParameters()); + result.result().getOutput().toHexString(), function.getOutputParameters()); if (decodedList.isEmpty()) { throw new IllegalStateException( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index b3de5094c8..0f9b1b7be1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -90,7 +90,7 @@ public abstract class AbstractEstimateGas implements JsonRpcMethod { Math.pow(SUB_CALL_REMAINING_GAS_RATIO, operationTracer.getMaxDepth()); // and minimum gas remaining is necessary for some operation (additionalStipend) final long gasStipend = operationTracer.getStipendNeeded(); - final long gasUsedByTransaction = result.getResult().getEstimateGasUsedByTransaction(); + final long gasUsedByTransaction = result.result().getEstimateGasUsedByTransaction(); return ((long) ((gasUsedByTransaction + gasStipend) * subCallMultiplier)); } @@ -123,7 +123,7 @@ public abstract class AbstractEstimateGas implements JsonRpcMethod { JsonRpcErrorConverter.convertTransactionInvalidReason( validationResult.getInvalidReason())); } else { - final TransactionProcessingResult resultTrx = result.getResult(); + final TransactionProcessingResult resultTrx = result.result(); if (resultTrx != null && resultTrx.getRevertReason().isPresent()) { return errorResponse( request, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java index ae5215282f..72aa575b0c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java @@ -83,7 +83,7 @@ public class DebugTraceCall extends AbstractTraceCall { final TransactionTrace transactionTrace = new TransactionTrace( - result.getTransaction(), result.getResult(), tracer.getTraceFrames()); + result.transaction(), result.result(), tracer.getTraceFrames()); return new DebugTraceTransactionResult(transactionTrace); }); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java index 569563ca93..698685f745 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java @@ -117,7 +117,7 @@ public class EthCall extends AbstractBlockParameterOrBlockHashMethod { JsonRpcErrorConverter.convertTransactionInvalidReason( validationResult.getInvalidReason())); } else { - final TransactionProcessingResult resultTrx = result.getResult(); + final TransactionProcessingResult resultTrx = result.result(); if (resultTrx != null && resultTrx.getRevertReason().isPresent()) { return errorResponse( request, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java index 9a382d6441..c0631bf44c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java @@ -85,7 +85,7 @@ public class EthEstimateGas extends AbstractEstimateGas { return errorResponse(requestContext, gasUsed.get()); } - var low = gasUsed.get().getResult().getEstimateGasUsedByTransaction(); + var low = gasUsed.get().result().getEstimateGasUsedByTransaction(); var lowResult = executeSimulation( blockHeader, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java index 439140fa31..06559af1ef 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java @@ -73,7 +73,7 @@ public class TraceCall extends AbstractTraceCall { final TransactionTrace transactionTrace = new TransactionTrace( - result.getTransaction(), result.getResult(), tracer.getTraceFrames()); + result.transaction(), result.result(), tracer.getTraceFrames()); final Block block = blockchainQueriesSupplier.get().getBlockchain().getChainHeadBlock(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java index a824b35f1e..611ebee8b0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java @@ -166,7 +166,7 @@ public class TraceCallMany extends TraceCall implements JsonRpcMethod { final TransactionTrace transactionTrace = new TransactionTrace( - simulatorResult.getTransaction(), simulatorResult.getResult(), tracer.getTraceFrames()); + simulatorResult.transaction(), simulatorResult.result(), tracer.getTraceFrames()); final Block block = blockchainQueriesSupplier.get().getBlockchain().getChainHeadBlock(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java index 26da6f2f13..ad34d92b49 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java @@ -102,7 +102,7 @@ public class TraceRawTransaction extends AbstractTraceByBlock implements JsonRpc result -> { final TransactionTrace transactionTrace = new TransactionTrace( - result.getTransaction(), result.getResult(), tracer.getTraceFrames()); + result.transaction(), result.result(), tracer.getTraceFrames()); final Optional maybeBlock = blockchainQueriesSupplier .get() diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java index 8aba16bfb9..261f8de505 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java @@ -197,7 +197,7 @@ public class EthCallTest { final TransactionSimulatorResult result = mock(TransactionSimulatorResult.class); when(result.isSuccessful()).thenReturn(false); when(result.getValidationResult()).thenReturn(ValidationResult.valid()); - when(result.getResult()).thenReturn(processingResult); + when(result.result()).thenReturn(processingResult); verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any()); assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result))) .isEqualTo(Optional.of(expectedResponse)); @@ -236,7 +236,7 @@ public class EthCallTest { final TransactionSimulatorResult result = mock(TransactionSimulatorResult.class); when(result.isSuccessful()).thenReturn(false); when(result.getValidationResult()).thenReturn(ValidationResult.valid()); - when(result.getResult()).thenReturn(processingResult); + when(result.result()).thenReturn(processingResult); verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any()); assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result))) .isEqualTo(Optional.of(expectedResponse)); @@ -277,7 +277,7 @@ public class EthCallTest { final TransactionSimulatorResult result = mock(TransactionSimulatorResult.class); when(result.isSuccessful()).thenReturn(false); when(result.getValidationResult()).thenReturn(ValidationResult.valid()); - when(result.getResult()).thenReturn(processingResult); + when(result.result()).thenReturn(processingResult); verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any()); System.out.println(result); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java index 0d129d2665..d23c10f23b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java @@ -300,7 +300,7 @@ public class EthCreateAccessListTest { when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getRevertReason()) .thenReturn(isReverted ? Optional.of(Bytes.of(0)) : Optional.empty()); - when(mockTxSimResult.getResult()).thenReturn(mockResult); + when(mockTxSimResult.result()).thenReturn(mockResult); when(mockTxSimResult.isSuccessful()).thenReturn(isSuccessful); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java index 307424ea9a..64d015d89a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java @@ -451,7 +451,7 @@ public class EthEstimateGasTest { when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getRevertReason()).thenReturn(revertReason); - when(mockTxSimResult.getResult()).thenReturn(mockResult); + when(mockTxSimResult.result()).thenReturn(mockResult); when(mockTxSimResult.isSuccessful()).thenReturn(isSuccessful); return mockTxSimResult; } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java index 4c843b2eae..a3b657dbc3 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java @@ -195,6 +195,10 @@ public abstract class AbstractBlockTransactionSelectorTest { return false; } + protected Wei getMinGasPrice() { + return Wei.ONE; + } + protected ProcessableBlockHeader createBlock(final long gasLimit) { return createBlock(gasLimit, Wei.ONE); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java index c053e1107b..e77062a7dd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java @@ -204,4 +204,31 @@ public class TransactionProcessingResult public Optional getRevertReason() { return revertReason; } + + @Override + public Optional getInvalidReason() { + return (validationResult.isValid() + ? Optional.empty() + : Optional.of(validationResult.getErrorMessage())); + } + + @Override + public String toString() { + return "TransactionProcessingResult{" + + "status=" + + status + + ", estimateGasUsedByTransaction=" + + estimateGasUsedByTransaction + + ", gasRemaining=" + + gasRemaining + + ", logs=" + + logs + + ", output=" + + output + + ", validationResult=" + + validationResult + + ", revertReason=" + + revertReason + + '}'; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java index e5d37e399e..d74f17baef 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java @@ -199,18 +199,61 @@ public class CallParameter { blobVersionedHashes); } + @Override + public String toString() { + return "CallParameter{" + + "from=" + + from + + ", to=" + + to + + ", gasLimit=" + + gasLimit + + ", maxPriorityFeePerGas=" + + maxPriorityFeePerGas.map(Wei::toHumanReadableString).orElse("N/A") + + ", maxFeePerGas=" + + maxFeePerGas.map(Wei::toHumanReadableString).orElse("N/A") + + ", maxFeePerBlobGas=" + + maxFeePerBlobGas.map(Wei::toHumanReadableString).orElse("N/A") + + ", gasPrice=" + + (gasPrice != null ? gasPrice.toHumanReadableString() : "N/A") + + ", value=" + + (value != null ? value.toHumanReadableString() : "N/A") + + ", payloadSize=" + + (payload != null ? payload.size() : "null") + + ", accessListSize=" + + accessList.map(List::size) + + ", blobVersionedHashesSize=" + + blobVersionedHashes.map(List::size) + + '}'; + } + public static CallParameter fromTransaction(final Transaction tx) { return new CallParameter( tx.getSender(), - tx.getTo().orElseGet(() -> null), + tx.getTo().orElse(null), tx.getGasLimit(), - Wei.fromQuantity(tx.getGasPrice().orElseGet(() -> Wei.ZERO)), - Optional.of(Wei.fromQuantity(tx.getMaxPriorityFeePerGas().orElseGet(() -> Wei.ZERO))), + tx.getGasPrice().orElse(Wei.ZERO), + tx.getMaxPriorityFeePerGas(), tx.getMaxFeePerGas(), - Wei.fromQuantity(tx.getValue()), + tx.getValue(), tx.getPayload(), tx.getAccessList(), tx.getMaxFeePerBlobGas(), tx.getVersionedHashes()); } + + public static CallParameter fromTransaction(final org.hyperledger.besu.datatypes.Transaction tx) { + return new CallParameter( + tx.getSender(), + tx.getTo().orElse(null), + tx.getGasLimit(), + tx.getGasPrice().map(Wei::fromQuantity).orElse(Wei.ZERO), + tx.getMaxPriorityFeePerGas().map(Wei::fromQuantity), + tx.getMaxFeePerGas().map(Wei::fromQuantity), + Wei.fromQuantity(tx.getValue()), + tx.getPayload(), + tx.getAccessList(), + tx.getMaxFeePerBlobGas().map(Wei::fromQuantity), + tx.getVersionedHashes()); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index 64efb97464..842dc36084 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -106,7 +106,21 @@ public class TransactionSimulator { header); } + public Optional process( + final CallParameter callParams, + final TransactionValidationParams transactionValidationParams, + final OperationTracer operationTracer, + final BlockHeader blockHeader) { + return process( + callParams, + transactionValidationParams, + operationTracer, + (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult, + blockHeader); + } + public Optional processAtHead(final CallParameter callParams) { + final var chainHeadHash = blockchain.getChainHeadHash(); return process( callParams, ImmutableTransactionValidationParams.builder() @@ -115,7 +129,10 @@ public class TransactionSimulator { .build(), OperationTracer.NO_TRACING, (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult, - blockchain.getChainHeadHeader()); + blockchain + .getBlockHeader(chainHeadHash) + .or(() -> blockchain.getBlockHeaderSafe(chainHeadHash)) + .orElse(null)); } /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java index 0627ca14fd..853bc4611a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java @@ -18,22 +18,10 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import java.util.Objects; - -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; -public class TransactionSimulatorResult { - - private final Transaction transaction; - private final TransactionProcessingResult result; - - @VisibleForTesting - public TransactionSimulatorResult( - final Transaction transaction, final TransactionProcessingResult result) { - this.transaction = transaction; - this.result = result; - } +public record TransactionSimulatorResult( + Transaction transaction, TransactionProcessingResult result) { public boolean isSuccessful() { return result.isSuccessful(); @@ -54,40 +42,4 @@ public class TransactionSimulatorResult { public ValidationResult getValidationResult() { return result.getValidationResult(); } - - public TransactionProcessingResult getResult() { - return result; - } - - public Transaction getTransaction() { - return transaction; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final TransactionSimulatorResult that = (TransactionSimulatorResult) o; - return Objects.equals(transaction, that.transaction) && Objects.equals(result, that.result); - } - - @Override - public int hashCode() { - return Objects.hash(transaction, result); - } - - @Override - public String toString() { - return "TransactionSimulatorResult{" - + "transaction=" - + transaction - + ", " - + "result=" - + result - + "}"; - } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java index 622d868a0b..b0e555c9dd 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java @@ -72,12 +72,12 @@ public class MainnetTransactionValidatorTest { private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); - private static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); + protected static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); private static final TransactionValidationParams transactionValidationParams = processingBlockParams; - @Mock private GasCalculator gasCalculator; + @Mock protected GasCalculator gasCalculator; private final Transaction basicTransaction = new TransactionTestFixture() diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java index 0b4c948f55..3fdb20dfd4 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java @@ -32,7 +32,6 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; import java.math.BigInteger; import java.util.Optional; @@ -42,7 +41,6 @@ import com.google.common.base.Suppliers; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -51,7 +49,6 @@ public class PermissionTransactionValidatorTest extends MainnetTransactionValida private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); private static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); - @Mock private GasCalculator gasCalculator; private final Transaction basicTransaction = new TransactionTestFixture() diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java index 5f619ee3f8..a034f049da 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java @@ -71,7 +71,7 @@ public class NodeSmartContractPermissioningController transactionSimulator.processAtHead(callParams); if (result.isPresent()) { - switch (result.get().getResult().getStatus()) { + switch (result.get().result().getStatus()) { case INVALID: throw new IllegalStateException("Permissioning transaction found to be Invalid"); case FAILED: diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java index ca114d1795..ae78a4ab85 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java @@ -114,7 +114,7 @@ public class NodeSmartContractV2PermissioningController } private boolean parseResult(final TransactionSimulatorResult result) { - switch (result.getResult().getStatus()) { + switch (result.result().getStatus()) { case INVALID: throw new IllegalStateException("Invalid node permissioning smart contract call"); case FAILED: diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java index f8ee921668..dd42727af8 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java @@ -134,7 +134,7 @@ public class TransactionSmartContractPermissioningController transactionSimulator.processAtHead(callParams); if (result.isPresent()) { - switch (result.get().getResult().getStatus()) { + switch (result.get().result().getStatus()) { case INVALID: throw new IllegalStateException( "Transaction permissioning transaction found to be Invalid"); diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 8d051eb516..a0129425e2 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -69,7 +69,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'B/pzTaARYvd/T9WtAXtX6vFbxoK5u82GigByKD0YP6M=' + knownHash = 'ytjNiSzw9IR8YHyO4ikmqRTg1GTWkCX9QiQtwq2dRSg=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java index 22f35ea37b..ac398788e4 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java @@ -88,4 +88,11 @@ public interface TransactionProcessingResult { * @return the revert reason. */ Optional getRevertReason(); + + /** + * Return the reason why the transaction is invalid or empty if the transaction is successful + * + * @return the optional invalid reason as a string + */ + Optional getInvalidReason(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java new file mode 100644 index 0000000000..1651534a9f --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java @@ -0,0 +1,54 @@ +/* + * 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.plugin.data; + +import org.hyperledger.besu.datatypes.Transaction; + +/** + * TransactionSimulationResult + * + * @param transaction tx + * @param result res + */ +public record TransactionSimulationResult( + Transaction transaction, TransactionProcessingResult result) { + + /** + * Was the simulation successful? + * + * @return boolean + */ + public boolean isSuccessful() { + return result.isSuccessful(); + } + + /** + * Was the transaction invalid? + * + * @return invalid + */ + public boolean isInvalid() { + return result.isInvalid(); + } + + /** + * Estimated gas used by the transaction + * + * @return estimated gas used + */ + public long getGasEstimate() { + return transaction.getGasLimit() - result.getGasRemaining(); + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java new file mode 100644 index 0000000000..77d5822c93 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java @@ -0,0 +1,42 @@ +/* + * 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.plugin.services; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.TransactionSimulationResult; + +import java.util.Optional; + +/** Transaction simulation service interface */ +@Unstable +public interface TransactionSimulationService extends BesuService { + /** + * Simulate transaction execution at the block identified by the hash + * + * @param transaction tx + * @param blockHash the hash of the block + * @param operationTracer the tracer + * @param isAllowExceedingBalance should ignore the sender balance during the simulation? + * @return the result of the simulation + */ + Optional simulate( + Transaction transaction, + Hash blockHash, + OperationTracer operationTracer, + boolean isAllowExceedingBalance); +} diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java index 69b21b37c8..93519d1c41 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java @@ -242,7 +242,13 @@ public class JsonTestParameters { return generate(getFilteredFiles(paths)); } - private Collection generate(final Collection filteredFiles) { + /** + * Generate collection. + * + * @param filteredFiles the filtered files + * @return the collection + */ + public Collection generate(final Collection filteredFiles) { checkState(generator != null, "Missing generator function"); final Collector collector =