Skip to content

fix: several improvements to how AddrMan works#781

Merged
Davidson-Souza merged 13 commits into
getfloresta:masterfrom
Davidson-Souza:ask-peers
Feb 24, 2026
Merged

fix: several improvements to how AddrMan works#781
Davidson-Souza merged 13 commits into
getfloresta:masterfrom
Davidson-Souza:ask-peers

Conversation

@Davidson-Souza

@Davidson-Souza Davidson-Souza commented Jan 13, 2026

Copy link
Copy Markdown
Member

Description and Notes

Fixes #853

This PR improves our AddrMan, and also makes our node more aggressive on requesting for new addresses. With these changes:

  • We always ask for new addresses when we get a new connection, even with feeler peers
  • We now limit how many addresses can we have in total, preventing a potential memory exhaustion DoS
  • We also evict the peers that we didn't successfully connect in a while, to free up more space
  • Limit how many addresses we can receive/send in a single AddrV2 message
  • Add nodes added though the CLI to AddrMan
  • Added several tests for AddrMan
  • Don't store addresses that are unreachable from our node
  • Rate limit AddrV2 messages to one every ten seconds
  • Added addresses from DNS seeds to the good addresses
  • Fixed a small bug where connected addresses wouldn't be seen as good until they disconnect

@Davidson-Souza Davidson-Souza added bug Something isn't working code quality Generally improves code readability and maintainability labels Jan 13, 2026
@Davidson-Souza Davidson-Souza force-pushed the ask-peers branch 2 times, most recently from 673d7e1 to dc1cb67 Compare January 13, 2026 21:34
@Davidson-Souza Davidson-Souza marked this pull request as ready for review January 13, 2026 21:34
Comment thread crates/floresta-wire/src/p2p_wire/address_man.rs Outdated
Comment thread crates/floresta-wire/src/p2p_wire/peer.rs Outdated
Comment thread crates/floresta-wire/src/p2p_wire/peer.rs Outdated
@Davidson-Souza

Copy link
Copy Markdown
Member Author

Pushed 8427273:

  • Move the 1k addresses to a constant in node.rs
  • Don't keep addresses for networks we can't reach - Right now we only really do ip, other networks proper handling will come in a follow-up
  • Don't remove peers from the good addresses table too quickly
  • Add an extra address range as private, as it seems to not be checked by Rust's std lib

Comment thread crates/floresta-wire/src/p2p_wire/peer.rs
@brunoerg

Copy link
Copy Markdown
Contributor

Also, it would be great to implement a rate-limiting for addrv2 messages.

@erickcestari

Copy link
Copy Markdown
Contributor

Also, it would be great to implement a rate-limiting for addrv2 messages.

I Agree. Bitcoin Core has a strict rate limit of accepting only one address every ten seconds from a peer to avoid eclipsing.

https://github.com/bitcoin/bitcoin/blob/0f7d4ee4e8281ed141a6ebb7e0edee7b864e4dcf/src/net_processing.cpp#L191

@luisschwab

Copy link
Copy Markdown
Member

While you're at it, maybe also make NodeInterface::remove_peer() also disconnect the peer we remove from the AddrMan? I'm getting this error when attempting to disconnect from a peer on bonsai:

WARN floresta_wire::p2p_wire::node: PeerNotFoundAtAddress(178.250.189.42, 38333)
ERROR bdk_floresta: Failed to manually disconnect from peer 178.250.189.42:38333

It claims the peer is not found even though I'm literally connected to it.

@jaoleal jaoleal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Complementing with some suggestions

good_peers_by_service: HashMap::new(),
peers_by_service: HashMap::new(),
max_size: max_size.unwrap_or(MAX_ADDRESSES),
reachable_networks,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isnt interesting to have a default for this and it being the addresses we support ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Knowing this isn't really the address_man business, and wouldn't help much. So I would say no

Comment thread crates/floresta-wire/src/p2p_wire/address_man.rs Outdated
@Davidson-Souza

