Skip to content

fix(network): reject outbound connections to non-serving peers#11071

Open
upbqdn wants to merge 3 commits into
mainfrom
fix-outbound-nonserving-peers
Open

fix(network): reject outbound connections to non-serving peers#11071
upbqdn wants to merge 3 commits into
mainfrom
fix-outbound-nonserving-peers

Conversation

@upbqdn

@upbqdn upbqdn commented Jul 23, 2026

Copy link
Copy Markdown
Member

Motivation

Closes #11061.

Solution

While the node is away from the network tip, reject outbound handshakes whose version message
doesn't advertise NODE_NETWORK (new HandshakeError::MissingRequiredServices, mirroring the
obsolete-version rejection). This keeps a fresh sync's outbound slots free for block-serving
peers, matching Bitcoin Core's policy of requiring block-serving peers during initial block
download.

At or near the tip the requirement is dropped, so Zebra keeps peering with non-serving peers —
the services=0 listeners on mainnet are pruned Zakura nodes that serve recent blocks well
(review feedback below). For the same reason, the rejection records a plain retryable failure
rather than the peer's advertised services: stored non-serving services would exclude the peer
from outbound connections permanently, so it could never be dialed once the node reaches the
tip. Inbound and isolated connections are always exempt, so light clients can still connect.

Tests

  • Handshake vectors: reject/accept matrix over connection direction and sync state (outbound
    direct and proxy rejected without NODE_NETWORK while syncing; accepted at the network tip;
    inbound, isolated, and serving outbound always accepted), plus an end-to-end Handshake
    service test asserting the rejected peer is not recorded with poisoning services.
  • Full local CI gate (fmt, clippy -D warnings, check --locked, docs, feature powerset,
    nextest ci profile, udeps, deny, MSRV build).

Follow-up Work

  • Service-aware request routing in the PeerSet (route block requests only to NODE_NETWORK
    peers), as discussed in the review thread — useful on top of this gate, but insufficient
    alone, because non-serving peers would still occupy outbound slots during a sync.
  • Longer-term: a protocol-level way for pruned nodes to advertise the block range they serve
    (like Bitcoin's NODE_NETWORK_LIMITED, or the served-range advertising in Zakura's
    experimental v2 stack), which needs a ZIP.

AI Disclosure

PR Checklist

  • The PR title follows conventional commits format: type(scope): description
  • The PR follows the contribution guidelines.
  • This change was discussed in an issue or with the team beforehand.
  • The solution is tested.
  • The documentation and changelogs are up to date.

A large share of reachable mainnet listeners currently advertise no services, and Zebra
kept them in outbound slots and routed block requests to them, which can stall a fresh
sync at genesis. Reject outbound handshakes without NODE_NETWORK and record the peer's
advertised services in the address book, so the crawler stops re-dialing and gossiping it.
Inbound and isolated connections are unaffected.
@ValarDragon

Copy link
Copy Markdown
Contributor

So these are coming from communicating to Zakura pruned nodes on the network, as the v1 p2p stack does not yet enable communicating which block heights you know to peers.

The impact of rejecting connections to them would be effectively kicking them off the p2p network, when they are very capable (if not more capable) of communicating recent blocks faster (they spend less delays in DB time :) )

We are aware of exchanges using pruned functionality.

Are there other ways we can achieve the desired goal? (Perhaps setting a cap on number of pruned peers you talk to, or rejecting connecting to these during checkpoint sync?)

@conradoplg

Copy link
Copy Markdown
Collaborator

The impact of rejecting connections to them would be effectively kicking them off the p2p network, when they are very capable (if not more capable) of communicating recent blocks faster (they spend less delays in DB time :) )

I believe they would still be able to connect to Zebra nodes, it's just that Zebra nodes won't connect to them. So they won't be kicked out off the p2p network.

But we're looking into if the behaviour can be improved.

…cing

Review feedback: the services=0 peers are pruned nodes that can serve recent blocks, so an
unconditional rejection would stop Zebra from ever peering with them. Gate the rejection on the
node being away from the network tip (mirroring Bitcoin Core's IBD service requirements), and
stop recording the peer's advertised services on rejection - stored non-serving services
exclude a peer from outbound connections permanently, which would defeat the gate. Rejected
peers now get the standard retryable failure from the crawler instead.
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.

Outbound slots fill with non-serving (services=0) peers, stalling fresh mainnet sync at Height(0)

3 participants