I was running the mainnet test for Winnow, my Taproot-only iOS wallet that learns about the chain from ordinary Bitcoin peers over compact filters. No server, no account. Fresh wallet, receive a few thousand sats, watch it confirm, send it back. Simple.
It never got to the receive step. The filter scan stopped at block 965,021 of 965,521, five hundred short of the tip, with three peers connected and a red line saying "The connection to the peer was not open." Ten minutes later it was still sitting there.
The peer list explained it.
| Peer | Software | Reported height | Fee filter |
|---|---|---|---|
| 142.114.30.4 | Core 31.1 | 965,521 | 0.1 sat/vB |
| 85.92.61.97 | Knots 29.2 (Nov 2025) | 965,521 | 1 sat/vB |
| 108.233.254.177 | Knots 29.4 (May 2026) | 961,639 | 9,171 sat/vB |
The third peer was 3,882 blocks behind. Block 961,632 is where the BIP-110 chain split off on August 8. The minority chain produced a handful of blocks and stopped. That node had been sitting at 961,639 ever since, with a mempool full of transactions that will never confirm, which is exactly what a 9,171 sat/vB fee filter means.
Ask a node like that for filter checkpoints at your tip, a block hash it has never seen, and it hangs up on you. That is what Bitcoin Core does with an unknown stop hash. My checkpoint loop asked every connected peer so it could compare their answers and drop liars, and it treated the hang-up as a failed sync. Every pass. The design that protects against a lying peer had turned a mute one into a veto, while two honest peers at the tip sat idle next to it.
Winnow itself was never on the wrong chain. It validates proof of work on every header, its tip matched an independent node, and the dead fork has no work past the split. This was slot hygiene and failover, and the fix is small: unseat any peer reporting a tip more than a hundred blocks behind the best connected one, fail over when a peer hangs up during the checkpoint exchange, and never ship a behind-the-tip node in the compiled-in fallback list. All of it keys on what a node reports, never on which software it runs.
Why nobody else noticed
Full nodes have not cared since 2017. Bitcoin Core evicts an outbound peer whose chain lags for twenty minutes. Wallets that talk to an Electrum or Esplora server never meet a peer at all. The only clients that can hit this are the ones that read the chain from peers directly over compact filters: lnd with Neutrino, the mobile nodes built on it, BDK's Kyoto, and mine. And when it bites, it looks like "sync is stuck, restart the app." The restart redials, the pool seats different peers, and nobody connects it to a fork that died in August.
So how common is it? I built a census tool to find out.
The census
WinnowCensus dials a list of nodes with the same connection code the wallet uses, records what each one says in its handshake, waits a moment for the fee filter Core pushes right after, and disconnects. Nothing is requested. Using the wallet's own connection is the point: a peer that fails this handshake is a peer Winnow could never seat, so the numbers describe the wallet's world rather than the abstract network.
The node list came from the btcnodes.io snapshot: 26,746 reachable endpoints on September 4. Clearnet addresses I dialled directly. Tor and I2P addresses, which turn out to be most of the list, went through a local Tor and a local i2pd router over a SOCKS5 option I added to the connection for the purpose. All 26,746, in about ninety minutes.
| Network | Dialled | Usable | No compact filters | Stuck at the split, of usable |
|---|---|---|---|---|
| Clearnet | 9,612 | 1,283 (13%) | 72% | 82 (6.4%) |
| Tor | 12,984 | 3,776 (29%) | 60% | 306 (8.1%) |
| I2P | 4,150 | 2,702 (65%) | 33% | 231 (8.5%) |
| All | 26,746 | 7,761 (29%) | 60% | 619 (8.0%) |
Three things fall out of that table.
The compact-filter network is mostly hidden. Only one clearnet endpoint in eight serves filters. On Tor it is one in three, and on I2P two in three. Of the 7,761 usable endpoints, 6,478 are behind Tor or I2P.
Endpoints, not nodes. One Core node listens on clearnet, Tor and I2P at once with a line of configuration each, and Core deliberately makes linking a node's addresses across networks hard, so the census cannot say how many of those endpoints are doors into the same node. Distinct usable nodes number somewhere between 1,283 and 7,761. What holds either way is that a phone wallet without a Tor transport cannot reach any node that is Tor-only or I2P-only, and among operators who run I2P at all, running it alone is common. That reframed my "add a proxy setting someday" item into the single biggest reachability lever the wallet has.
One usable endpoint in twelve is on the dead chain. 619 of 7,761 report the BIP-110 split height, and the share is nearly the same on all three networks, which is what you would expect if much of the same population answers on each. With three seats and random selection, a fresh wallet has roughly a one-in-four chance of seating one. My fallback list, generated two weeks after the split with no height check, had two in forty-two.
There is a third population. 153 usable endpoints report heights around 967,723, about 2,200 blocks above Bitcoin's tip. Every one of them is a Knots 29.4.1 build from May 8, and the height matches the BLAKE2b fork chain that launched on September 1 with a different proof of work. A wallet that seats one of these gets headers that fail proof-of-work validation, and Winnow already evicts a peer for that. But it is a reminder that "height" in a handshake is a claim, not a fact, which is why the census judges peers against the median reported height and the wallet validates every header itself.
Of the usable endpoints, by software:
| Family | Usable | At the tip | Stuck at the split | Median fee filter |
|---|---|---|---|---|
| Core | 6,077 | 99.9% | 0.0% | 0.1 sat/vB |
| Knots | 1,661 | 62.4% | 37.2% | 1.0 sat/vB |
| btcd | 16 | 81.2% | 0.0% | — |
And the Knots detail is worth a look:
| Knots build | Usable | Stuck at the split |
|---|---|---|
| 20251110 | 89 | 6% |
| 20260210 | 124 | 10% |
| 20260507 | 538 | 0.2% |
| 20260508 | 752 | 79.5% |
Two builds one day apart. The May 7 build is on the majority chain. The May 8 build, which enforces BIP-110, is four in five stuck, and the other one in five is mostly the BLAKE2b population above rather than nodes that resynced. Four weeks on, most of those operators are running a node that has not seen a Bitcoin block since August 8 and is still answering handshakes as if nothing happened.
What I changed
- Peers are judged by the height they report. A peer more than a hundred blocks behind the best connected peer is unseated, dropped from the remembered-peers file, and cooled off. A hundred blocks is about sixteen hours, the wallet's own reorg horizon. Peers the user typed in by hand are exempt; their node, their choice.
- A hang-up is a transport fault, not a verdict. The checkpoint loop now cools that peer off and asks the others, the same distinction the header sync already drew.
- The fallback list generator refuses candidates behind the median tip, and prints each one it drops into the release log.
- No announcements to peers whose fee filter refuses the transaction. Harmless, but it was earning those peers a mark that hid the real reason.
None of it looks at the user-agent string. A stuck Core node and a stuck Knots node get the same treatment, and a Knots node at the tip is as good a peer as any.
Reproduce it
The tool and the tables script are in the Winnow repository, and the SOCKS5 option that reached the hidden networks is in the wallet's own connection layer now. A scheduled job runs the census daily and publishes the aggregates, per network, with the per-node detail kept as a two-week artifact, so the numbers above can be checked rather than believed, and watched as the stuck nodes upgrade or don't.
swift run WinnowCensus --input snapshot.json --tor-socks 127.0.0.1:9050 --i2p-socks 127.0.0.1:4447 \
--tip 965522 --out census.jsonl --summary-json summary.json
scripts/census-tables census.jsonl
Sources: btcnodes.io snapshot of 2026-09-04; Bitcoin Core net_processing.cpp, stale-tip eviction; coverage of the BIP-110 split by CoinDesk and Crypto Briefing.
