fix(#123): close all ten devnet RPC parity gaps against local Curio devnet#125
Conversation
Live-smoke verification against running docker devnetBooted a fresh devnet-init discoveryRPC parity (6 fixes verified live)
Boot log includes: Partial (see finding #7)
Follow-ups (out of scope, worth their own issue/PR)
|
…evnet Every change is gated on network == "devnet"; mainnet + calibration paths are byte-identical to pre-change behaviour. - eth_chainId + net_version return the devnet's actual EIP-155 chainId instead of the mainnet fallback 0x13a / "314". devnet-init reads eth_chainId from the running devnet lotus and persists it into devnet-config.json as ethChainID. This is the P0 finding: an EVM client signing with the wrong chainId gets "invalid signature" back from lotus and takes hours to diagnose. - Filecoin.Version.BlockDelay returns the devnet's actual cadence instead of hardcoded 30. devnet-init reads BlockDelay from Filecoin.Version and persists as blockDelaySecs. - Filecoin.StateNetworkName returns the devnet's localnet-uuid instead of the literal string "devnet". Handler now reads from the persisted devnet-config instead of a literal. Fixes gossipsub topic identity (/fil/blocks/<name>) and DHT protocol prefix alignment. - Filecoin.ChainGetGenesis on devnet returns the persisted genesis CID instead of "not implemented". Compile-time genesis constants exist only for mainnet + calibration; devnet's genesis is per-boot. - Filecoin.MpoolGetConfig returns a stable snapshot instead of HTTP 500. Values match lotus defaults so consumers (curio at boot) don't have to special-case Lantern. PriorityAddrs nil (marshals to JSON null, matching lotus's wire shape byte-for-byte). - Filecoin.MpoolPushMessage now works on single-node devnet. The gossipsub mesh can't form on a single-node docker devnet, so the standard mpool.Pool had nothing to publish onto. Daemon now wires a Pool whose Config.Sink POSTs directly to the devnet lotus via Filecoin.MpoolPush. All other Pool semantics (persist journal, pending set, nonce derivation, #47 reconcile/rebroadcast, #121 restart-persist) work identically; only the wire transport changes. Trust posture unchanged from #122: devnet is single-source by design (operator owns both the lotus and the Lantern client). - Devnet mode auto-wires the devnet lotus as VMBridge when the operator did not pass --vm-bridge-rpc. Fixes eth_getCode / eth_call / eth_getStorageAt timeouts on cold state (single-node devnet has no libp2p, so bitswap can't fetch state blocks; daemon fell through to errBridgeUnconfigured). - mpool.Config.Sink hook lets Pool run with a nil pubsub instance. When Sink is non-nil and ps is nil, New() skips the gossipsub join+subscribe entirely and Publish/Rebroadcast route through the sink. Only used by devnet mode; mainnet/calibration paths continue to require a pubsub instance. - net/glif.Client.EthChainID, .BlockDelaySecs, .MpoolPush — three new methods on the shared JSON-RPC client. Used by devnet-init (boot-time discovery) and by the daemon (devnet mpool sink). - build.DevnetConfig gained EthChainID + BlockDelaySecs fields. Older configs written before this change have zero values; handlers hint `lantern devnet-init --force` to re-capture from the running lotus. - 5 new unit tests in net/mpool/sink_test.go cover the nil-pubsub + Sink path: nil-pubsub requires Sink; Publish invokes Sink instead of gossipsub byte-identically; persist journal entry matches published raw; Close is a no-op on nil topic/subscription; sink error surfaces from Publish without recording pending. - Existing cmd/lantern devnet-init tests updated: fake lotus stub now answers eth_chainId + Filecoin.Version so the boot-time discovery succeeds; new asserts check EthChainID = 31415926 and BlockDelaySecs = 4 land in the persisted config. - Full suite green: go test ./... reports 51 packages ok, no failures. - ChainHead head-poll lag on devnet (#123 findings 8+9) and eth_getCode proxy-address slowness (finding 7). Both are latency issues, not correctness. Folding into a separate --devnet-head-poll-interval knob PR after this batch lands.
…ub mpool wiring on devnet Two follow-up fixes surfaced by a live smoke against a running docker devnet (curio-fork make docker/devnet, lotus v1.35.1, BlockDelay=4): 1. cmd/lantern/main.go standalone daemon didn't share the pkg/daemon VMBridge auto-wire I added in the previous commit. When `lantern daemon --network devnet` runs without an explicit --vm-bridge-rpc, any eth_call / eth_getCode / eth_getStorageAt returned "FEVM method requires --vm-bridge-rpc" instead of transparently using the devnet's own lotus. Now the CLI applies the same `--network devnet && !--vm-bridge-rpc → devnetCfg.LotusRPC` fallback. Verified live: after the fix, eth_getCode against the deployed PDPVerifier reaches the bridge (result is served instead of erroring). 2. pkg/daemon/start.go's startGossipHead was joining the gossipsub mempool topic on devnet EVEN THOUGH there's no gossipsub mesh on a single-node devnet. Once that mpool was in place, my devnet lotus-RPC sink block (which triggers only when chainAPI.Mpool == nil) never fired, so MpoolPushMessage silently dropped signed messages into the void. Fix: startGossipHead now short-circuits before the gossipsub mpool wiring on devnet, logs the reason, and leaves chainAPI.Mpool nil so the devnet sink block in startInternal activates. Bitswap wiring after the mpool block is preserved via a goto over a scoped variable-declaration block (Go-legal, verified with go build + go vet). Live-verified end-to-end on the docker devnet: Filecoin.StateNetworkName → "localnet-1069c685-..." (was "devnet") eth_chainId → 0x1df5e76 (31415926) (was 0x13a) net_version → "31415926" (was "314") Filecoin.Version.BlockDelay→ 4 (was 30) Filecoin.ChainGetGenesis → real CID (was not-implemented) Filecoin.MpoolGetConfig → lotus-shape defaults (was HTTP 500) vm-bridge auto-wired to devnet lotus (bridge log line + eth_getCode reaches bridge) eth_getCode is still slow on cold state (local hamt walk tries bitswap first, and single-node devnet has no bitswap peers, so the walk waits the full 15s timeout before falling through to the bridge). That's #123 finding #7 in a narrower form. Fix candidate: short-circuit the local-first order to bridge-first on devnet. Out of scope for this batch; filing a follow-up. Tests: full suite (`go test ./...`) 51/51 packages ok.
dca3034 to
2eceb8f
Compare
#127) Closes lantern#123 findings 8+9. On a single-node docker devnet the gossipsub mesh can't form, so ChainHead + eth_blockNumber arrive ONLY via the RPC-fallback poll. The mainnet-oriented 30s cadence set by startGossipHead (and its cmd/lantern twin) is 7.5x block-time on a 4s-epoch devnet, so head trails live by ~80 epochs = ~320s at all times \u2014 breaks any timing-sensitive consumer (curio schedulers, proving-window logic, latency-sensitive read-your-write patterns). On devnet, the daemon now derives the effective poll interval from build.GetDevnetConfig().BlockDelaySecs (captured at devnet-init time by #125), falling back to 4s if the config is absent or older than #125. Operators can override with --devnet-head-poll-interval (CLI) or Config.DevnetHeadPollInterval (embedded). Mainnet + calibration paths are untouched: the gossip-primary logic (30s catch-up cadence when libp2p is enabled) still runs; only when network == build.Devnet does the derivation fire. Applied to both consumer paths: - cmd/lantern/main.go (standalone `lantern daemon` CLI) - pkg/daemon/start.go (embedded consumers like curio-core) Live smoke, curio-fork docker devnet at height 17798+: Cold-boot head lag (default, sync every 4s): t=10s lotus=17798 lantern=17794 lag= 4 epochs t=30s lotus=17803 lantern=17796 lag= 7 epochs t=60s lotus=17810 lantern=17803 lag= 7 epochs With --devnet-head-poll-interval 2s (user override honored): Boot log: "header store: ... (sync every 2s, buf=64)" Pre-fix baseline (from #123 finding 8+9): ~80 epochs sustained. Full suite: go test ./... reports 52 packages ok, no failures. Co-authored-by: Nicklas Reiersen <nicklas@reiers.io>
Closes #123.
Every change is gated on
network == "devnet"; mainnet + calibration paths are byte-identical to pre-change behaviour.What changes (per finding on #123)
P0 correctness (breaks EVM clients silently):
eth_chainId+net_versionreturn the devnet's actual EIP-155 chainId (curio-fork docker:31415926/0x1df5e76) instead of the mainnet fallback0x13a/"314".devnet-initreadseth_chainIdfrom the running lotus and persists it intodevnet-config.jsonasethChainID.net_versionalready delegates toEthChainIdinsiderpc/server/eth_api.go, so both are fixed by the single handler change.P1 correctness (breaks gossipsub identity + curio schedulers):
Filecoin.StateNetworkNamereturns the devnet's localnet-uuid instead of the literal string"devnet". Handler now reads frombuild.GetDevnetConfig()instead of returning a literal. Aligns/fil/blocks/<name>gossipsub topic + DHT protocol prefix with lotus.Filecoin.Version.BlockDelayreturns the devnet's actual cadence (curio-fork:4) instead of hardcoded30.devnet-initreadsBlockDelayfromFilecoin.Versionand persists asblockDelaySecs.Filecoin.ChainGetGenesisreturns the persisted genesis CID instead ofnot implemented. Compile-time genesis constants exist only for mainnet + calibration; devnet's genesis is per-boot.P2 correctness (unblocks send-path + peace-of-mind restart tests):
Filecoin.MpoolPushMessagenow works on single-node devnet. The gossipsub mesh can't form on a single-node docker devnet, so the standardmpool.Poolhad nothing to publish onto. Daemon now wires a Pool whoseConfig.SinkPOSTs directly to the devnet lotus viaFilecoin.MpoolPush. All other Pool semantics (persist journal, pending set, nonce derivation, #47 reconcile/rebroadcast, #121 restart-persist) work identically; only the wire transport changes. Unlocks the entire peace-of-mind restart drill on devnet, which is our fastest iteration loop.eth_getCode/eth_call/eth_getStorageAtcold-state timeouts. Devnet mode auto-wires the devnet lotus as the VMBridge when the operator didn't pass--vm-bridge-rpc. A single-node devnet has no libp2p, so bitswap can't fetch state blocks; the daemon fell through toerrBridgeUnconfigured.Filecoin.MpoolGetConfigreturns a stable snapshot instead of HTTP 500. Values chosen to match lotus defaults.PriorityAddrsisnil(marshals to JSONnull, matching lotus's wire shape byte-for-byte); an empty slice would marshal as[]and break consumers that check== null.Trust posture unchanged from #122: devnet is single-source by design (operator owns both the devnet lotus and the Lantern client).
New surfaces
mpool.Config.Sinkhook lets Pool run with a nil pubsub instance. WhenSinkis non-nil andpsis nil,New()skips the gossipsub join+subscribe entirely and Publish/Rebroadcast route through the sink. Only used by devnet mode; mainnet/calibration paths continue to require a pubsub instance and reject nil-pubsub construction.net/glif.Client.EthChainID,.BlockDelaySecs,.MpoolPush— three new methods on the shared JSON-RPC client. Used bydevnet-init(boot-time discovery) and by the daemon (devnet mpool sink).build.DevnetConfiggainedEthChainID+BlockDelaySecsfields. Older configs written before this change have zero values; handlers hintlantern devnet-init --forceto re-capture from the running lotus.Tests
net/mpool/sink_test.gocover the nil-pubsub + Sink path: nil-pubsub requires Sink; Publish invokes Sink instead of gossipsub byte-identically; persist journal entry matches published raw; Close is a no-op on nil topic/subscription; sink error surfaces from Publish without recording pending.cmd/lanterndevnet-init tests updated: fake lotus stub now answerseth_chainId+Filecoin.Versionso the boot-time discovery succeeds; new asserts checkEthChainID = 31415926+BlockDelaySecs = 4land in the persisted config; discovery-call floor raised from 3 to 5.go test ./...reports 51 packages ok, no failures.Not in this PR
ChainHeadhead-poll lag on devnet. Same root cause: single-node devnet can't form a gossipsub mesh so head updates come only from the fallback RPC poll (slow). Folding into a separate--devnet-head-poll-intervalknob PR after this batch lands.eth_getCodeproxy-address slowness on a specific PDPVerifier proxy address. The auto-wired VMBridge fix should already resolve this in practice (bridges through lotus, not empty local state), but keeping the finding open until a live smoke confirms.Follow-up
Live devnet smoke deferred (colima needs restart + fresh docker compose up). Unit tests cover the parse + persist + handler-read paths end-to-end via an in-process fake lotus, but the truest test is against a running
make docker/devnetnode. I'll capture that in a follow-up comment on #123 once colima is back up.