Skip to content

test/state: blob fee affordability overflow can accept invalid blob tx (diverges from geth) #1480

@chfast

Description

@chfast

Summary

evmone test-state transition logic can accept an invalid Cancun blob transaction when maxFeePerBlobGas is extremely large.

The affordability check in test/state/state.cpp overflows in the blob-fee component and underestimates max_total_fee, causing divergence vs go-ethereum (evm statetest / evm t8n).

This affects test tooling (evmone-statetest, evmone-t8n) and state-transition correctness in edge cases.

Spec expectation

EIP-4844 execution-layer validation requires:

max_total_fee += get_total_blob_gas(tx) * tx.max_fee_per_blob_gas

and then checking sender balance against that value (no wraparound).

Reproducer (state test JSON)

{
  "blob_fee_overflow_balance_check": {
    "_info": {
      "comment": "Blob max fee overflow affordability check differential test"
    },
    "env": {
      "currentCoinbase": "0x8888f1f195afa192cfee860698584c030f4c9db1",
      "currentNumber": "0x01",
      "currentTimestamp": "0x54c99069",
      "currentGasLimit": "0x2fefd8",
      "currentDifficulty": "0x0",
      "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000001",
      "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "currentBaseFee": "0x1",
      "currentExcessBlobGas": "0x0",
      "withdrawals": []
    },
    "pre": {
      "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
        "code": "0x",
        "nonce": "0x00",
        "balance": "0x02540be400",
        "storage": {}
      },
      "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": {
        "code": "0x",
        "nonce": "0x00",
        "balance": "0x00",
        "storage": {}
      }
    },
    "transaction": {
      "data": ["0x"],
      "gasLimit": ["0x5208"],
      "value": ["0x0"],
      "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
      "nonce": "0x0",
      "chainId": "0x1",
      "type": "0x3",
      "maxFeePerGas": "0x1",
      "maxPriorityFeePerGas": "0x0",
      "maxFeePerBlobGas": "0x800000000000000000000000000000000000000000000000000000000000",
      "blobVersionedHashes": [
        "0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
      ],
      "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
      "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
    },
    "post": {
      "Cancun": [
        {
          "indexes": { "data": 0, "gas": 0, "value": 0 },
          "hash": "0x94be762b5d5ce9c88c5aed904aebaec40df6f5c4d9aee8ef4ee0218e6d3ea0b3",
          "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
          "expectException": "insufficient funds"
        }
      ]
    }
  }
}

Commands

build/debug/bin/evmone-statetest blob_fee_overflow_state_test.json
evm statetest blob_fee_overflow_state_test.json

Observed

  • evmone-statetest fails with:
    • unexpected valid transaction
  • evm statetest (go-ethereum v1.17.2) passes:
    • state root 0x94be762b5d5ce9c88c5aed904aebaec40df6f5c4d9aee8ef4ee0218e6d3ea0b3

Equivalent t8n run shows:

  • evmone-t8n: accepts tx (rejected: [], nonzero gas used)
  • geth evm t8n: rejects tx with
    • insufficient funds for gas * price + value ... required balance exceeds 256 bits

Suspected root cause

In test/state/state.cpp:

if (tx.type == Transaction::Type::blob)
{
    const auto total_blob_gas = tx.blob_gas_used();
    // FIXME: Can overflow uint256.
    max_total_fee += total_blob_gas * tx.max_blob_gas_price;
}

total_blob_gas * max_blob_gas_price wraps (here 2^17 * 2^239 = 2^256) and effectively contributes 0, so affordability check is bypassed.

Practicality

This particular trigger is not practically reachable on current mainnet economics (requires enormous fee cap), but it is still a deterministic cross-client divergence and violates EIP-4844 arithmetic semantics for edge inputs.

Possible fix direction

Compute blob max fee contribution in wide precision (same style as other umul(...) usage) and reject if final required balance exceeds representable uint256/balance domain, matching geth behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions