From dbfa5bc9742266a37462ec299e2dffb88c297723 Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Wed, 6 Mar 2024 19:43:13 +0100 Subject: [PATCH] Transaction simulation service Signed-off-by: Fabio Di Fabio --- CHANGELOG.md | 1 + .../tests/acceptance/dsl/node/BesuNode.java | 1 + .../dsl/node/ThreadBesuNodeRunner.java | 58 +++++++++++-- .../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(+), 98 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 7f984840ad2..40a27ade9f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Add blob transaction support to `eth_call` [#6661](https://github.com/hyperledger/besu/pull/6661) - Add blobs to `eth_feeHistory` [#6679](https://github.com/hyperledger/besu/pull/6679) - Refactor and extend `TransactionPoolValidatorService` [#6636](https://github.com/hyperledger/besu/pull/6636) +- Introduce `TransactionSimulationService` [#6686](https://github.com/hyperledger/besu/pull/6686) ### Bug fixes 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 ec9d7b1173e..c822ce899ce 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 NodeRequests nodeRequests() { 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 1c10f82e3bf..6b5140ff4dd 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.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.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,16 +46,19 @@ 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.PicoCLIOptions; import org.hyperledger.besu.plugin.services.RpcEndpointService; 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.RpcEndpointServiceImpl; @@ -61,6 +66,7 @@ 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; @@ -92,18 +98,27 @@ private BesuPluginContextImpl buildPluginContext( 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()) { @@ -120,9 +135,6 @@ private BesuPluginContextImpl buildPluginContext( besuPluginContext.registerPlugins(pluginsPath); commandLine.parseArgs(node.getConfiguration().getExtraCLIOptions().toArray(new String[0])); - - besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); - // register built-in plugins new RocksDBPlugin().register(besuPluginContext); @@ -143,8 +155,14 @@ public void startNode(final BesuNode node) { 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 = @@ -165,7 +183,11 @@ public void startNode(final BesuNode node) { node, storageService, securityModuleService, + transactionSimulationServiceImpl, transactionSelectionServiceImpl, + transactionPoolValidatorServiceImpl, + blockchainServiceImpl, + rpcEndpointServiceImpl, commonPluginConfiguration)); GlobalOpenTelemetry.resetForTest(); @@ -199,6 +221,7 @@ public void startNode(final BesuNode node) { ImmutableTransactionPoolConfiguration.builder() .from(node.getTransactionPoolConfiguration()) .strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled()) + .transactionPoolValidatorService(transactionPoolValidatorServiceImpl) .build(); final int maxPeers = 25; @@ -232,6 +255,10 @@ public void startNode(final BesuNode node) { 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()); @@ -261,7 +288,7 @@ public void startNode(final BesuNode node) { .besuPluginContext(new BesuPluginContextImpl()) .autoLogBloomCaching(false) .storageProvider(storageProvider) - .rpcEndpointService(new RpcEndpointServiceImpl()); + .rpcEndpointService(rpcEndpointServiceImpl); node.engineRpcConfiguration().ifPresent(runnerBuilder::engineJsonRpcConfiguration); final Runner runner = runnerBuilder.build(); @@ -285,6 +312,25 @@ public void startNode(final BesuNode node) { 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 65d257fbfb7..ff6dc2ac9b1 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.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 BesuNode createCliqueNode(final String name) throws IOException { 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 BesuNode createCliqueNodeWithValidators(final String name, final String.. 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 f2682993f8a..219d15d1adf 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 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 Optional createGenesisConfigForValidators( 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 4741e397ddc..1151100065b 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 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 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 void shutdown() { netEth.shutdown(); websocketService.ifPresent(WebSocketService::close); } + + public Web3jService getWeb3jService() { + return web3jService; + } } diff --git a/besu/build.gradle b/besu/build.gradle index 0b31c4a43c2..85c17e7ff35 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 679bfc88dd7..4a1e17c86e8 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.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.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.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 BesuCommand( new RpcEndpointServiceImpl(), new TransactionSelectionServiceImpl(), new TransactionPoolValidatorServiceImpl(), + new TransactionSimulationServiceImpl(), new BlockchainServiceImpl()); } @@ -978,6 +983,7 @@ public BesuCommand( * @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 @@ protected BesuCommand( 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 @@ protected BesuCommand( this.rpcEndpointServiceImpl = rpcEndpointServiceImpl; this.transactionSelectionServiceImpl = transactionSelectionServiceImpl; this.transactionValidatorServiceImpl = transactionValidatorServiceImpl; + this.transactionSimulationServiceImpl = transactionSimulationServiceImpl; this.blockchainServiceImpl = blockchainServiceImpl; } @@ -1210,6 +1218,8 @@ private void preparePlugins() { 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 @@ private Runner buildRunner() { 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 00000000000..5ebf48f0cef --- /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 d624064e5c1..55cdee554bd 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 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi aheadDbNodeKey, createKeyValueStorageProvider( dataDirAhead, dbAhead, dataStorageConfiguration, miningParameters), - noOpMetricsSystem); + noOpMetricsSystem, + miningParameters); setupState( blockCount, controllerAhead.getProtocolSchedule(), controllerAhead.getProtocolContext()); @@ -235,7 +236,8 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi dataDirBehind, behindDbNodeKey, new InMemoryKeyValueStorageProvider(), - noOpMetricsSystem); + noOpMetricsSystem, + miningParameters); final EnodeURL aheadEnode = runnerAhead.getLocalEnode().get(); final EthNetworkConfig behindEthNetworkConfiguration = @@ -452,14 +454,15 @@ private BesuController getController( 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 4b59491dc18..dff42ef654a 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 @@ protected BesuController createController() throws IOException { 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 d784733608f..63f664d7469 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.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 static class TestBesuCommand extends BesuCommand { 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 a7b705e4a9d..e6786a92ef1 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 @@ private List decodeResult( 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 b3de5094c8f..0f9b1b7be14 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 @@ protected long processEstimateGas( 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 @@ protected JsonRpcErrorResponse errorResponse( 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 ae5215282f8..72aa575b0cb 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 @@ protected PreCloseStateHandler getSimulatorResultHandler( 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 569563ca932..698685f7457 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 @@ private JsonRpcErrorResponse errorResponse( 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 9a382d6441b..c0631bf44cb 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 JsonRpcResponse response(final JsonRpcRequestContext requestContext) { 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 439140fa315..06559af1efa 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 @@ protected PreCloseStateHandler getSimulatorResultHandler( 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 a824b35f1eb..611ebee8b04 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 @@ private JsonNode getSingleCallResult( 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 26da6f2f13d..ad34d92b490 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 JsonRpcResponse response(final JsonRpcRequestContext requestContext) { 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 8aba16bfb9c..261f8de505d 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 void shouldReturnBasicExecutionRevertErrorWithoutReason() { 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 void shouldReturnExecutionRevertErrorWithABIParseError() { 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 void shouldReturnExecutionRevertErrorWithParsedABI() { 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 0d129d26652..d23c10f23b4 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 @@ private void mockTransactionSimulatorResult( 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 307424ea9a1..64d015d89a0 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 @@ private TransactionSimulatorResult getMockTransactionSimulatorResult( 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 ebca2bb17aa..4a0dd20f62a 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 @@ private Boolean isCancelled() { 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 c053e1107b9..e77062a7ddd 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 ValidationResult getValidationResult() { 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 e5d37e399ef..d74f17baef8 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 int hashCode() { 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 64efb974647..842dc360848 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 Optional process( 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 Optional processAtHead(final CallParameter ca .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 0627ca14fd2..853bc4611a3 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.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 Bytes getOutput() { 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 622d868a0ba..b0e555c9dd0 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 0b4c948f55e..3fdb20dfd4f 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.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 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 5f619ee3f80..a034f049da0 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 @@ boolean checkSmartContractRules(final EnodeURL sourceEnode, final EnodeURL desti 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 ca114d17955..ae78a4ab85a 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 @@ private Bytes createPayload(final EnodeURL enodeUrl) { } 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 f8ee921668f..dd42727af85 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 boolean isPermitted(final Transaction transaction) { 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 8d051eb516d..a0129425e21 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 22f35ea37bc..ac398788e40 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 00000000000..1651534a9fa --- /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 00000000000..77d5822c93c --- /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 69b21b37c8b..93519d1c41d 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 Collection generate(final String... paths) { 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 =