From 53e81e393cd1e3b2a5557730e0c56e4905bd7fcc Mon Sep 17 00:00:00 2001 From: Grubby Date: Sat, 25 Apr 2026 02:55:30 +0800 Subject: [PATCH 1/3] debug_traceCallMany: strip filler blocks from overrides result **Q: Bug?** A: `debug_traceCallMany` overrides path returned traces for every simulated block, including the filler blocks `SimulateTxExecutor` inserts when `BlockOverride.Number` has gaps. Result count no longer matched the input bundles. **Q: Fix?** A: Compute each bundle's target block number (same `overrideNumber ?? lastBlockNumber + 1` rule the executor uses) and filter `simulationResult.Data` down to that set. Regression test covers a non-contiguous gap between two bundles. --- .../DebugRpcModuleTests.TraceCallMany.cs | 19 +++++++++++++++++++ .../Modules/DebugModule/DebugRpcModule.cs | 17 ++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs index deec41ff1780..955177f9a5de 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs @@ -180,6 +180,25 @@ public async Task Debug_traceCallMany_mixed_bundles_preserves_order() result.Data.Select(r => r.Count()).Should().BeEquivalentTo([1, 1]); } + [Test] + public async Task Debug_traceCallMany_with_block_number_gap_returns_one_entry_per_bundle() + { + using Context ctx = await CreateContext(); + long headNumber = ctx.Blockchain.BlockTree.Head!.Number; + + TransactionBundle first = CreateBundle(CreateTransaction()); + first.BlockOverride = new BlockOverride { Number = (ulong)(headNumber + 1) }; + + TransactionBundle second = CreateBundle(CreateTransaction(to: TestItem.AddressD)); + second.BlockOverride = new BlockOverride { Number = (ulong)(headNumber + 5) }; + + ResultWrapper>> result = + ctx.DebugRpcModule.debug_traceCallMany([first, second], BlockParameter.Latest); + + result.Data.Should().HaveCount(2); + result.Data.Select(r => r.Count()).Should().BeEquivalentTo([1, 1]); + } + [Test] public async Task Debug_traceCallMany_caps_gas_to_gas_cap() { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs index 0c80b67e4b7a..74eed06f7dcc 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs @@ -482,6 +482,19 @@ private ResultWrapper>> TraceCallManyWi }).ToList() }; + // SimulateTxExecutor inserts filler blocks between bundles when BlockOverride.Number has gaps. + // Pre-compute the block number each bundle targets so we can drop fillers from the result and + // keep a 1:1 mapping to the input bundles. + HashSet bundleBlockNumbers = new(bundles.Length); + long lastBlockNumber = header.Number; + foreach (TransactionBundle bundle in bundles) + { + ulong? overrideNumber = bundle.BlockOverride?.Number; + long number = overrideNumber is not null ? (long)overrideNumber.Value : lastBlockNumber + 1; + bundleBlockNumbers.Add(number); + lastBlockNumber = number; + } + BlockParameter concreteBlockParameter = new(header.Number); using CancellationTokenSource timeout = BuildTimeoutCancellationTokenSource(); @@ -503,7 +516,9 @@ private ResultWrapper>> TraceCallManyWi return ResultWrapper>>.Fail(errorMessage, simulationResult.ErrorCode); } - IEnumerable> bundleTraces = simulationResult.Data.Select(blockResult => blockResult.Traces); + IEnumerable> bundleTraces = simulationResult.Data + .Where(blockResult => blockResult.Number is long n && bundleBlockNumbers.Contains(n)) + .Select(blockResult => blockResult.Traces); return ResultWrapper>>.Success(bundleTraces); } From fe8ac0ec1d63c99924266dbc1e02638ac32e9798 Mon Sep 17 00:00:00 2001 From: Grubby Date: Sat, 25 Apr 2026 05:16:08 +0800 Subject: [PATCH 2/3] add test case --- .../Modules/DebugRpcModuleTests.TraceCallMany.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs index 955177f9a5de..79369f9944cd 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.TraceCallMany.cs @@ -180,8 +180,9 @@ public async Task Debug_traceCallMany_mixed_bundles_preserves_order() result.Data.Select(r => r.Count()).Should().BeEquivalentTo([1, 1]); } - [Test] - public async Task Debug_traceCallMany_with_block_number_gap_returns_one_entry_per_bundle() + [TestCase(3, TestName = "Debug_traceCallMany_with_minimum_block_number_gap_returns_one_entry_per_bundle")] + [TestCase(5, TestName = "Debug_traceCallMany_with_block_number_gap_returns_one_entry_per_bundle")] + public async Task Debug_traceCallMany_with_block_number_gap_returns_one_entry_per_bundle(int secondBundleOffset) { using Context ctx = await CreateContext(); long headNumber = ctx.Blockchain.BlockTree.Head!.Number; @@ -190,7 +191,7 @@ public async Task Debug_traceCallMany_with_block_number_gap_returns_one_entry_pe first.BlockOverride = new BlockOverride { Number = (ulong)(headNumber + 1) }; TransactionBundle second = CreateBundle(CreateTransaction(to: TestItem.AddressD)); - second.BlockOverride = new BlockOverride { Number = (ulong)(headNumber + 5) }; + second.BlockOverride = new BlockOverride { Number = (ulong)(headNumber + secondBundleOffset) }; ResultWrapper>> result = ctx.DebugRpcModule.debug_traceCallMany([first, second], BlockParameter.Latest); From 67856eb252e246bc285d136417285b187179d646 Mon Sep 17 00:00:00 2001 From: Grubby Date: Sat, 25 Apr 2026 15:15:25 +0800 Subject: [PATCH 3/3] debug_traceCallMany: extract block number helper shared with executor --- .../Nethermind.Evm/BlockOverrideExtensions.cs | 10 ++++++++++ .../Modules/DebugModule/DebugRpcModule.cs | 4 ++-- .../Modules/Eth/SimulateTxExecutor.cs | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/Nethermind/Nethermind.Evm/BlockOverrideExtensions.cs diff --git a/src/Nethermind/Nethermind.Evm/BlockOverrideExtensions.cs b/src/Nethermind/Nethermind.Evm/BlockOverrideExtensions.cs new file mode 100644 index 000000000000..b251fd4d038e --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/BlockOverrideExtensions.cs @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Evm; + +public static class BlockOverrideExtensions +{ + public static ulong GetBlockNumber(this BlockOverride? blockOverride, long lastBlockNumber) + => blockOverride?.Number ?? (ulong)lastBlockNumber + 1; +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs index 74eed06f7dcc..923ae88de510 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs @@ -10,6 +10,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Evm; using Nethermind.Blockchain.Tracing.GethStyle; using Nethermind.JsonRpc.Data; using Nethermind.Logging; @@ -489,8 +490,7 @@ private ResultWrapper>> TraceCallManyWi long lastBlockNumber = header.Number; foreach (TransactionBundle bundle in bundles) { - ulong? overrideNumber = bundle.BlockOverride?.Number; - long number = overrideNumber is not null ? (long)overrideNumber.Value : lastBlockNumber + 1; + long number = (long)bundle.BlockOverride.GetBlockNumber(lastBlockNumber); bundleBlockNumbers.Add(number); lastBlockNumber = number; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs index bd44d5e05989..59f487d6a3f5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs @@ -128,7 +128,7 @@ public override ResultWrapper>> Execut foreach (BlockStateCall? blockToSimulate in call.BlockStateCalls) { blockToSimulate.BlockOverrides ??= new BlockOverride(); - ulong givenNumber = blockToSimulate.BlockOverrides.Number ?? (ulong)lastBlockNumber + 1; + ulong givenNumber = blockToSimulate.BlockOverrides.GetBlockNumber(lastBlockNumber); if (givenNumber > long.MaxValue) return ResultWrapper>>.Fail($"Block number too big {givenNumber}!", ErrorCodes.InvalidParams);