Skip to content

#45 Stage 7: real SP tx declines local send path bridge-off (parser mismatch + uncovered maxPriorityFeePerGas dep) #46

Description

@Reiers

Summary

The first live --vm-bridge-rpc-disable attempt on cc-smoke (2026-06-15) caught a real curio SP CreateDataSet tx that failed bridge-off. Stage 4/5 handlers are correct in isolation (unit + synthetic-tx live proof), but the real SP send flow exposed two concrete gaps that must close before bridge-off can work on a writing SP. Spun out of #45 so the write-path tracker stays clean.

Evidence (cc-smoke, calibration, 2026-06-15 15:07:44 UTC)

ERROR rpc  error in RPC call to 'eth_sendRawTransaction': FEVM method requires --vm-bridge-rpc pointing at a Forest/Lotus node
INFO  curio/message sender_eth.go:470  sent transaction  {"hash":"0x000...0", "task_id":40002, "send_error":"send error: FEVM method requires --vm-bridge-rpc...", "poll_loops":6}
ERROR pdpv0 handlers_create.go:137  Failed to send transaction: ...

The SP's SenderETH.Send hit errBridgeUnconfigured, meaning our local EthSendRawTransaction path declined and fell through to the (disabled) bridge. No local path declined, bridging debug line fired.

Ruled out

  • Not a dual-ChainAPI bug: single instance (start.go:108), Mpool wired synchronously in startGossipHead before rpcserver.New binds (:261), ethAPI.EthSendRawTransaction → e.full.EthSendRawTransaction confirmed.
  • Not a boot race: tx was ~5 min post-boot; gossipsub mempool publisher wired logged at boot.

Item 1 — local send declines on the real curio tx encoding

The curio SenderETH builds the tx as a go-ethereum types.DynamicFeeTxMarshalBinary (see curio tasks/message/sender_eth.go). Our synthetic test tx round-trips through ParseSignedEIP1559 fine, but the real one declined (parse/convert failed, or hex decode returned derr != nil and skipped the block silently).

Suspected mismatch: access-list encoding (EIP-2930 list present/empty), v/r/s signature normalization, or chainID placement in our RLP decoder vs go-ethereum's canonical 0x02 || rlp([...]) envelope.

To do:

  • Add per-branch decline logging at the top of EthSendRawTransaction (distinct lines for: hex-decode fail, Mpool == nil, ParseSignedEIP1559 err, ToSignedFilecoinMessage err) so the next bridge-off window pinpoints the exact branch.
  • Capture one real SP raw-tx hex (bridge-on, log the raw bytes in SenderETH or sniff the bridge call) and round-trip it offline through ParseSignedEIP1559 → fix the decoder to accept it.
  • Add a regression test in chain/ethtx using a real captured curio DynamicFeeTx payload.

Item 2 — eth_maxPriorityFeePerGas is an UNCOVERED write-path bridge dependency

The SP's full pre-send chain (curio sender_eth.go):

EstimateGas        -> eth_estimateGas            (#45 Stage 2, LOCAL ✅)
HeaderByNumber(nil) -> eth_getBlockByNumber latest (base fee; should be LOCAL)
SuggestGasTipCap   -> eth_maxPriorityFeePerGas   (STILL BRIDGE-ONLY ❌)
NetworkID          -> eth_chainId / net_version  (LOCAL ✅)
SendTransaction    -> eth_sendRawTransaction     (#45 Stage 4, see Item 1)

SuggestGasTipCap → eth_maxPriorityFeePerGas is still bridge-only. This means Stage 6 is not lower-priority — it is directly on the write path and must be local before any bridge-off succeeds, even after Item 1 is fixed.

To do:

  • Implement local eth_maxPriorityFeePerGas (conservative tip estimate from recent tipset base-fee trend, or a safe floor). Over-estimate is acceptable; under-estimate is the hazard, same invariant as estimateGas.
  • Confirm eth_getBlockByNumber("latest") returns a non-nil baseFee locally (the SP needs header.BaseFee); add to the bridge-off coverage checklist.

Acceptance / re-test gate

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions