Repository navigation
fix: eth_estimateGas executes the call and reports reverts (stop Curio broadcasting doomed txs) - #195
Merged
Merged
Conversation
…ecord revert backtraces
…ns, backtraces, payable, sends) + handler flow tests
…argets, proxy revert backtrace
…om un-executed gas estimates
This was referenced Oct 31, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
eth_estimateGasreturned a fixed heuristic gas limit (0x55b13d93, about 1.44B gas) without executing the call. Lotus executes the message and fails with JSON-RPC code 3 when it would revert; stock Curio relies on that failure to not broadcast a doomed transaction. With Lantern as its only chain node, Curio broadcast transactions that then reverted on chain (PDPVerifierprovePossession"Incorrect fee amount",nextProvingPeriodDataSetPaymentBeyondEndEpoch, FilecoinPaysettleRailexit 7). An external operator saw about 30% of roughly 230 txs/hour revert, each burning gas at the 1.44B limit.Same request on calibration, before this PR: Glif returns
Only the storage provider can prove possession, Lantern returned0x55b13d93.Fix
EthEstimateGas(moved torpc/handlers/eth_estimategas.go) executes the call with the local EVM against verified state at the live head, withfrom,to,value,data:api.ErrExecutionReverted: code 3,message execution failed (exit=[33], revert reason=[...], vm error=[message failed with backtrace: ...]),data= revert bytes hex. Reason decoding ports Lotus'ParseEthRevert(Error(string), Solidity panics, raw hex for custom errors,nonefor empty). Backtrace: actor id, FVM method (3844450837, or 6 for DELEGATECALL frames), pc of each REVERT, exit code.valueexit=[SysErrInsufficientFunds(6)]error, data0x--vm-bridge-rpcis set (an upstream code-3 revert is passed through as code 3), else the existing heuristiceth_estimateGas: call was NOT executed locallyThe error type implements
jsonrpc.RPCErrorCodec, so it goes out on the wire as{code: 3, message, data: "0x..."}exactly like Lotus; go-ethereum's ethclient surfaces it as a revert (rpc.DataError). The same codec is added to the existing localeth_callrevert error, soeth_callreverts are now code 3 + data too (previously code 1, data only in the message text).Call value (payable calls, #178 follow-up)
vm/evmgainsCallWithValue: the sender balance check (elseInsufficientFundsError, nothing runs), CALLVALUE, and balance moves journaled on the ephemeral overlay (BALANCE / SELFBALANCE see them; a reverted sub-call rolls them back).CALL{value}from a contract is modeled into EVM contracts and account-type actors (EOAs, placeholders, burnt funds actor f099, new f4 addresses), so the usual "burn the fee, refund the excess to msg.sender" pattern executes. Nothing is transferred on chain.Coverage and limits (explicit)
Covered:
msg.value, value forwarded to EVM contracts / EOAs / burnt funds actor.Not covered (falls back to the bridge, or heuristic + WARN when bridge-off; never a guessed success):
to).CALL{value}into a native actor, a precompile (including the FEVM call_actor "pay" path 0xfe..03), an unknown ID address, or more value than the contract holds; unimplemented precompiles / opcodes (existing ErrUnsupported faults). If such an operation is reached after the point where the revert would be decided, the call is still treated as not executable rather than guessed.head.ParentState()(whateth_call"latest" runs on). Lotus estimates on the post-head state plus the sender's pending mpool messages; Lantern does not execute tipsets, so it is one epoch older and ignores pending messages. A tx whose outcome flipped in the last epoch can be mis-judged once; Curio retries.datais0x.Tests
rpc/handlers/testdata/estimategas_calib.json: every code / storage slot / balance the local EVM read for 7 cases, recorded from a public calibration Lotus at a pinned block together with Lotus' owneth_estimateGasanswer (re-record withLANTERN_RECORD_ESTGAS_FIXTURE=<rpc> go test ./rpc/handlers -run TestRecordEstimateGasFixture).TestEstimateGas_Fixturerequires the local error message and data to equal Lotus' byte for byte (backtrace PCs and actor ids included) and success where Lotus estimates: provePossession from a non-provider, addPieces with value=1 ("no fee on add to existing dataset", payable fee check) and value=0, USDFC decimals() and transfer(0), FIL send, FIL send beyond balance.vm/evm/value_test.go: payable fee check with insufficient fee / no value / enough (burn + refund), insufficient sender funds, BALANCE / SELFBALANCE visibility, rollback of value on a reverted sub-call, unmodeled targets fault, STATICCALL stays read-only, proxy revert backtrace.Live check (temp calibration home, bridge-off daemon on a loopback port, vs Glif)
provePossession(44538, ...) from 0x...dEaD to PDPVerifier:
addPieces(44538, ...) value=0x1 (payable): both
revert reason=[Error(no fee on add to existing dataset)], same backtrace (pc 14774) and data. value=0: bothError(Only the storage provider can add pieces)(pc 14695). FIL send beyond balance: identicalSysErrInsufficientFunds(6)message. Successes (USDFC decimals(), transfer(0), FIL send): Lantern returns an estimate (heuristic, no bridge configured), Glif returns its metered estimate.go build,go vet,go test ./...green with CGO_ENABLED=0. Merges cleanly with perf/curio-pdp-burst (trial merge builds and passes rpc/handlers + vm/evm); no changes to evmexec.go / chain_api.go.CHANGELOG entry and a RUN-PDP troubleshooting row added.