Skip to content

fix(das): prevent bridge DASer from receiving gossip headers before EDS is stored#4820

Open
walldiss wants to merge 3 commits intocelestiaorg:mainfrom
walldiss:fix/bridge-daser-fanout-only
Open

fix(das): prevent bridge DASer from receiving gossip headers before EDS is stored#4820
walldiss wants to merge 3 commits intocelestiaorg:mainfrom
walldiss:fix/bridge-daser-fanout-only

Conversation

@walldiss
Copy link
Member

@walldiss walldiss commented Mar 4, 2026

Summary

Fixes #4802

Bridge nodes subscribe to the p2p header gossip topic. Remote peers can gossip the same header before the local core.Listener finishes storing EDS, causing the DASer to look up a header it just received, call HasByHeight, fall through to GetEDS over shrex, and log a flood of WARN messages at every block height:

WARN	das	requesting data from peer failed	...

Fix: Add p2p.WithTopicOpts(pubsub.FanoutOnly()) to the bridge node's p2p.Subscriber. With FanoutOnly, the subscriber can still broadcast (gossip headers out), but it never receives incoming gossip messages. The DASer only processes headers that the local core.Listener produced, guaranteeing EDS is already in the store.

Implementation notes:

  • The fx.Decorate for the bridge's p2p.Subscriber wraps with fx.Annotate(..., fx.OnStart, fx.OnStop) — without explicit lifecycle hooks the decorated instance's Start() is never called, leaving s.topic = nil and causing a panic in Broadcast.
  • Bumps go-header to v0.8.3-rc which introduces p2p.WithTopicOpts.
  • Adds replace github.com/libp2p/go-libp2p-pubsub => github.com/celestiaorg/go-libp2p-pubsub required for pubsub.FanoutOnly() support.

closes https://linear.app/celestia/issue/DA-1072

@walldiss walldiss self-assigned this Mar 4, 2026
@walldiss walldiss added the kind:feat Attached to feature PRs label Mar 4, 2026
@walldiss walldiss force-pushed the fix/bridge-daser-fanout-only branch from 380e6fa to 0f1cba4 Compare March 4, 2026 18:22
…DS is stored

Add WithTopicOpts(pubsub.FanoutOnly()) to the bridge node's p2p header
subscriber so the bridge can broadcast headers but never receives incoming
gossip messages. This eliminates the race condition where a bridge node's
DASer would try to sample EDS for a gossiped header that the local
core.Listener hasn't finished storing yet, causing spurious "requesting
data from peer failed" WARN logs (issue celestiaorg#4802).

Bumps go-header to v0.8.3-rc which introduces p2p.WithTopicOpts, and
adds the celestiaorg/go-libp2p-pubsub replace directive required for
pubsub.FanoutOnly() support.

The fx.Decorate for the bridge's p2p.Subscriber uses fx.Annotate with
fx.OnStart/fx.OnStop to ensure Start() is called on the decorated instance;
without the lifecycle hooks s.topic remains nil and Broadcast panics.

Closes celestiaorg#4802
@walldiss walldiss force-pushed the fix/bridge-daser-fanout-only branch from 0f1cba4 to f1c205a Compare March 4, 2026 18:23
@walldiss walldiss requested a review from Wondertan March 4, 2026 18:26
Wondertan
Wondertan previously approved these changes Mar 4, 2026
Comment on lines +62 to +67
// Bridge nodes must not receive headers via p2p gossip: a remote peer
// can gossip the same header before the local core.Listener finishes
// storing EDS, causing the DASer to access EDS prematurely.
if tp == node.Bridge {
opts = append(opts, p2p.WithTopicOpts(pubsub.FanoutOnly()))
}
Copy link
Member

@Wondertan Wondertan Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would also extract that in a separate func that returns fx.Option and take node type, but non blocking

The celestiaorg/go-libp2p-pubsub fork (required for FanoutOnly support)
does not support subscription exchange over mocknet connections without
the companion go-libp2p fork (which has incompatible quic-go deps).
Replace mocknet with real libp2p hosts in the three affected tests so
the pubsub protocol handshake completes correctly.
@codecov-commenter
Copy link

codecov-commenter commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 13.33333% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 35.75%. Comparing base (2469e7a) to head (8f80914).
⚠️ Report is 673 commits behind head on main.

Files with missing lines Patch % Lines
nodebuilder/header/module.go 13.33% 12 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4820      +/-   ##
==========================================
- Coverage   44.83%   35.75%   -9.09%     
==========================================
  Files         265      311      +46     
  Lines       14620    21460    +6840     
==========================================
+ Hits         6555     7672    +1117     
- Misses       7313    12792    +5479     
- Partials      752      996     +244     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

kind:feat Attached to feature PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(das): bridge node DASer races p2p gossip against core listener EDS storage

3 participants