evmone: top-level tx with sender nonce 2^64-2 is executed as a light failure
Summary
When sender nonce is 2^64-2 and tx nonce matches, evmone increments sender nonce to 2^64-1 and then incorrectly aborts top-level execution as a light failure (EVMC_FAILURE with full execution gas left).
geth executes the transaction normally.
This causes consensus output divergence (stateRoot, receiptsRoot, gasUsed) on the same input JSON.
Why this is wrong
Per EIP-2681, transactions are invalid only when tx nonce >= 2^64-1.
A transaction with nonce 2^64-2 is valid and should execute; sender nonce becomes 2^64-1 after inclusion.
Relevant EIP-2681 text:
- “Consider any transaction invalid, where the nonce exceeds or equals to
2^64-1.”
Minimal repro (t8n JSON)
alloc.json:
{
"0x1cb29f6b656a8ae0f6c1a3352b2daa7dc5b71393": {
"nonce": "0xfffffffffffffffe",
"balance": "0xffffffffffffffffffffffffffffffff",
"code": "0x",
"storage": {}
}
}
env.json:
{
"currentCoinbase": "0x8888f1f195afa192cfee860698584c030f4c9db1",
"currentNumber": "0x1",
"currentTimestamp": "0x54c99069",
"currentGasLimit": "0x5f5e100",
"currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000001",
"parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
"currentBaseFee": "0x1",
"currentExcessBlobGas": "0x0",
"withdrawals": []
}
txs.json:
[
{
"chainId": "0x1",
"gas": "0x186a0",
"gasPrice": "0x1",
"hash": "0x08278b7d422136b9796e09a103c60332e510e6d5768348c5f0deb393267080bd",
"input": "0x",
"nonce": "0xfffffffffffffffe",
"r": "0x8b9272c0bd5cdbd21ae659a306b137b1d6d01c4ebdc59b743fe457cbc03784ac",
"s": "0x35b6594ffe5276f5f6fbb1af28edd48fcb569fd02ffbef660eff4c20d4a78e17",
"sender": "0x1cb29f6b656A8ae0f6C1A3352B2DaA7DC5b71393",
"to": "0x0000000000000000000000000000000000000004",
"v": "0x26",
"value": "0x0"
}
]
Command:
# geth
evm t8n \
--state.fork Cancun --state.chainid 1 \
--input.alloc alloc.json --input.env env.json --input.txs txs.json \
--output.basedir out-geth --output.result result.json --output.alloc alloc.json --output.body body.rlp
# evmone
evmone-t8n \
--state.fork Cancun --state.chainid 1 \
--input.alloc alloc.json --input.env env.json --input.txs txs.json \
--output.basedir out-evmone --output.result result.json --output.alloc alloc.json --output.body body.rlp
Observed (Cancun):
geth: gasUsed = 0x5217, stateRoot = 0x89ee41a1...33ea2d0
evmone: gasUsed = 0x5208, stateRoot = 0x34820ad7...1587de89
(receiptsRoot also differs.)
Also visible in blockchaintest
Artifact:
case27-nonce-maxminus1-cancun-call/blocktest_nonce_maxminus1_call.json
Running evmone-blockchaintest on this file fails with mismatched stateRoot, receiptsRoot, and gasUsed.
Root cause
evmone validates nonce correctly at tx validation time (sender nonce == 2^64-1 is rejected, 2^64-2 is accepted), then:
- increments sender nonce in transition, making it
2^64-1;
- calls
Host::prepare_message() for top-level execution;
prepare_message() re-checks sender_acc.nonce == Account::NonceMax and returns failure even for depth-0 tx.
This second check is appropriate for internal CREATE/CREATE2, but not for already-validated top-level transaction execution.
Relevant locations:
test/state/state.cpp (transition(): sender nonce bumped before execution)
test/state/host.cpp (prepare_message(): if (sender_acc.nonce == Account::NonceMax) return {};)
test/state/host.cpp (call(): nullopt => light failure with full gas returned)
Extra confirmation
The same behavior affects top-level create transactions at nonce 2^64-2 (case26-nonce-maxminus1-create): geth creates contract, evmone skips execution.
evmone: top-level tx with sender nonce
2^64-2is executed as a light failureSummary
When sender nonce is
2^64-2and tx nonce matches,evmoneincrements sender nonce to2^64-1and then incorrectly aborts top-level execution as a light failure (EVMC_FAILUREwith full execution gas left).gethexecutes the transaction normally.This causes consensus output divergence (
stateRoot,receiptsRoot,gasUsed) on the same input JSON.Why this is wrong
Per EIP-2681, transactions are invalid only when tx nonce
>= 2^64-1.A transaction with nonce
2^64-2is valid and should execute; sender nonce becomes2^64-1after inclusion.Relevant EIP-2681 text:
2^64-1.”Minimal repro (t8n JSON)
alloc.json:{ "0x1cb29f6b656a8ae0f6c1a3352b2daa7dc5b71393": { "nonce": "0xfffffffffffffffe", "balance": "0xffffffffffffffffffffffffffffffff", "code": "0x", "storage": {} } }env.json:{ "currentCoinbase": "0x8888f1f195afa192cfee860698584c030f4c9db1", "currentNumber": "0x1", "currentTimestamp": "0x54c99069", "currentGasLimit": "0x5f5e100", "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000000001", "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", "currentBaseFee": "0x1", "currentExcessBlobGas": "0x0", "withdrawals": [] }txs.json:[ { "chainId": "0x1", "gas": "0x186a0", "gasPrice": "0x1", "hash": "0x08278b7d422136b9796e09a103c60332e510e6d5768348c5f0deb393267080bd", "input": "0x", "nonce": "0xfffffffffffffffe", "r": "0x8b9272c0bd5cdbd21ae659a306b137b1d6d01c4ebdc59b743fe457cbc03784ac", "s": "0x35b6594ffe5276f5f6fbb1af28edd48fcb569fd02ffbef660eff4c20d4a78e17", "sender": "0x1cb29f6b656A8ae0f6C1A3352B2DaA7DC5b71393", "to": "0x0000000000000000000000000000000000000004", "v": "0x26", "value": "0x0" } ]Command:
Observed (Cancun):
geth:gasUsed = 0x5217,stateRoot = 0x89ee41a1...33ea2d0evmone:gasUsed = 0x5208,stateRoot = 0x34820ad7...1587de89(
receiptsRootalso differs.)Also visible in blockchaintest
Artifact:
case27-nonce-maxminus1-cancun-call/blocktest_nonce_maxminus1_call.jsonRunning
evmone-blockchainteston this file fails with mismatchedstateRoot,receiptsRoot, andgasUsed.Root cause
evmonevalidates nonce correctly at tx validation time (sender nonce == 2^64-1is rejected,2^64-2is accepted), then:2^64-1;Host::prepare_message()for top-level execution;prepare_message()re-checkssender_acc.nonce == Account::NonceMaxand returns failure even for depth-0 tx.This second check is appropriate for internal
CREATE/CREATE2, but not for already-validated top-level transaction execution.Relevant locations:
test/state/state.cpp(transition(): sender nonce bumped before execution)test/state/host.cpp(prepare_message():if (sender_acc.nonce == Account::NonceMax) return {};)test/state/host.cpp(call(): nullopt => light failure with full gas returned)Extra confirmation
The same behavior affects top-level create transactions at nonce
2^64-2(case26-nonce-maxminus1-create):gethcreates contract,evmoneskips execution.