← index

Address Relay under stress

An archive of bnoc.xyz · view original topic →

· Martin Zumsande · #1 ·

This is joint work with @stratospher.

From April 2026 to July 2026, there was a period of increased address relay caused by some entity spamming the network. In this post, we will discuss what consequences it had on the network, together with some ideas to improve the resilience of address relay against such periods of spam. See this post for a more detailed analysis of the data.

The spam

As can be seen from the KIT monitoring website (“Observed Addresses”), the number of addresses being relayed increased by a factor of 10, until it got back to its previous level. Most of these addresses were random IPv4, not belonging to any real bitcoin node. A similar period of spam already happened in 2021. The rate limiting of addresses that was implemented as a response in 2021 reduces the amount of spam that is possible, but it cannot prevent the spam entirely.

Consequences for the network

On a high level, we believe that the following happened during the spam period:

In summary, even though there are no immediate major disturbances, the spam has a negative long-term impact on various important p2p mechanisms, especially if it is sustained for an extended period.

Mitigation ideas

Since address relay, unlike transaction or block relay, does not come with a significant intrinsic cost, it is hard to prevent the spam itself. So the goal should be to improve things so that even in periods of spam, address relay and acceptance of legitimate addresses is effective.

1. Relax IsTerrible rules

A candidate for the new table only replaces an existing entry in its slot if that entry is deemed Terrible. Whether an address is Terrible is decided by various criteria, among them that we failed 3 times to connect to an address that we never connected to before.

We suggest reducing this number from 3 to 1. With a full new table, a count of 3 retries is effectively unreachable, since there are ~65k other candidates.

With that change, non-reachable addresses would be replaceable faster. Since Terrible also excludes nodes from being part of GETADDR answers, we would also send less low-quality addres that way.
This mitigation measure would be simple to implement, but on its own it likely won’t have a big impact since we don’t make enough new connections that bad addresses would become terrible fast.

2. Increase the feeler rate

We currently make a feeler connection every 2 minutes. This results in 720 attempts per day, compared to a maximum of 65536 possible entries in the New table, so that it would take >90 days to probe every address. Raising the rate, to e.g. once every 30s could be helpful, and comes at a negligible cost, at least for clearnet addresses. Moreover, it could make sense to couple the rate to the network of the address since I2P/Tor connections are more expensive.

3. Relay some quality addresses with priority

We could consider filling the first N slots of a GetAddr response with addresses from Tried. The receiving node could then treat these addresses with a higher priority, i.e. always accept them to AddrMan in case of a collision, and possibly schedule them for a feeler connection with a higher priority.

Because we only send GetAddr to peers we connect to, a spammer cannot easily influence these preferred addresses. However, this idea still has to be evaluated very carefully because it has a downside: While it helps against spam attacks, it could also help an eclipse attacker (i.e. someone running a number of nodes with the goal of making the victim pick all of their outbound connections to them): Especially with a larger N, there could be an amplifying effect in which a new node would quickly fill a large percentage of its Tried table with attacker-controlled nodes. However, choosing a small N, maybe just N=1, could be beneficial.

Revealing addresses to belong to the Tried table also has privacy implications, which are reduced by the fact that GetAddr answers are cached for 24h.

· Anthony Towns · #2 ·

What about something like “expire an entry from the new table if it’s older than a week, and the new table is at greater than 50% capacity” ? Or would that just encourage more address spam and ultimately reach the same state?

We could consider filling the first N slots of a GetAddr response with addresses from Tried.

Perhaps select a random subset (20%?) of the tried table to draw these addresses from, with the subset preserved across restarts?

· b10c · #3 ·

The cost might be negligible on the side that’s establishing these connections, but I fear that a 4x inbound rate to listening nodes (from a diverse set of inbound netgroups) will evict far more connections that might become long-lived connections than currently are evicted. Each eviction will in-turn cause another outbound connection being made, which might again evict a connection…

Would a separate handling of feeler/crawler/bitnodes-uptime-check connections, that specifically opt-in to being short-lived and only for checking if there’s a node behind this IP, make sense? For these connections, the mutual agreement would be to close the connection immediately after the version handshake, so the receiving node wouldn’t need to evict a connection for a crawler/feeler. I guess the implementation of this is tricky, as you need to tell the node to not-evict someone very early on in the crawler/feeler-connection-lifecyle.

· Martin Zumsande · #4 · · in reply to #2

Assuming that with “expire”, you mean making Terrible, not outright delete. I think that tying it to the current capacity is an interesting idea - although 50%! The downside of relaxing the IsTerrible too much is that we might not have enough addresses to include in a GetAddr message (or we would need to have two different notions of Terrible for that).

Yes, that’s an interesting idea that could work! I think it that even a low number (e.g. N<5, and take only from 1 tried bucket), together with a preferred feeler scheduling, would have the effect that new nodes would be much less affected by spam.

I think it makes sense to view it such that already now 1 or maybe even 2 of our inbound slots are basically reserved for short-term connections such as feelers (or other software that does regular connection pings such as DNS seed crawlers etc.) and cannot host a long-term inbound peer anyway.

Given that a feeler connection will only be connected for a very short time period (we only send a VERSION, receive a VERSION and disconnect), I would not expect a higher feeler rate will have much of an effect.
In addition, because #28463 was merged, many nodes will get a lot of additional capacity for non-tx relaying connections which can also be used by feelers.