Copy link
Copy Markdown
Member Author

While you're at it, maybe also make NodeInterface::remove_peer() also disconnect the peer we remove from the AddrMan?

This method is used for the addnode RPC, and it shouldn't disconnect a peer. We need a new one for disconnectpeer

@Davidson-Souza Davidson-Souza force-pushed the ask-peers branch 3 times, most recently from 1703ed1 to 724469e Compare January 20, 2026 21:33
@Davidson-Souza

Copy link
Copy Markdown
Member Author

Pushed 724469e

  • Moved the methods about address reachability to LocalAddress. I think it makes more sense to have them there
  • Also made them public, so they can be used for testing
  • Added a per-peer rate-limit for AddrV2 messages, one every ten seconds
  • rebased

This was annoying to rebase

Comment thread crates/floresta-wire/src/p2p_wire/address_man.rs
@erickcestari

Copy link
Copy Markdown
Contributor

I had to simulate the behaviour of the Bitcoin Core isRoutable() function in Rust-Bitcoin for a target in bitcoinfuzz. You might find it useful to see the additional verification that it has, which might be missing in Floresta, to ensure that it behaves equally to Core and does not waste time connecting to a non-routeable address.

https://github.com/bitcoinfuzz/bitcoinfuzz/pull/400/changes#diff-97d1c6ff1c09f64634ec511f5134bf731ad57fd139ac86c539a93d0dab8d182bR237

@Davidson-Souza Davidson-Souza force-pushed the ask-peers branch 3 times, most recently from fadc867 to 3124891 Compare January 28, 2026 17:34
@Davidson-Souza

Copy link
Copy Markdown
Member Author

After a little 1v1 against CI, pushed 3124891

  • Refactored prune_address based on @moisesPompilio's suggestion
  • Improved our reachability tests based on @erickcestari's code
  • Unified most reachability tests in one place

The BIP demands nodes to ignore any message with more than 1_000
addresses. We currently may receive and send packages bigger than
that. This commit fixes it by enforcing this limit.
We don't currently push peers added through CLI to our AddrMan, therefore
we don't learn about them to use in the future. This commit fixes it,
by calling `addr_man.push` with any peer that have been added from
the CLI
Some networks might not be reachable for use, usually because
they require a specific proxy that we might not have. Therefore,
addresses for that net are always unreachable for us. This commit
adds a method to filter for unreachable addresses, so they are
never added to our address manager.
We try to keep a good view of the network, knowing which peers are
working a ready to connect. To accieve this, we keep track of the
lastest connections we've made, so we know what peers should be
online and responsive. However, after a certain time from the last
connection, we assume that the information we have is too old to
be counted. But our current code is too aggressive in what it
considers outdated, and will drop this info for peers we've connected
just a few minutes ago. This commit fixes this by preserving this
information for one day (24 hours) before considering it stale and
moving that peer to the unknown state.
The address manager implements some methods like `is_private` and `is_good_peer` to help
with peer selection. However, they are closer to the LocalAddress struct, so I've moved
them to LocalAddress and made them public.
To avoid being eclipsed with an address spam attack, we limit
the rate of addrv2 messages a peer can send us to one every
10 seconds.
DNS seeds are trusted to deliver recently seen peers, that are very
likely to still be alive. This commit adds those peers directly to
the "good" peers table, making it easier for our node to start up
peers
Before connecting to an address, we mark it as `Failed`, in case
we never hear from that peer again, and only after handshake we
mark as `Connected`. However, before this commit, we would remove
then from the `good_peers` bucket, and only re-add after disconnecting.

That meant we wouldn't send their addresses in `AddrV2` messages, and
they wouldn't count towards the `enough_peers` function. This commit
fixes this by adding `Connected` peers back to the good peers bucket
Before this commit we would add duplicate addresses to each one of
the per-service bucket. This commit checks whether the address already
existed there, and only adds if not

