Skip to content

fix: bridge-off eth blocks include transactions (Curio reorg check) - #180

Merged
Reiers merged 1 commit into
mainfrom
fix/eth-block-transactions
Oct 26, 2026
Merged

Reiers merged 1 commit into
mainfrom
fix/eth-block-transactions

Conversation

@Reiers

@Reiers Reiers commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Problem

Stock Curio-PDP's reorg check (tasks/pdpv0/task_reorg_check.go, ReorgCheckEthAPI) walks the chain with go-ethereum's ethclient and looks for its tx hashes inside eth blocks:

  • BlockByNumber(nil) -> eth_getBlockByNumber("latest", true)
  • BlockByHash(parentHash) -> eth_getBlockByHash(hash, true), repeated down the chain
  • blk.Transactions() -> tx.Hash() matched against its sent txs; TransactionReceipt for its own txs

Other Curio eth calls (message/watch_eth.go, sender, replace, GC): TransactionReceipt, TransactionByHash, HeaderByNumber(nil) (= eth_getBlockByNumber("latest", false)), BlockNumber. Curio does not call eth_getTransactionByBlock*AndIndex or eth_getBlockTransactionCountBy*.

Bridge-off Lantern returned every block with transactions: [] and transactionsRoot 0x00. go-ethereum's getBlock rejects that combination outright ("server returned empty transaction list but block header indicates transactions"), so every walk failed. Even a parseable empty body would have made Curio think its txs had been reorged out.

Fix (Lotus v1.38.3 newEthBlockFromFilecoinTipSet parity, bridge-off)

  • Transactions: the messages included in the tipset at H (execution order, deduped across blocks, the same order the receipt index uses). Delegated messages become their EIP-1559 or legacy (homestead / EIP-155, new chain/ethtx/legacy.go) eth tx. Secp / BLS messages go through Lotus' native conversion: ABI-wrapped params, InvokeContract / EAM CreateExternal payload decode, f410 / masked-ID addressing against the post-execution state, the reverted-recipient sentinel, and a CID-derived hash. fullTx=false returns the hashes.
  • Header: hash = tipset key CID (Lotus), not the first block CID; parentHash likewise. gasUsed = sum of the receipts, logsBloom = the chain-indexer bloom (emitter + topics of every EVM-shaped event), gasLimit = 10B x blocks, Lotus constants for sha3Uncles / zero roots / zero miner, transactionsRoot 0x00 with txs and EmptyRootHash without (so go-ethereum accepts both).
  • Tags, Lotus semantics: latest and eth_blockNumber = head's parent (the newest executed tipset, so receipts exist); pending = head (gasUsed 0, unknown bloom); numbers above latest give "requested a future epoch (beyond 'latest')"; null rounds give "requested epoch was a null round (N)"; earliest is unsupported; safe / finalized = head-30 / head-900.
  • Canonical chain from parent links, never from the header store's possibly partial per-height tipset. A height/hash index is updated incrementally as the head moves, so each step of Curio's walk is a map lookup. Hashes handed out by older builds (single block CID) still resolve. eth_getLogs uses and accepts the same block hash.
  • Cost: built blocks are cached by tipset key (compact summary for 4096 tipsets with hashes / gasUsed / bloom, full bodies for 32). A cached summary skips the receipts and events walk on full-tx rebuilds. Messages load with bounded concurrency, and emitter lookups are memoized per block. By-number lookups reach 20160 epochs back; by-hash scans cover the tx lookback + 900.
  • Local eth_getTransactionByBlockNumberAndIndex, plus new eth_getTransactionByBlockHashAndIndex and eth_getBlockTransactionCountByNumber / ByHash (count uses the deduped list, like Lotus BlockMsgsForTipset).
  • eth_getTransactionByHash / receipts now also serve native messages and legacy txs locally, receipt type follows the tx, and delegated txs render accessList: null exactly like Lotus.
  • Degraded mode: if messages or receipts can't be loaded and no bridge is configured, fullTx=false falls back to the header-only block, which keeps HeaderByNumber (base fee for sends) working. fullTx=true returns the error instead of a misleading empty body.

Verification

TestEthBlockLocal_MatchesLotus compares against Glif, field by field, on calibration blocks 0x3feed0 (tx 0xfd05...8865, 42 txs, 3-block tipset), 0x3feec7 (tx 0xb3dc...9903, 51 txs including 1 EIP-155 legacy tx) and 0x3feeb0 (1 native secp SubmitWindowedPoSt). It covers by number and by hash, fullTx false and true, tx by number/hash + index, and counts by number/hash, and finds 0 diffs. The header store holds only a partial head tipset (1 of 4 blocks at 4189905), so latest must resolve to the full 3-block tipset. The same test replays Curio's walk (latest, then BlockByHash(parentHash) down to 0x3feec7) with go-ethereum's header/body consistency checks, and both Curio txs turn up in their canonical blocks. TestEthBlockLocal_Tags pins the tag and error semantics and the cache.

Glif load-balances Lotus 1.38.3 and Forest. The two backends render one field differently: accessList on delegated txs (Lotus null, Forest [] or omitted on legacy). The test normalizes recorded responses to the Lotus form (asLotusShape), and the existing receipt test does the same. One Lotus backend had cached 0x3feed0 before indexing it and serves the "unknown" full bloom. In that case the recorder uses the OR of Glif's eth_getBlockReceipts blooms, which is what the Lotus indexer stores.

go vet ./... and go test ./... pass (CGO_ENABLED=0).

Remaining gaps

  • eth_getLogs still reports transactionHash as the message CID digest for delegated txs. Lotus reports the eth tx hash (pre-existing, separate fix).
  • pending (the head) is built from the pre-state: gasUsed 0, full bloom. Native-message address lookups could differ only for actors created by the head's own messages.
  • By-hash only resolves canonical tipsets within the scan window. Lotus also resolves known fork tipsets.
  • safe / finalized use the static head-30 / head-900, with no EC-finality calculator or F3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant