Skip to content

blockchain: Fix potential overflow in DoEstimateGas#492

Merged
2dvorak merged 1 commit intokaiachain:devfrom
2dvorak:estimategas-potential-overflow
Aug 6, 2025
Merged

blockchain: Fix potential overflow in DoEstimateGas#492
2dvorak merged 1 commit intokaiachain:devfrom
2dvorak:estimategas-potential-overflow

Conversation

@2dvorak
Copy link
Contributor

@2dvorak 2dvorak commented Aug 5, 2025

Proposed changes

Types of changes

Please put an x in the boxes related to your change.

  • Bugfix
  • New feature or enhancement
  • Others

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I have read the CONTRIBUTING GUIDELINES doc
  • I have read the CLA and signed by comment I have read the CLA Document and I hereby sign the CLA in first time contribute
  • Lint and unit tests pass locally with my changes ($ make test)
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)
  • Any dependent changes have been merged and published in downstream modules

Related issues

Further comments

I tried to add test case in api/api_eth_test.go for the overflow leading to misbehavior, however it was tricky.

  • We could trigger overflow easily with very large gaslimit however the search could easily find out the correct value if the estimated gas was realistic.
  • We need very large estimated gas to trigger misbehavior, however it was not feasible to make such tx.

Instead I simulated the tx execution that requires very large gas with a something like:

testFn := func(gas uint64) (bool, *blockchain.ExecutionResult, error) {
	threshold := uint64(math.MaxUint64 - 2000000)
	if gas < threshold {
		return true, nil, nil
	}
	return false, &blockchain.ExecutionResult{}, nil
}

The blockchain.DoEstimateGas reached timeout without the fix, and returned a very large estimated gas with the fix.

However to use the simulated tx execution, I had to bypass actual API functions like api.EstimateGas and directly call blockchain.DoEstimateGas. That make the test case irrelevant to API test. So I did not include the test case in this PR. Also I didn't feel like adding a new test function for the blockchain.DoEstimateGas itself (e.g., create blockchain/evm_test.go).

If you're interested, here's the test case:

diff
diff --git a/api/api_eth_test.go b/api/api_eth_test.go
index 9e5ac702c..ef8fb8a1a 100644
--- a/api/api_eth_test.go
+++ b/api/api_eth_test.go
@@ -6,6 +6,7 @@ import (
        "encoding/json"
        "errors"
        "fmt"
+       "math"
        "math/big"
        "reflect"
        "testing"
@@ -2527,6 +2528,7 @@ func testEstimateGas(t *testing.T, mockBackend *mock_api.MockBackend, fnEstimate
                expectErr string
                expectGas uint64
                overrides EthStateOverride
+               testFunc  func(args EthTransactionArgs, overrides *EthStateOverride) (hexutil.Uint64, error)
        }{
                { // simple transfer
                        args: EthTransactionArgs{
@@ -2633,10 +2635,45 @@ func testEstimateGas(t *testing.T, mockBackend *mock_api.MockBackend, fnEstimate
                        // So EstimateGas will be able to return expected gas instead of this error.
                        // expectErr: "insufficient gas for floor data gas cost",
                },
+               { // Test case for DoEstimateGas overflow bug - reproduces timeout/erratic behavior
+                       args:      EthTransactionArgs{},
+                       expectGas: 18446744073707551615, // Actual gas for minimal transaction with extreme gas limit
+                       testFunc: func(args EthTransactionArgs, overrides *EthStateOverride) (hexutil.Uint64, error) {
+                               gasLimit := uint64(math.MaxUint64 - 1000000) // Very large gas limit
+                               rpcGasCap := uint64(math.MaxUint64 - 500000) // Very large RPC gas cap
+
+                               // Set transaction parameters
+                               txValue := big.NewInt(0)
+                               gasPrice := big.NewInt(1)                         // Very small gas price to allow large gas allowance
+                               balance := new(big.Int).SetUint64(math.MaxUint64) // Very large balance
+                               testFn := func(gas uint64) (bool, *blockchain.ExecutionResult, error) {
+
+                                       // Simulate a scenario where execution fails for lower gas values
+                                       // and succeeds for higher gas values, but the threshold is very high
+                                       // This will cause the binary search to have both hi and lo as very large numbers
+                                       threshold := uint64(math.MaxUint64 - 2000000)
+
+                                       if gas < threshold {
+                                               // Execution "fails" - need more gas
+                                               return true, nil, nil
+                                       }
+
+                                       // Execution "succeeds"
+                                       return false, &blockchain.ExecutionResult{}, nil
+                               }
+                               return blockchain.DoEstimateGas(context.Background(), gasLimit, rpcGasCap, txValue, gasPrice, balance, testFn)
+                       },
+               },
        }

        for i, tc := range testcases {
-               gas, err := fnEstimateGas(tc.args, &tc.overrides)
+               var gas hexutil.Uint64
+               var err error
+               if tc.testFunc != nil {
+                       gas, err = tc.testFunc(tc.args, &tc.overrides)
+               } else {
+                       gas, err = fnEstimateGas(tc.args, &tc.overrides)
+               }
                t.Logf("tc[%02d] = %d %v", i, gas, err)
                if len(tc.expectErr) > 0 {
                        require.NotNil(t, err)

@2dvorak 2dvorak self-assigned this Aug 5, 2025
@2dvorak 2dvorak added do not merge Do not merge just yet and removed do not merge Do not merge just yet labels Aug 5, 2025
@2dvorak 2dvorak merged commit 5dfa6fe into kaiachain:dev Aug 6, 2025
9 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Aug 6, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants