v1.8.5: head catch-up + bridge-off proving + fork-choice & eclipse hardening (#50 #79 #80 #81 #82 #83)#84
Merged
Merged
Conversation
…(UN-DEPLOYED, needs supervised bridge-off verify) When eth_sendRawTransaction publishes a tx locally (#45 Stage 4), start a background StateSearchMsg poll for its message CID on a standalone, generous-deadline context. That background search drives the embedded Bitswap source to pull the freshly-produced message + AMT + receipt blocks into cache as soon as the tx lands. By the time the client's own short- deadline receipt poll runs, the blocks are warm and the receipt resolves locally instead of racing (and losing to) a cold cross-peer Bitswap fetch inside the poll window — the residual that kept #50 open. - new ChainAPI.OnSentTx hook, fired non-blocking after a successful local MpoolPush (mirrors the #44 OnLocalMiss adaptive-warming pattern) - pkg/daemon sendWarmer: per-tx background warm loop, dedup, concurrency cap (64), self-bounded (stop on found / 10m max), bound to daemon root ctx - purely additive + read-only: nil hook = unchanged behavior; never affects the send result; never publishes or mutates state - 5 unit tests (stop-on-found, dedup, concurrency cap, ctx-cancel, nil-safe), pass under -race; full pkg/daemon + rpc/handlers suites green NOT deployed to cc-smoke. Needs live bridge-off verification in a supervised daytime window before merge (the engine builds + unit-tests clean, but the real proof is a bridge-off write-confirm cycle showing zero 'net/bitswap: context canceled' receipt misses).
Formatting-only; no behavioral change. The branch CI failed on gofmt drift (struct field alignment in sendWarmer). No logic touched.
…hind tip The #71 gossip-fresh skip suspended the polling Sync's catch-up whenever gossip installed any block recently. A node whose gossip is fresh-but-lagging (installing some blocks while skipping head+N>1 blocks it can't backfill) would then have neither path drive to the tip: gossip skips the far blocks, Sync defers to gossip. Head wedges ~10-20 epochs behind (Beck, v1.8.4-m), which also fails PDP prove with 'cannot draw randomness from future epoch' when the prove randEpoch lands above the lagging head. Make the skip lag-aware: blockingest exposes ObservedHead() (highest installed epoch = gossip's view of the tip); Sync skips only when the store head is within SkipLagTolerance (default 2) of that observed tip, and runs the catch-up poll when fresh-but-lagging. Uses the gossip-observed tip, so no extra upstream HeadEpoch RPC - #71's 429-protection is preserved at the tip. Wired in both pkg/daemon and cmd/lantern. Two regression tests.
…dge-off) tipsetForRandomness compared randEpoch against the frozen boot anchor (c.Trusted.Epoch, never advances - see #48) instead of the live header-store head, so a PDP prove asking for randomness at its challenge epoch could trip 'cannot draw randomness from future epoch' even on a node that had reached that epoch. The documented fallback in StateGetRandomnessDigestFromBeacon was bridge-only, so bridge-off nodes (the stock-Curio target) just failed and burned the prove task's retry budget (Beck, v1.8.4-m, dataset 1294). Now the ceiling is the live head, and when randEpoch is within a small window above it (normal sync catch-up) we wait briefly (bounded 20s) for the header sync to reach it, then draw locally - no bridge needed. Genuinely-future epochs still error promptly. Pairs with #83 (which keeps the head at the tip in the first place).
The gossip head ingestor advanced head on height-fence + parent-linkage only, so an attacker who eclipses the peer table could feed parent-linked, height-advancing blocks on a valid-but-lighter fork and walk a node onto it (zenground0/FilOz, fil-curio-dev 2026-06-30). Content addressing doesn't catch it: the attacker's blocks hash fine, they're just not canonical. Apply Filecoin's actual fork-choice rule: adopt a candidate as head only when its ParentWeight strictly exceeds the current head's. A real descendant always has strictly greater weight; a competing lighter fork has lower/equal weight and is now rejected (counted as rejectedLighter for observability). Pure header arithmetic - no proof verification, no ffi, no one-binary impact. Raises the eclipse cost from 'spin up N sybil peers' to 'out-weight the real chain' (control real storage power). Does NOT fully close the unfinalized-tip split against an adversary with real power - that's finality (F3). Periodic live-head re-quorum (the other half of #79) remains a follow-up.
Section 2.1 claimed election-proof and weight were re-verified on every header back to genesis. That overstated the code: weight fork-choice only just landed (#79), and election-proof is presence-checked not VRF-verified (full proof verification needs filecoin-ffi, which the no-CGo design excludes). Rewrote to state precisely what IS verified (CID integrity, sig shape, parent linkage, heaviest-ParentWeight fork choice) vs what is NOT (election-proof VRF, winning-PoSt, message re-execution) and why, and to point at F3 + the boot quorum as what actually carries head trust.
Part 3 of #50. The embedded daemon hardcoded Glif as the polling Sync head source + cold-block fallback with no opt-out, so a bridge-off node silently fell back to Glif whenever gossip stalled - a hidden third-party dependency. - Config.FallbackRPC overrides the upstream RPC URL (e.g. operator's own Forest), defaulting to the network Glif URL (unchanged behavior). - Config.NoFallbackRPC removes the upstream RPC entirely: head comes only from gossipsub, cold blocks only from gateway+Bitswap. A gossip stall then surfaces as a stalled head (observable) instead of a silent Glif fetch. Refuses to start if libp2p is disabled (no head source at all). Wired so syncSrc/backfillSrc are truly-nil interfaces (not typed-nil) when NoFallbackRPC, and hstore.Sync.pollAndApply guards nil src as a no-op so gossip drives the head alone without nil-panicking. Test: nil-source Sync is a safe no-op leaving head intact. (Part 1 = PR #52 prefetch-on-send, merged. Part 2 = msgsearch bitswap-with-retry, already shipped in the #77 bridge-off parity work.)
Part 1 of #80 (eclipse hardening). Host.ProtectPeers marks the trusted bootstrap/beacon/direct-peer set as connmgr-protected so the trim path can never evict them. An attacker flooding the node with dials can no longer fully replace the peer table with peers it controls - the honest trusted floor always survives, so the node keeps hearing the heavier canonical chain. This is the eclipse-resistance complement to #79: #79 makes a lighter fork unadoptable; #80-part1 keeps honest peers in the table so the heavier chain stays reachable. Wired at host construction over GossipSubDirectPeers (falls back to BootstrapPeers). Test (non-short): a protected peer is reported connmgr-protected; garbage multiaddrs are skipped. Part 2 (head-source diversity: require >=N distinct scored peers before adopting a head advance) is deferred as a follow-up - it needs source-peer plumbing through blockpub->blockingest and interacts with the #79 fork choice; not worth rushing into this release.
…source) + #80 (peer floor)
5 tasks
This was referenced Jun 30, 2026
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.
v1.8.5 — head catch-up + bridge-off proving + fork-choice & eclipse hardening
Came out of Beck's v1.8.4-m test (PDP prove failing
cannot draw randomness from future epoch) and the zenground0/FilOz fil-curio-dev thread on head agreement / fork selection. Scope expanded to also close the parked bridge-off availability work (#50/#52) and the first eclipse-hardening piece (#80).Fixes
blockingest.ObservedHead(); no extra RPC, Standalone daemon polls Glif ChainHead every 6s even when gossipsub is live → 429 under stock Curio + PDP #71 429-protection preserved.Added
rejectedLighter). Pure header arithmetic, no ffi. Doesn't close the un-finalized-tip split against a real-power adversary — that's F3.connmgr-protected so a dial flood can't fully replace the peer table. Complements Running-head fork choice: heaviest-ParentWeight + periodic re-quorum (not boot-only) #79. Diversity quorum deferred.FallbackRPC/NoFallbackRPC— no more silent Glif fallback when gossip stalls). msgsearch bitswap-with-retry already shipped in feat: bridge-off RPC parity for stock Curio (maxboom / PR #1311) #77.Changed
Tests
-short,LANTERN_OFFLINE=1); build + vet + gofmt clean. (The one network test that needs :1234 collides with the local dev daemon only.)Closes #50, #79, #80 (part 1), #81, #82, #83. Supersedes PR #52 (merged in). Follow-ups: #80 head-source diversity quorum, #79 periodic re-quorum.