@JoseSK999 JoseSK999 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One last nit, if it makes sense to you

Comment on lines 130 to 153
impl FromStr for LocalAddress {
fn from_str(s: &str) -> Result<Self, Self::Err> {
LocalAddress::try_from(s)
}
type Err = std::net::AddrParseError;
}

// Note that, since we can't know the network we are operating in, this code
// can't know what's the default port. Therefore, it will only work if you give
// a SocketAddr, i.e. <IP:PORT>
impl TryFrom<&str> for LocalAddress {
fn try_from(value: &str) -> Result<Self, Self::Error> {
let split = value.split(':').collect::<Vec<_>>();
let address = split[0].parse::<Ipv4Addr>()?;
let port = if let Some(port) = split.get(1) {
port.parse().unwrap_or(8333)
} else {
8333
let address = value.parse::<SocketAddr>()?;
let ip = match address {
SocketAddr::V4(ipv4) => AddrV2::Ipv4(*ipv4.ip()),
SocketAddr::V6(ipv6) => AddrV2::Ipv6(*ipv6.ip()),
};

Ok(LocalAddress::new(
AddrV2::Ipv4(address),
ip,
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Optional nit: we are now repeating this same logic 9 times in the file. A helper would be good:

fn time_since_unix() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

@Davidson-Souza

Copy link
Copy Markdown
Member Author

Pushed db86233

  • Added Cjdns to the is_net_reachable match
  • Added NETWORK_LIMTED to dns seed addresses
  • implemented and used a time_since_unix method

@luisschwab

Copy link
Copy Markdown
Member

Missed linting

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
.unwrap_or_default()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Azure is so annoying

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Azure is so annoying

100%

This method returns the time elapsed since the unix epoch. This logic
is reused all over addr_man, so it is better to just have it somewhere
and use it.

@JoseSK999 JoseSK999 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ACK 21e316e

@moisesPompilio

Copy link
Copy Markdown
Collaborator

ACK 21e316e

@luisschwab luisschwab left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ACK 21e316e

@Davidson-Souza Davidson-Souza merged commit 9d575d3 into getfloresta:master Feb 24, 2026
11 checks passed
@luisschwab

Copy link
Copy Markdown
Member

BOOM

Davidson-Souza added a commit to Davidson-Souza/Floresta that referenced this pull request Mar 6, 2026
In getfloresta#793 we reduced the `DNS_SEED_REQUEST_INTERVAL` to two minutes.
This tells how long do we wait before re-doing a request do the DNS
seeds. The goal was to make sure we had enough peers to keep going.

However, it doesn't seem like it worked as intended since DNS servers
are currently bad at giving utreexo peers (and on signet they are also
bad at giving CBS peers). This forces our node to retry DNS requests
every two minutes. But since most users don't have their own recursive
resolver (I don't), the previous result is almost always cached, so you
get basically the same answer several times.

This pollutes stdout for no good reason. Since getfloresta#781 we now are super
aggressive about asking for addresses and checking whether they are
alive and learning about their capabilities and getfloresta#865 will make it
even better, since we can try several feeler connections at the same
time. So I think DNS seeds should be only used to get us some starting
peers, and then we build our local network view from the P2P net
ourselves.
Davidson-Souza added a commit to Davidson-Souza/Floresta that referenced this pull request Mar 6, 2026
In getfloresta#793 we reduced the `DNS_SEED_REQUEST_INTERVAL` to two minutes.
This tells how long do we wait before re-doing a request do the DNS
seeds. The goal was to make sure we had enough peers to keep going.

However, it doesn't seem like it worked as intended since DNS servers
are currently bad at giving utreexo peers (and on signet they are also
bad at giving CBS peers). This forces our node to retry DNS requests
every two minutes. But since most users don't have their own recursive
resolver (I don't), the previous result is almost always cached, so you
get basically the same answer several times.

This pollutes stdout for no good reason. Since getfloresta#781 we now are super
aggressive about asking for addresses and checking whether they are
alive and learning about their capabilities and getfloresta#865 will make it
even better, since we can try several feeler connections at the same
time. So I think DNS seeds should be only used to get us some starting
peers, and then we build our local network view from the P2P net
ourselves.
Davidson-Souza added a commit to Davidson-Souza/Floresta that referenced this pull request Mar 6, 2026
In getfloresta#793 we reduced the `DNS_SEED_REQUEST_INTERVAL` to two minutes.
This tells how long do we wait before re-doing a request do the DNS
seeds. The goal was to make sure we had enough peers to keep going.

However, it doesn't seem like it worked as intended since DNS servers
are currently bad at giving utreexo peers (and on signet they are also
bad at giving CBS peers). This forces our node to retry DNS requests
every two minutes. But since most users don't have their own recursive
resolver (I don't), the previous result is almost always cached, so you
get basically the same answer several times.

This pollutes stdout for no good reason. Since getfloresta#781 we now are super
aggressive about asking for addresses and checking whether they are
alive and learning about their capabilities and getfloresta#865 will make it
even better, since we can try several feeler connections at the same
time. So I think DNS seeds should be only used to get us some starting
peers, and then we build our local network view from the P2P net
ourselves.
Davidson-Souza added a commit to Davidson-Souza/Floresta that referenced this pull request Mar 9, 2026
In getfloresta#793 we reduced the `DNS_SEED_REQUEST_INTERVAL` to two minutes.
This tells how long do we wait before re-doing a request do the DNS
seeds. The goal was to make sure we had enough peers to keep going.

However, it doesn't seem like it worked as intended since DNS servers
are currently bad at giving utreexo peers (and on signet they are also
bad at giving CBS peers). This forces our node to retry DNS requests
every two minutes. But since most users don't have their own recursive
resolver (I don't), the previous result is almost always cached, so you
get basically the same answer several times.

This pollutes stdout for no good reason. Since getfloresta#781 we now are super
aggressive about asking for addresses and checking whether they are
alive and learning about their capabilities and getfloresta#865 will make it
even better, since we can try several feeler connections at the same
time. So I think DNS seeds should be only used to get us some starting
peers, and then we build our local network view from the P2P net
ourselves.
Davidson-Souza added a commit that referenced this pull request Mar 9, 2026
0ceeb59 chore(wire): increase the dns seed grace period time (Davidson Souza)

Pull request description:

  ### Description and Notes

  In #793 we reduced the `DNS_SEED_REQUEST_INTERVAL` to two minutes.
  This tells how long do we wait before re-doing a request do the DNS
  seeds. The goal was to make sure we had enough peers to keep going.

  However, it doesn't seem like it worked as intended since DNS servers
  are currently bad at giving utreexo peers (and on signet they are also
  bad at giving CBS peers). This forces our node to retry DNS requests
  every two minutes. But since most users don't have their own recursive
  resolver (I don't), the previous result is almost always cached, so you
  get basically the same answer several times.

  This pollutes stdout for no good reason. Since #781 we now are super
  aggressive about asking for addresses and checking whether they are
  alive and learning about their capabilities and #865 will make it
  even better, since we can try several feeler connections at the same
  time. So I think DNS seeds should be only used to get us some starting
  peers, and then we build our local network view from the P2P net
  ourselves.

ACKs for top commit:
  luisschwab:
    ACK 0ceeb59
  JoseSK999:
    ACK 0ceeb59

Tree-SHA512: a3529e147ca18ba18eab7b59918b17521a70addf9de7c37750612e58b6aea9f3173869672454126f516b2e38bd3d7f597d5e3731b850d2b86201982b8e14eae7
@csgui csgui added this to Floresta Mar 12, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in Floresta Mar 12, 2026
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Floresta Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working code quality Generally improves code readability and maintainability

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug] Duplicates on AddrMan

8 participants