diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aebc644c68..64550386061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Fast Sync ### Additions and Improvements +- Add ability to pass a custom tracer to block simulation [#9708](https://github.com/hyperledger/besu/pull/9708) ### Bug fixes diff --git a/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java b/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java index 2cf528fcdaa..904ce4e8f59 100644 --- a/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java +++ b/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.trie.pathbased.common.provider.WorldStateQueryParams; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.plugin.Unstable; import org.hyperledger.besu.plugin.data.BlockOverrides; import org.hyperledger.besu.plugin.data.PluginBlockSimulationResult; @@ -102,7 +103,35 @@ public PluginBlockSimulationResult simulate( final List transactions, final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides) { - return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, false); + return processSimulation( + blockNumber, + transactions, + blockOverrides, + stateOverrides, + false, + OperationTracer.NO_TRACING); + } + + /** + * Simulate the processing of a block given a header, a list of transactions, blockOverrides, and + * a tracer. + * + * @param blockNumber the block number + * @param transactions the transactions to include in the block + * @param blockOverrides the blockSimulationOverride of the block + * @param stateOverrides state overrides of the block + * @param tracer the operation tracer to use during simulation + * @return the block context + */ + @Override + public PluginBlockSimulationResult simulate( + final long blockNumber, + final List transactions, + final BlockOverrides blockOverrides, + final StateOverrideMap stateOverrides, + final OperationTracer tracer) { + return processSimulation( + blockNumber, transactions, blockOverrides, stateOverrides, false, tracer); } /** @@ -122,7 +151,13 @@ public PluginBlockSimulationResult simulateAndPersistWorldState( final List transactions, final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides) { - return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, true); + return processSimulation( + blockNumber, + transactions, + blockOverrides, + stateOverrides, + true, + OperationTracer.NO_TRACING); } private PluginBlockSimulationResult processSimulation( @@ -130,7 +165,8 @@ private PluginBlockSimulationResult processSimulation( final List transactions, final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides, - final boolean persistWorldState) { + final boolean persistWorldState, + final OperationTracer tracer) { BlockHeader header = getBlockHeader(blockNumber); List callParameters = transactions.stream().map(CallParameter::fromTransaction).toList(); @@ -145,7 +181,7 @@ private PluginBlockSimulationResult processSimulation( .build(); List results = - blockSimulator.process(header, blockSimulationParameter, ws); + blockSimulator.process(header, blockSimulationParameter, ws, tracer); BlockSimulationResult result = results.getFirst(); if (persistWorldState) { ws.persist(result.getBlock().getHeader()); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java index f6b36297950..79ba536718c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulator.java @@ -126,8 +126,23 @@ public BlockSimulator( */ public List process( final BlockHeader header, final BlockSimulationParameter blockSimulationParameter) { + return process(header, blockSimulationParameter, OperationTracer.NO_TRACING); + } + + /** + * Processes a list of BlockStateCalls sequentially, collecting the results. + * + * @param header The block header for all simulations. + * @param blockSimulationParameter The BlockSimulationParameter containing the block state calls. + * @param operationTracer The operation tracer to use during simulation. + * @return A list of BlockSimulationResult objects from processing each BlockStateCall. + */ + public List process( + final BlockHeader header, + final BlockSimulationParameter blockSimulationParameter, + final OperationTracer operationTracer) { try (final MutableWorldState ws = getWorldState(header)) { - return process(header, blockSimulationParameter, ws); + return process(header, blockSimulationParameter, ws, operationTracer); } catch (IllegalArgumentException | BlockStateCallException e) { throw e; } catch (final Exception e) { @@ -147,6 +162,23 @@ public List process( final BlockHeader blockHeader, final BlockSimulationParameter simulationParameter, final MutableWorldState worldState) { + return process(blockHeader, simulationParameter, worldState, OperationTracer.NO_TRACING); + } + + /** + * Processes a list of BlockStateCalls sequentially, collecting the results. + * + * @param blockHeader The block header for all simulations. + * @param simulationParameter The BlockSimulationParameter containing the block state calls. + * @param worldState The initial MutableWorldState to start with. + * @param operationTracer The operation tracer to use during simulation. + * @return A list of BlockSimulationResult objects from processing each BlockStateCall. + */ + public List process( + final BlockHeader blockHeader, + final BlockSimulationParameter simulationParameter, + final MutableWorldState worldState, + final OperationTracer operationTracer) { int countStateCalls = simulationParameter.getBlockStateCalls().size(); List results = new ArrayList<>(countStateCalls); @@ -171,7 +203,8 @@ public List process( simulationParameter.isReturnTrieLog(), simulationParameter::getFakeSignature, blockHashCache, - simulationCumulativeGasUsed); + simulationCumulativeGasUsed, + operationTracer); results.add(result); BlockHeader resultBlockHeader = result.getBlock().getHeader(); blockHashCache.put(resultBlockHeader.getNumber(), resultBlockHeader.getHash()); @@ -187,6 +220,7 @@ public List process( * @param baseBlockHeader The block header for the simulation. * @param blockStateCall The BlockStateCall to process. * @param ws The MutableWorldState to use for the simulation. + * @param operationTracer The operation tracer to use * @return A BlockSimulationResult from processing the BlockStateCall. */ private BlockSimulationResult processBlockStateCall( @@ -198,7 +232,8 @@ private BlockSimulationResult processBlockStateCall( final boolean returnTrieLog, final Supplier signatureSupplier, final Map blockHashCache, - final long simulationCumulativeGasUsed) { + final long simulationCumulativeGasUsed, + final OperationTracer operationTracer) { BlockOverrides blockOverrides = blockStateCall.getBlockOverrides(); ProtocolSpec protocolSpec = @@ -257,7 +292,8 @@ private BlockSimulationResult processBlockStateCall( blockHashLookup, signatureSupplier, simulationCumulativeGasUsed, - blockAccessListBuilder); + blockAccessListBuilder, + operationTracer); Optional postExecutionAccessLocationTracker = blockAccessListBuilder.map( @@ -305,7 +341,8 @@ protected BlockStateCallSimulationResult processTransactions( final BlockHashLookup blockHashLookup, final Supplier signatureSupplier, final long simulationCumulativeGasUsed, - final Optional blockAccessListBuilder) { + final Optional blockAccessListBuilder, + final OperationTracer operationTracer) { TransactionValidationParams transactionValidationParams = shouldValidate ? STRICT_VALIDATION_PARAMS : SIMULATION_PARAMS; @@ -327,8 +364,18 @@ protected BlockStateCallSimulationResult processTransactions( transactionLocation++) { final WorldUpdater transactionUpdater = blockUpdater.updater(); final CallParameter callParameter = blockStateCall.getCalls().get(transactionLocation); - OperationTracer operationTracer = - isTraceTransfers ? new EthTransferLogOperationTracer() : OperationTracer.NO_TRACING; + + // Custom tracer and EthTraceTransfers are mutually exclusive + OperationTracer finalOperationTracer = operationTracer; + if (isTraceTransfers) { + if (finalOperationTracer == OperationTracer.NO_TRACING) { + finalOperationTracer = new EthTransferLogOperationTracer(); + } else { + // this shouldn't happen, and isTraceTransfers will go away with Glamsterdam + throw new IllegalArgumentException( + "Custom tracer and EthTraceTransfers are mutually exclusive"); + } + } long gasLimit = transactionSimulator.calculateSimulationGasCap( @@ -347,7 +394,7 @@ protected BlockStateCallSimulationResult processTransactions( callParameter, Optional.empty(), // We have already applied state overrides on block level transactionValidationParams, - operationTracer, + finalOperationTracer, blockHeader, transactionUpdater, miningBeneficiaryCalculator.calculateBeneficiary(blockHeader), @@ -379,7 +426,7 @@ protected BlockStateCallSimulationResult processTransactions( transactionUpdater.commit(); blockUpdater.commit(); - blockStateCallSimulationResult.add(transactionSimulationResult, ws, operationTracer); + blockStateCallSimulationResult.add(transactionSimulationResult, ws, finalOperationTracer); } blockAccessListBuilder.ifPresent(b -> blockStateCallSimulationResult.set(b.build())); diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 7deb69442ff..919154dda92 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -71,7 +71,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 = 'jZO39Zata8CAfqjFrodIVAVXHUb+u0QzOK9eGIpvY84=' + knownHash = 'VUXqv8VKnFsYb7fqL9svGN7+tsxIUFdmt9G7JAIDpeI=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockSimulationService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockSimulationService.java index 20e438d361e..3b6e28c51ef 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockSimulationService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockSimulationService.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.StateOverrideMap; 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.BlockOverrides; import org.hyperledger.besu.plugin.data.PluginBlockSimulationResult; @@ -40,6 +41,24 @@ PluginBlockSimulationResult simulate( BlockOverrides blockOverrides, StateOverrideMap stateOverrides); + /** + * Simulate the processing of a block given a header, a list of transactions, blockOverrides, and + * a tracer. + * + * @param blockNumber the block number + * @param transactions the transactions to include in the block + * @param blockOverrides the blockSimulationOverride of the block + * @param stateOverrides state overrides of the block + * @param tracer the operation tracer to use during simulation + * @return the block context + */ + PluginBlockSimulationResult simulate( + long blockNumber, + List transactions, + BlockOverrides blockOverrides, + StateOverrideMap stateOverrides, + OperationTracer tracer); + /** * This method is experimental and should be used with caution. Simulate the processing of a block * given a header, a list of transactions, and blockOverrides and persist the WorldState