From 6a80a94f403495e494a529cf55f08ae9e41fab5b Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 11:07:58 +1000 Subject: [PATCH 01/10] initial groundwork Signed-off-by: Sally MacFarlane --- .../services/BlockSimulatorServiceImpl.java | 56 ++++++++++++++++++- .../ethereum/transaction/BlockSimulator.java | 2 + .../services/BlockSimulationService.java | 37 ++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) 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..473161c6ee2 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,27 @@ 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,15 +143,44 @@ 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); + } + + /** + * This method is experimental and should be used with caution. Simulate the processing of a block + * given a header, a list of transactions, blockOverrides, a tracer and persist the WorldState + * + * @param blockNumber the block number + * @param transactions the transactions to include in the block + * @param blockOverrides block overrides for the block + * @param stateOverrides state overrides of the block + * @param tracer the operation tracer to use during simulation + * @return the PluginBlockSimulationResult + */ + @Unstable + @Override + public PluginBlockSimulationResult simulateAndPersistWorldState( + final long blockNumber, + final List transactions, + final BlockOverrides blockOverrides, + final StateOverrideMap stateOverrides, + final OperationTracer tracer) { + return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, true, tracer); } + @SuppressWarnings("UnusedVariable") private PluginBlockSimulationResult processSimulation( final long blockNumber, final List transactions, final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides, - final boolean persistWorldState) { + final boolean persistWorldState, + final OperationTracer tracer) { + // TODO: Custom tracer support needs to be implemented in BlockSimulator + // Currently BlockSimulator hardcodes tracer selection based on traceTransfers flag + // See BlockSimulator.java line ~332 for existing tracer logic + // The tracer parameter is accepted for API compatibility but not yet used + BlockHeader header = getBlockHeader(blockNumber); List callParameters = transactions.stream().map(CallParameter::fromTransaction).toList(); 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..d3308f7c9ef 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 @@ -327,6 +327,8 @@ protected BlockStateCallSimulationResult processTransactions( transactionLocation++) { final WorldUpdater transactionUpdater = blockUpdater.updater(); final CallParameter callParameter = blockStateCall.getCalls().get(transactionLocation); + // TODO can't use this at same time as passing in a custom tracer + // TODO see TraceAggregator - collection of tracers OperationTracer operationTracer = isTraceTransfers ? new EthTransferLogOperationTracer() : OperationTracer.NO_TRACING; 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..9537d000be4 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,23 @@ 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 @@ -56,4 +74,23 @@ PluginBlockSimulationResult simulateAndPersistWorldState( List transactions, BlockOverrides blockOverrides, StateOverrideMap stateOverrides); + + /** + * This method is experimental and should be used with caution. Simulate the processing of a block + * given a header, a list of transactions, blockOverrides, a tracer and persist the WorldState + * + * @param blockNumber the block number + * @param transactions the transactions to include in the block + * @param blockOverrides block overrides for the block + * @param stateOverrides state overrides of the block + * @param tracer the operation tracer to use during simulation + * @return the PluginBlockSimulationResult + */ + @Unstable + PluginBlockSimulationResult simulateAndPersistWorldState( + long blockNumber, + List transactions, + BlockOverrides blockOverrides, + StateOverrideMap stateOverrides, + OperationTracer tracer); } From b5cd7790b47e7e16703df439b50e157a34eedc33 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 11:15:07 +1000 Subject: [PATCH 02/10] block simulator Signed-off-by: Sally MacFarlane --- .../services/BlockSimulatorServiceImpl.java | 7 +--- .../transaction/BlockSimulationParameter.java | 35 ++++++++++++++++++- .../ethereum/transaction/BlockSimulator.java | 28 ++++++++++----- 3 files changed, 55 insertions(+), 15 deletions(-) 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 473161c6ee2..6708fa19be7 100644 --- a/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java +++ b/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java @@ -168,7 +168,6 @@ public PluginBlockSimulationResult simulateAndPersistWorldState( return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, true, tracer); } - @SuppressWarnings("UnusedVariable") private PluginBlockSimulationResult processSimulation( final long blockNumber, final List transactions, @@ -176,11 +175,6 @@ private PluginBlockSimulationResult processSimulation( final StateOverrideMap stateOverrides, final boolean persistWorldState, final OperationTracer tracer) { - // TODO: Custom tracer support needs to be implemented in BlockSimulator - // Currently BlockSimulator hardcodes tracer selection based on traceTransfers flag - // See BlockSimulator.java line ~332 for existing tracer logic - // The tracer parameter is accepted for API compatibility but not yet used - BlockHeader header = getBlockHeader(blockNumber); List callParameters = transactions.stream().map(CallParameter::fromTransaction).toList(); @@ -192,6 +186,7 @@ private PluginBlockSimulationResult processSimulation( .blockStateCalls(List.of(blockStateCall)) .validation(true) .fakeSignature(FAKE_SIGNATURE) + .operationTracer(tracer) .build(); List results = diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java index 9ae8274d2e9..258b2640eb6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.StateOverride; import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallError; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.math.BigInteger; import java.util.HashMap; @@ -47,6 +48,7 @@ public class BlockSimulationParameter { private final boolean returnFullTransactions; private final boolean returnTrieLog; private final SECPSignature fakeSignature; + private final OperationTracer operationTracer; public BlockSimulationParameter( final List blockStateCalls, @@ -78,13 +80,33 @@ public BlockSimulationParameter( final boolean returnFullTransactions, final boolean returnTrieLog, final SECPSignature fakeSignature) { + this( + blockStateCalls, + validation, + traceTransfers, + returnFullTransactions, + returnTrieLog, + fakeSignature, + OperationTracer.NO_TRACING); + } + + public BlockSimulationParameter( + final List blockStateCalls, + final boolean validation, + final boolean traceTransfers, + final boolean returnFullTransactions, + final boolean returnTrieLog, + final SECPSignature fakeSignature, + final OperationTracer operationTracer) { checkNotNull(blockStateCalls); + checkNotNull(operationTracer); this.blockStateCalls = blockStateCalls; this.validation = validation; this.traceTransfers = traceTransfers; this.returnFullTransactions = returnFullTransactions; this.returnTrieLog = returnTrieLog; this.fakeSignature = fakeSignature; + this.operationTracer = operationTracer; } public List getBlockStateCalls() { @@ -111,6 +133,10 @@ public SECPSignature getFakeSignature() { return fakeSignature; } + public OperationTracer getOperationTracer() { + return operationTracer; + } + public Optional validate(final Set
validPrecompileAddresses) { if (blockStateCalls.size() > MAX_BLOCK_CALL_SIZE) { return Optional.of(TOO_MANY_BLOCK_CALLS); @@ -216,6 +242,7 @@ public static class BlockSimulationParameterBuilder { private boolean returnTrieLog = false; private SECPSignature fakeSignature = new SECPSignature(BigInteger.ZERO, BigInteger.ZERO, (byte) 0); + private OperationTracer operationTracer = OperationTracer.NO_TRACING; public BlockSimulationParameterBuilder blockStateCalls( final List blockStateCalls) { @@ -249,6 +276,11 @@ public BlockSimulationParameterBuilder fakeSignature(final SECPSignature fakeSig return this; } + public BlockSimulationParameterBuilder operationTracer(final OperationTracer operationTracer) { + this.operationTracer = operationTracer; + return this; + } + public BlockSimulationParameter build() { return new BlockSimulationParameter( blockStateCalls, @@ -256,7 +288,8 @@ public BlockSimulationParameter build() { traceTransfers, returnFullTransactions, returnTrieLog, - fakeSignature); + fakeSignature, + operationTracer); } } } 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 d3308f7c9ef..7b8f02a5ce5 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 @@ -171,7 +171,8 @@ public List process( simulationParameter.isReturnTrieLog(), simulationParameter::getFakeSignature, blockHashCache, - simulationCumulativeGasUsed); + simulationCumulativeGasUsed, + simulationParameter.getOperationTracer()); results.add(result); BlockHeader resultBlockHeader = result.getBlock().getHeader(); blockHashCache.put(resultBlockHeader.getNumber(), resultBlockHeader.getHash()); @@ -187,6 +188,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 customOperationTracer The custom operation tracer to use * @return A BlockSimulationResult from processing the BlockStateCall. */ private BlockSimulationResult processBlockStateCall( @@ -198,7 +200,8 @@ private BlockSimulationResult processBlockStateCall( final boolean returnTrieLog, final Supplier signatureSupplier, final Map blockHashCache, - final long simulationCumulativeGasUsed) { + final long simulationCumulativeGasUsed, + final OperationTracer customOperationTracer) { BlockOverrides blockOverrides = blockStateCall.getBlockOverrides(); ProtocolSpec protocolSpec = @@ -257,7 +260,8 @@ private BlockSimulationResult processBlockStateCall( blockHashLookup, signatureSupplier, simulationCumulativeGasUsed, - blockAccessListBuilder); + blockAccessListBuilder, + customOperationTracer); Optional postExecutionAccessLocationTracker = blockAccessListBuilder.map( @@ -305,7 +309,8 @@ protected BlockStateCallSimulationResult processTransactions( final BlockHashLookup blockHashLookup, final Supplier signatureSupplier, final long simulationCumulativeGasUsed, - final Optional blockAccessListBuilder) { + final Optional blockAccessListBuilder, + final OperationTracer customOperationTracer) { TransactionValidationParams transactionValidationParams = shouldValidate ? STRICT_VALIDATION_PARAMS : SIMULATION_PARAMS; @@ -327,10 +332,17 @@ protected BlockStateCallSimulationResult processTransactions( transactionLocation++) { final WorldUpdater transactionUpdater = blockUpdater.updater(); final CallParameter callParameter = blockStateCall.getCalls().get(transactionLocation); - // TODO can't use this at same time as passing in a custom tracer - // TODO see TraceAggregator - collection of tracers - OperationTracer operationTracer = - isTraceTransfers ? new EthTransferLogOperationTracer() : OperationTracer.NO_TRACING; + + // Use custom tracer if provided, otherwise fall back to transfer tracing logic + OperationTracer operationTracer = customOperationTracer; + if (isTraceTransfers) { + if (operationTracer == OperationTracer.NO_TRACING) { + operationTracer = new EthTransferLogOperationTracer(); + } else { + // this shouldn't happen and isTraceTransfers will go away with Glamsterdam + throw new IllegalArgumentException("can't use a custom tracer with Eth Trace Transfers"); + } + } long gasLimit = transactionSimulator.calculateSimulationGasCap( From 3c253b73f2e4aea77c76e8dcb44b8f733cc41e52 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 11:30:14 +1000 Subject: [PATCH 03/10] formatting Signed-off-by: Sally MacFarlane --- .../services/BlockSimulatorServiceImpl.java | 25 +++++++++++++++---- .../services/BlockSimulationService.java | 3 ++- 2 files changed, 22 insertions(+), 6 deletions(-) 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 6708fa19be7..4702d786152 100644 --- a/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java +++ b/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java @@ -103,11 +103,18 @@ public PluginBlockSimulationResult simulate( final List transactions, final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides) { - return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, false, OperationTracer.NO_TRACING); + 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. + * 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 @@ -123,7 +130,8 @@ public PluginBlockSimulationResult simulate( final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides, final OperationTracer tracer) { - return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, false, tracer); + return processSimulation( + blockNumber, transactions, blockOverrides, stateOverrides, false, tracer); } /** @@ -143,7 +151,13 @@ public PluginBlockSimulationResult simulateAndPersistWorldState( final List transactions, final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides) { - return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, true, OperationTracer.NO_TRACING); + return processSimulation( + blockNumber, + transactions, + blockOverrides, + stateOverrides, + true, + OperationTracer.NO_TRACING); } /** @@ -165,7 +179,8 @@ public PluginBlockSimulationResult simulateAndPersistWorldState( final BlockOverrides blockOverrides, final StateOverrideMap stateOverrides, final OperationTracer tracer) { - return processSimulation(blockNumber, transactions, blockOverrides, stateOverrides, true, tracer); + return processSimulation( + blockNumber, transactions, blockOverrides, stateOverrides, true, tracer); } private PluginBlockSimulationResult processSimulation( 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 9537d000be4..05db9b3ed0a 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 @@ -42,7 +42,8 @@ PluginBlockSimulationResult simulate( StateOverrideMap stateOverrides); /** - * Simulate the processing of a block given a header, a list of transactions, blockOverrides, and a tracer. + * 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 From d48d0679a9d866217f49a6af732e7c95753e5f34 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 11:53:23 +1000 Subject: [PATCH 04/10] comments Signed-off-by: Sally MacFarlane --- .../besu/ethereum/transaction/BlockSimulator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7b8f02a5ce5..ed1b878b8a2 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 @@ -333,14 +333,14 @@ protected BlockStateCallSimulationResult processTransactions( final WorldUpdater transactionUpdater = blockUpdater.updater(); final CallParameter callParameter = blockStateCall.getCalls().get(transactionLocation); - // Use custom tracer if provided, otherwise fall back to transfer tracing logic + // Custom tracer and EthTraceTransfers are mutually exclusive OperationTracer operationTracer = customOperationTracer; if (isTraceTransfers) { if (operationTracer == OperationTracer.NO_TRACING) { operationTracer = new EthTransferLogOperationTracer(); } else { - // this shouldn't happen and isTraceTransfers will go away with Glamsterdam - throw new IllegalArgumentException("can't use a custom tracer with Eth Trace Transfers"); + // this shouldn't happen, and isTraceTransfers will go away with Glamsterdam + throw new IllegalArgumentException("Custom tracer and EthTraceTransfers are mutually exclusive"); } } From a581d55dee264a78c42c60bd0129322505b5a8a4 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 12:01:43 +1000 Subject: [PATCH 05/10] changelog Signed-off-by: Sally MacFarlane --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b836ff41ec6..6267fdb63fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Add `engine_getBlobsV3` method [#9582](https://github.com/hyperledger/besu/pull/9582) - Verify plugins on start [#9601](https://github.com/hyperledger/besu/pull/9601) - Add EIP-7778 to Amsterdam [#9664](https://github.com/hyperledger/besu/pull/9664) +- Add ability to pass a custom tracer to block simulation []() ### Bug fixes - Fix promotion to prioritized layer for gas price fee markets [#9635](https://github.com/hyperledger/besu/pull/9635) From dedeafe9ca45392d62b96a749ae29a11cc64da85 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 12:51:23 +1000 Subject: [PATCH 06/10] plugin hash Signed-off-by: Sally MacFarlane --- .../hyperledger/besu/ethereum/transaction/BlockSimulator.java | 3 ++- plugin-api/build.gradle | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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 ed1b878b8a2..15bba137c91 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 @@ -340,7 +340,8 @@ protected BlockStateCallSimulationResult processTransactions( operationTracer = new EthTransferLogOperationTracer(); } else { // this shouldn't happen, and isTraceTransfers will go away with Glamsterdam - throw new IllegalArgumentException("Custom tracer and EthTraceTransfers are mutually exclusive"); + throw new IllegalArgumentException( + "Custom tracer and EthTraceTransfers are mutually exclusive"); } } diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index a114d84714a..3893e30d4a7 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 = '84BmqlIikN5TDhxacuy8DqaYsmeyJUScJvOoGCR0GeA=' + knownHash = '0jFqgyNB52sTZlOfJ8abMk4EYBPCbmY5bdtmmD4HrPI=' } check.dependsOn('checkAPIChanges') From c9250511d4e2a9e815d2a8b52e1c9c3ec4c10df6 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Thu, 29 Jan 2026 13:36:23 +1000 Subject: [PATCH 07/10] changelog to unreleased Signed-off-by: Sally MacFarlane --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e87b1dac35..d249788dc60 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 @@ -49,7 +50,6 @@ - Add `engine_getBlobsV3` method [#9582](https://github.com/hyperledger/besu/pull/9582) - Verify plugins on start [#9601](https://github.com/hyperledger/besu/pull/9601) - Add EIP-7778 to Amsterdam [#9664](https://github.com/hyperledger/besu/pull/9664) -- Add ability to pass a custom tracer to block simulation []() ### Bug fixes - Fix promotion to prioritized layer for gas price fee markets [#9635](https://github.com/hyperledger/besu/pull/9635) From 9a1da6663f0fabc38de769894f03d9bab7854cf0 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Fri, 30 Jan 2026 08:02:45 +1000 Subject: [PATCH 08/10] update plugin api Signed-off-by: Sally MacFarlane --- plugin-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 7deb69442ff..ba66bc51130 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 = '5oL9XdQtad+Afrz6LEVZdNUhwSRayyp9Ag8+BF/Oj2s=' } check.dependsOn('checkAPIChanges') From aca7d0cbe86de77c5d507eb59e027f3c98f92084 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Fri, 30 Jan 2026 08:17:40 +1000 Subject: [PATCH 09/10] don't need changes to simulateAndPersistWorldState Signed-off-by: Sally MacFarlane --- .../services/BlockSimulatorServiceImpl.java | 23 ------------------- .../services/BlockSimulationService.java | 19 --------------- 2 files changed, 42 deletions(-) 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 4702d786152..5b1045c67c3 100644 --- a/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java +++ b/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java @@ -160,29 +160,6 @@ public PluginBlockSimulationResult simulateAndPersistWorldState( OperationTracer.NO_TRACING); } - /** - * This method is experimental and should be used with caution. Simulate the processing of a block - * given a header, a list of transactions, blockOverrides, a tracer and persist the WorldState - * - * @param blockNumber the block number - * @param transactions the transactions to include in the block - * @param blockOverrides block overrides for the block - * @param stateOverrides state overrides of the block - * @param tracer the operation tracer to use during simulation - * @return the PluginBlockSimulationResult - */ - @Unstable - @Override - public PluginBlockSimulationResult simulateAndPersistWorldState( - final long blockNumber, - final List transactions, - final BlockOverrides blockOverrides, - final StateOverrideMap stateOverrides, - final OperationTracer tracer) { - return processSimulation( - blockNumber, transactions, blockOverrides, stateOverrides, true, tracer); - } - private PluginBlockSimulationResult processSimulation( final long blockNumber, final List transactions, 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 05db9b3ed0a..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 @@ -75,23 +75,4 @@ PluginBlockSimulationResult simulateAndPersistWorldState( List transactions, BlockOverrides blockOverrides, StateOverrideMap stateOverrides); - - /** - * This method is experimental and should be used with caution. Simulate the processing of a block - * given a header, a list of transactions, blockOverrides, a tracer and persist the WorldState - * - * @param blockNumber the block number - * @param transactions the transactions to include in the block - * @param blockOverrides block overrides for the block - * @param stateOverrides state overrides of the block - * @param tracer the operation tracer to use during simulation - * @return the PluginBlockSimulationResult - */ - @Unstable - PluginBlockSimulationResult simulateAndPersistWorldState( - long blockNumber, - List transactions, - BlockOverrides blockOverrides, - StateOverrideMap stateOverrides, - OperationTracer tracer); } From 140976eb1da9dd1b0ae02961f735fb6064b113b6 Mon Sep 17 00:00:00 2001 From: Sally MacFarlane Date: Fri, 30 Jan 2026 08:29:00 +1000 Subject: [PATCH 10/10] remove tracer from param Signed-off-by: Sally MacFarlane --- .../services/BlockSimulatorServiceImpl.java | 3 +- .../transaction/BlockSimulationParameter.java | 35 +----------- .../ethereum/transaction/BlockSimulator.java | 54 +++++++++++++++---- plugin-api/build.gradle | 2 +- 4 files changed, 46 insertions(+), 48 deletions(-) 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 5b1045c67c3..904ce4e8f59 100644 --- a/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java +++ b/app/src/main/java/org/hyperledger/besu/services/BlockSimulatorServiceImpl.java @@ -178,11 +178,10 @@ private PluginBlockSimulationResult processSimulation( .blockStateCalls(List.of(blockStateCall)) .validation(true) .fakeSignature(FAKE_SIGNATURE) - .operationTracer(tracer) .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/BlockSimulationParameter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java index 258b2640eb6..9ae8274d2e9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/BlockSimulationParameter.java @@ -26,7 +26,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.StateOverride; import org.hyperledger.besu.ethereum.transaction.exceptions.BlockStateCallError; -import org.hyperledger.besu.evm.tracing.OperationTracer; import java.math.BigInteger; import java.util.HashMap; @@ -48,7 +47,6 @@ public class BlockSimulationParameter { private final boolean returnFullTransactions; private final boolean returnTrieLog; private final SECPSignature fakeSignature; - private final OperationTracer operationTracer; public BlockSimulationParameter( final List blockStateCalls, @@ -80,33 +78,13 @@ public BlockSimulationParameter( final boolean returnFullTransactions, final boolean returnTrieLog, final SECPSignature fakeSignature) { - this( - blockStateCalls, - validation, - traceTransfers, - returnFullTransactions, - returnTrieLog, - fakeSignature, - OperationTracer.NO_TRACING); - } - - public BlockSimulationParameter( - final List blockStateCalls, - final boolean validation, - final boolean traceTransfers, - final boolean returnFullTransactions, - final boolean returnTrieLog, - final SECPSignature fakeSignature, - final OperationTracer operationTracer) { checkNotNull(blockStateCalls); - checkNotNull(operationTracer); this.blockStateCalls = blockStateCalls; this.validation = validation; this.traceTransfers = traceTransfers; this.returnFullTransactions = returnFullTransactions; this.returnTrieLog = returnTrieLog; this.fakeSignature = fakeSignature; - this.operationTracer = operationTracer; } public List getBlockStateCalls() { @@ -133,10 +111,6 @@ public SECPSignature getFakeSignature() { return fakeSignature; } - public OperationTracer getOperationTracer() { - return operationTracer; - } - public Optional validate(final Set
validPrecompileAddresses) { if (blockStateCalls.size() > MAX_BLOCK_CALL_SIZE) { return Optional.of(TOO_MANY_BLOCK_CALLS); @@ -242,7 +216,6 @@ public static class BlockSimulationParameterBuilder { private boolean returnTrieLog = false; private SECPSignature fakeSignature = new SECPSignature(BigInteger.ZERO, BigInteger.ZERO, (byte) 0); - private OperationTracer operationTracer = OperationTracer.NO_TRACING; public BlockSimulationParameterBuilder blockStateCalls( final List blockStateCalls) { @@ -276,11 +249,6 @@ public BlockSimulationParameterBuilder fakeSignature(final SECPSignature fakeSig return this; } - public BlockSimulationParameterBuilder operationTracer(final OperationTracer operationTracer) { - this.operationTracer = operationTracer; - return this; - } - public BlockSimulationParameter build() { return new BlockSimulationParameter( blockStateCalls, @@ -288,8 +256,7 @@ public BlockSimulationParameter build() { traceTransfers, returnFullTransactions, returnTrieLog, - fakeSignature, - operationTracer); + fakeSignature); } } } 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 15bba137c91..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); @@ -172,7 +204,7 @@ public List process( simulationParameter::getFakeSignature, blockHashCache, simulationCumulativeGasUsed, - simulationParameter.getOperationTracer()); + operationTracer); results.add(result); BlockHeader resultBlockHeader = result.getBlock().getHeader(); blockHashCache.put(resultBlockHeader.getNumber(), resultBlockHeader.getHash()); @@ -188,7 +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 customOperationTracer The custom operation tracer to use + * @param operationTracer The operation tracer to use * @return A BlockSimulationResult from processing the BlockStateCall. */ private BlockSimulationResult processBlockStateCall( @@ -201,7 +233,7 @@ private BlockSimulationResult processBlockStateCall( final Supplier signatureSupplier, final Map blockHashCache, final long simulationCumulativeGasUsed, - final OperationTracer customOperationTracer) { + final OperationTracer operationTracer) { BlockOverrides blockOverrides = blockStateCall.getBlockOverrides(); ProtocolSpec protocolSpec = @@ -261,7 +293,7 @@ private BlockSimulationResult processBlockStateCall( signatureSupplier, simulationCumulativeGasUsed, blockAccessListBuilder, - customOperationTracer); + operationTracer); Optional postExecutionAccessLocationTracker = blockAccessListBuilder.map( @@ -310,7 +342,7 @@ protected BlockStateCallSimulationResult processTransactions( final Supplier signatureSupplier, final long simulationCumulativeGasUsed, final Optional blockAccessListBuilder, - final OperationTracer customOperationTracer) { + final OperationTracer operationTracer) { TransactionValidationParams transactionValidationParams = shouldValidate ? STRICT_VALIDATION_PARAMS : SIMULATION_PARAMS; @@ -334,10 +366,10 @@ protected BlockStateCallSimulationResult processTransactions( final CallParameter callParameter = blockStateCall.getCalls().get(transactionLocation); // Custom tracer and EthTraceTransfers are mutually exclusive - OperationTracer operationTracer = customOperationTracer; + OperationTracer finalOperationTracer = operationTracer; if (isTraceTransfers) { - if (operationTracer == OperationTracer.NO_TRACING) { - operationTracer = new EthTransferLogOperationTracer(); + if (finalOperationTracer == OperationTracer.NO_TRACING) { + finalOperationTracer = new EthTransferLogOperationTracer(); } else { // this shouldn't happen, and isTraceTransfers will go away with Glamsterdam throw new IllegalArgumentException( @@ -362,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), @@ -394,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 ba66bc51130..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 = '5oL9XdQtad+Afrz6LEVZdNUhwSRayyp9Ag8+BF/Oj2s=' + knownHash = 'VUXqv8VKnFsYb7fqL9svGN7+tsxIUFdmt9G7JAIDpeI=' } check.dependsOn('checkAPIChanges')