Skip to content

revert: do not skip balance validation in eth_simulateV1#11368

Merged
svlachakis merged 2 commits into
masterfrom
revert-bal
Apr 27, 2026
Merged

revert: do not skip balance validation in eth_simulateV1#11368
svlachakis merged 2 commits into
masterfrom
revert-bal

Conversation

@DarkLord017
Copy link
Copy Markdown
Contributor

@DarkLord017 DarkLord017 commented Apr 27, 2026

Reverts #11332

Changes

  • The original PR incorrectly removed balance validation for eth_simulateV1 with validation:false.
  • go-ethereum returns InsufficientFunds when validate:false too (see ethereum/go-ethereum api_test.go L1568, L2463)
  • Fixes broken hive tests:
    • ethSimulate-gas-fees-and-value-error-38014
    • ethSimulate-simple-no-funds
    • ethSimulate-simple-no-funds-with-balance-querying
    • ethSimulate-simple-send-from-contract-no-balance
    • ethSimulate-override-address-twice

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Notes on testing

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

Remarks

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 27, 2026

Claude finished @DarkLord017's task in 4m 46s —— View job


PR Review

  • Gather context and read changed files
  • Read relevant coding rules
  • Analyze correctness of the revert
  • Review code quality and edge cases
  • Post final review

Summary

This PR correctly reverts #11332, which improperly removed balance validation for eth_simulateV1 with validation:false. The revert re-aligns Nethermind with go-ethereum's behavior: even with validate:false, a InsufficientFunds error is returned when the sender lacks balance to cover the transferred value. The revert is clean — no orphaned Simulate method implementations remain and the removed SkipBalanceValidation/SimulateAndCommit flags are fully purged.


Findings

Medium — No regression test for the correct behavior

File: src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs

The removed test Test_eth_simulate_no_validation_skips_balance_check was asserting the wrong behavior (that balance is skipped). Removing it is correct. However, no new test asserts the correct behavior — that validate:false still returns -38014 / InsufficientFunds for a zero-balance sender.

Per AGENTS.md: "When fixing a bug, always add a regression test." The hive tests mentioned in the PR description (ethSimulate-simple-no-funds, etc.) provide integration-level coverage, but they run in a separate environment and do not prevent the incorrect behavior from being reintroduced by a future unit-test-passing PR.

Suggested replacement test:

[Test]
public async Task Test_eth_simulate_no_validation_still_returns_insufficient_balance()
{
    TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain();
    SimulatePayload<TransactionForRpc> payload = new()
    {
        BlockStateCalls =
        [
            new() { Calls = [ new EIP1559TransactionForRpc { From = TestItem.AddressA, To = TestItem.AddressB, Value = 1_000_000.Ether() } ] }
        ],
        Validation = false
    };

    SimulateTxExecutor<SimulateCallResult> executor = new(chain.Bridge, chain.BlockFinder, new JsonRpcConfig(), chain.SpecProvider, new SimulateBlockMutatorTracerFactory());
    ResultWrapper<IReadOnlyList<SimulateBlockResult<SimulateCallResult>>> result = executor.Execute(payload, BlockParameter.Latest);

    Assert.That((bool)result.Result, Is.True);
    SimulateCallResult call = result.Data[0].Calls.First();
    Assert.That(call.Error?.Code, Is.EqualTo(-38014)); // InsufficientFunds
}

Fix this →


Low — PR type checkbox

The PR ticks Refactoring but this is a Bugfix — it corrects incorrect behavior introduced by #11332. Should also tick the Bugfix checkbox (or replace Refactoring with Bugfix).


Low — Slightly imprecise Trace doc comment (pre-existing)

File: src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs:31

The XML doc for Trace reads "no validations" but balance IS validated (the SubtractUnderflow check in BuyGas is unconditional). This was the state before #11332 and is not introduced by this PR, but it will now more visibly affect eth_simulate callers since SimulateTransactionProcessorAdapter routes validate:false through Trace.


Correctness analysis

BuyGas balance flow (post-revert, Trace = SkipValidationAndCommit):

Condition Behavior
validate:false, MaxFeePerGas = 0, balance < value InsufficientSenderBalance ✓ (matches geth)
validate:false, MaxFeePerGas = 0, balance >= value proceeds, no gas charged from sender ✓
validate:false, MaxFeePerGas > 0 ShouldValidateGas returns true; gas fee checks also apply ✓
validate:true full Execute path, unchanged ✓

PayValue simplification is safe: BuyGas gate at line 611 ensures senderBalance ≥ value before PayValue is ever reached. The algebraic proof: BuyGas asserts senderBalance ≥ value + senderReservedGasPayment; after subtracting senderReservedGasPayment from state, the remaining balance satisfies remainingBalance ≥ value, so SubtractFromBalance in PayValue cannot underflow.

No orphaned Simulate method implementations remain in any ITransactionProcessor implementors (OptimismTransactionProcessor, TaikoTransactionProcessor, XdcTransactionProcessor, SystemTransactionProcessor — all checked).

@DarkLord017
Copy link
Copy Markdown
Contributor Author

@claude re-review

@DarkLord017 DarkLord017 marked this pull request as ready for review April 27, 2026 10:24
Copy link
Copy Markdown
Contributor

@svlachakis svlachakis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DarkLord017 please attach the link of a passing hive job

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 27, 2026

Claude finished @DarkLord017's task in 2m 15s —— View job


PR Re-Review

  • Gather context and read changed files
  • Read relevant coding rules
  • Analyze correctness of the revert
  • Review code quality and edge cases
  • Check for the regression test added since last review
  • Post final review

Summary

All findings from the previous review have been addressed. The revert is correct and complete.

Previous Medium finding resolved: A regression test Test_eth_simulate_no_validation_still_returns_insufficient_balance has been added (lines 521–539 of EthSimulateTestsBlocksAndTransactions.cs). It correctly:

  • Sets Validation = false
  • Sends 1,000,000 ETH from a zero-balance TestItem.AddressA
  • Asserts the request fails with "insufficient sender balance" and error code -38014 (ErrorCodes.InsufficientFunds)

This aligns with go-ethereum behavior (api_test.go L1568/L2463) and matches what the hive tests expect.


Findings

Low — Pre-existing: Trace XML doc says "no validations" (not introduced by this PR)

File: src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs:29

/// Call transaction, no validations, commit state

The Trace path does enforce balance ≥ value (the SubtractUnderflow check in BuyGas is unconditional). The doc is slightly misleading but pre-dates this PR — no action required here.


Correctness analysis

BuyGas flow post-revert with validate:false (= SkipValidationAndCommit):

Condition Behavior
validate:false, GasPrice=0, balance < value InsufficientSenderBalance
validate:false, GasPrice=0, balance ≥ value proceeds, gas not charged from sender ✓
validate:false, MaxFeePerGas > 0 ShouldValidateGas returns true; fee checks apply ✓
validate:true full Execute path, unchanged ✓

PayValue simplification is safe: BuyGas gate at line 611 guarantees senderBalance ≥ value before PayValue is ever reached — no underflow possible.

Simulate method removal is clean: ITransactionProcessor, TransactionProcessorBase, and DisposableScopeOverridableEnvTests.TestTransactionProcessor are all consistent. No orphaned implementations.


Verdict: ready to merge from a code standpoint. The remaining ask from @svlachakis (link to a passing hive job) is a process/sign-off step, not a code concern.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 27, 2026

Claude finished @DarkLord017's task in 4m 54s —— View job


PR Re-Review

  • Gather context and check new commits
  • Read changed files
  • Check if Medium finding (regression test) was addressed
  • Analyze correctness and edge cases
  • Post final review

Summary

The Medium finding from the previous review (missing regression test) has been addressed by commit 8daf69e add test for revert. The PR is now clean and correct.


Finding Resolution

✅ Medium (Previous): Regression test — RESOLVED

Commit 8daf69e adds Test_eth_simulate_no_validation_still_returns_insufficient_balance, which correctly asserts the fixed behavior:

Assert.That(result.Result!.Error!.Contains("insufficient sender balance"), Is.True);
Assert.That(result.ErrorCode, Is.EqualTo(ErrorCodes.InsufficientFunds));

The test exercises the full RPC stack via chain.EthRpcModule.eth_simulateV1(...) rather than calling SimulateTxExecutor directly — this is the right level to test, since it validates the entire call path including SimulateTransactionProcessorAdapter → Trace → BuyGas.

Informational nit (non-blocking): result.Result!.Error! uses null-forgiving ! operators — if Error is unexpectedly null (i.e., regression reintroduced), the test will throw a NullReferenceException instead of a clear AssertionException. A minor robustness improvement would be Assert.That(result.Result?.Error, Does.Contain("insufficient sender balance")), but this is cosmetic and doesn't affect regression detection.


Pre-existing Low — Trace doc comment (unchanged)

File: src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs:31

The XML doc for Trace still reads "no validations", but balance IS validated unconditionally in BuyGas. Pre-existing; not introduced by this PR.


Correctness Confirmation

The error flow is verified correct post-revert:

  • TraceSkipValidationAndCommitBuyGas checks senderBalance < value unconditionally
  • InsufficientSenderBalance propagates → InvalidTransactionException thrown → caught by SimulateBridgeHelperresults.Error set → ResultWrapper.Fail(-38014) returned
  • Matches go-ethereum's behavior for validate:false

The PayValue simplification is safe: BuyGas gates entry so senderBalance ≥ value is guaranteed before PayValue is reached.

All implementors of ITransactionProcessor (OptimismTransactionProcessor, TaikoTransactionProcessor, XdcTransactionProcessor, SystemTransactionProcessor) correctly do not need to implement the removed Simulate method.


Verdict

Mergeable. All Medium+ findings are resolved. The revert is correct, the test coverage is appropriate, and the cleanup is thorough.

@DarkLord017
Copy link
Copy Markdown
Contributor Author

@svlachakis svlachakis merged commit b71c352 into master Apr 27, 2026
445 of 448 checks passed
@svlachakis svlachakis deleted the revert-bal branch April 27, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants