Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

authority-discovery: Populate DHT records with public listen addresses #6298

Merged
merged 28 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e42d31d
cli: Revert warning
lexnv Oct 30, 2024
fd68062
authority-discovery: Obtain global addresses from listen addresses
lexnv Oct 30, 2024
36a4a1e
authority-discovery: Chain an deduplicate addresses
lexnv Oct 30, 2024
f1c7ed6
authority-discovery: Refactor `address_without_p2p` into a dedicated fn
lexnv Oct 30, 2024
5ca1f8b
authority-discovery: Cap max addresses to publish
lexnv Oct 30, 2024
0b091ef
authority-discovery: Bump max cached addresses to 16
lexnv Oct 30, 2024
6bbfce0
Add prdoc
lexnv Oct 30, 2024
58a8432
authority-discovery: Warn in authority discovery about non public
lexnv Nov 1, 2024
9c0d40d
tests: Adress testing
lexnv Nov 1, 2024
0bb4526
authority-disovery: Replace validator with authority
lexnv Nov 1, 2024
9b34c65
prdoc: Add more details
lexnv Nov 1, 2024
f29ebcb
Merge remote-tracking branch 'origin/master' into lexnv/warn-dhtfix
lexnv Nov 1, 2024
1ce39e8
Update substrate/client/authority-discovery/src/worker.rs
lexnv Nov 1, 2024
d95fd51
Merge remote-tracking branch 'origin/master' into lexnv/warn-dhtfix
lexnv Nov 1, 2024
e6f329d
Fix clippy
lexnv Nov 1, 2024
c1c7739
Merge branch 'master' into lexnv/warn-dhtfix
lexnv Nov 4, 2024
7fca9ee
Update substrate/client/authority-discovery/src/worker.rs
lexnv Nov 4, 2024
cedf8fa
Update substrate/client/authority-discovery/src/worker.rs
lexnv Nov 4, 2024
7f6efce
Update substrate/client/authority-discovery/src/worker.rs
lexnv Nov 4, 2024
094b5f5
authority-discovery: Fix build
lexnv Nov 4, 2024
53a07d4
authority-discovery: Add docs for warn_public_addresses
lexnv Nov 4, 2024
9fb02e0
authority-discovery: Add AddressType wrapper for better logs
lexnv Nov 4, 2024
9fed62e
authority-discovery: Log error for all types on mismatched peerId
lexnv Nov 4, 2024
0a4e74c
Merge branch 'master' into lexnv/warn-dhtfix
lexnv Nov 4, 2024
eab484c
prdoc: Try to remove sc-cli for semvercheck
lexnv Nov 4, 2024
49f6d2e
primitives: Revert panicHook import to fix CI for now
lexnv Nov 4, 2024
e408fee
Merge branch 'master' into lexnv/warn-dhtfix
lexnv Nov 4, 2024
31b3595
Revert "primitives: Revert panicHook import to fix CI for now"
lexnv Nov 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions prdoc/pr_6298.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
title: Populate authority DHT records with public listen addresses

doc:
- audience: [ Node Dev, Node Operator ]
description: |
This PR populates the authority DHT records with public listen addresses if any.
The change effectively ensures that addresses are added to the DHT record in the
following order:
1. Public addresses provided by CLI `--public-addresses`
2. Maximum of 4 public (global) listen addresses (if any)
3. Any external addresses discovered from the network (ie from `/identify` protocol)

While at it, this PR adds the following constraints on the number of addresses:
- Total number of addresses cached is bounded at 16 (increased from 10).
- A maximum number of 32 addresses are published to DHT records (previously unbounded).
- A maximum of 4 global listen addresses are utilized.

This PR also removes the following warning:
`WARNING: No public address specified, validator node may not be reachable.`

crates:
- name: sc-cli
bump: patch
- name: sc-authority-discovery
bump: patch
120 changes: 73 additions & 47 deletions substrate/client/authority-discovery/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ pub mod tests;
const LOG_TARGET: &str = "sub-authority-discovery";

/// Maximum number of addresses cached per authority. Additional addresses are discarded.
const MAX_ADDRESSES_PER_AUTHORITY: usize = 10;
const MAX_ADDRESSES_PER_AUTHORITY: usize = 16;

/// Maximum number of global listen addresses published by the node.
const MAX_GLOBAL_LISTEN_ADDRESSES: usize = 4;

/// Maximum number of addresses to publish in a single record.
const MAX_ADDRESSES_TO_PUBLISH: usize = 32;

/// Maximum number of in-flight DHT lookups at any given point in time.
const MAX_IN_FLIGHT_LOOKUPS: usize = 8;
Expand Down Expand Up @@ -271,20 +277,7 @@ where
config
.public_addresses
.into_iter()
.map(|mut address| {
if let Some(multiaddr::Protocol::P2p(peer_id)) = address.iter().last() {
if peer_id != *local_peer_id.as_ref() {
error!(
target: LOG_TARGET,
"Discarding invalid local peer ID in public address {address}.",
);
}
// Always discard `/p2p/...` protocol for proper address comparison (local
// peer id will be added before publishing).
address.pop();
}
address
})
.map(|address| address_without_p2p(address, local_peer_id))
.collect()
};

Expand Down Expand Up @@ -376,44 +369,62 @@ where
fn addresses_to_publish(&self) -> impl Iterator<Item = Multiaddr> {
let local_peer_id = self.network.local_peer_id();
let publish_non_global_ips = self.publish_non_global_ips;
let addresses = self
.public_addresses
.clone()
.into_iter()
.chain(self.network.external_addresses().into_iter().filter_map(|mut address| {
// Make sure the reported external address does not contain `/p2p/...` protocol.
if let Some(multiaddr::Protocol::P2p(peer_id)) = address.iter().last() {
if peer_id != *local_peer_id.as_ref() {
error!(
target: LOG_TARGET,
"Network returned external address '{address}' with peer id \
not matching the local peer id '{local_peer_id}'.",
);
debug_assert!(false);
}
address.pop();
}

if self.public_addresses.contains(&address) {
// Already added above.
None
// Checks that the address is global.
let address_is_global = |address: &Multiaddr| {
address.iter().all(|protocol| match protocol {
// The `ip_network` library is used because its `is_global()` method is stable,
// while `is_global()` in the standard library currently isn't.
multiaddr::Protocol::Ip4(ip) if !IpNetwork::from(ip).is_global() => false,
multiaddr::Protocol::Ip6(ip) if !IpNetwork::from(ip).is_global() => false,
lexnv marked this conversation as resolved.
Show resolved Hide resolved
_ => true,
})
};

// These are the addresses the node is listening for incoming connections,
// as reported by installed protocols (tcp / websocket etc).
//
// We double check the address is global. In other words, we double check the node
// is not running behind a NAT.
// Note: we do this regardless of the `publish_non_global_ips` setting, since the
// node discovers many external addresses via the identify protocol.
let global_listen_addresses = self
.network
.listen_addresses()
.into_iter()
.filter_map(|address| {
if address_is_global(&address) {
Some(address_without_p2p(address, local_peer_id))
lexnv marked this conversation as resolved.
Show resolved Hide resolved
} else {
Some(address)
None
}
}))
.filter(move |address| {
})
.take(MAX_GLOBAL_LISTEN_ADDRESSES);

// Similar to listen addresses that takes into consideration `publish_non_global_ips`.
let external_addresses =
self.network.external_addresses().into_iter().filter_map(|address| {
let address = address_without_p2p(address, local_peer_id);
if publish_non_global_ips {
lexnv marked this conversation as resolved.
Show resolved Hide resolved
return true
Some(address)
} else if address_is_global(&address) {
Some(address)
} else {
None
}
});

address.iter().all(|protocol| match protocol {
// The `ip_network` library is used because its `is_global()` method is stable,
// while `is_global()` in the standard library currently isn't.
multiaddr::Protocol::Ip4(ip) if !IpNetwork::from(ip).is_global() => false,
multiaddr::Protocol::Ip6(ip) if !IpNetwork::from(ip).is_global() => false,
_ => true,
})
})
let mut seen_addresses = HashSet::new();

let addresses = self
.public_addresses
.clone()
.into_iter()
.chain(global_listen_addresses)
.chain(external_addresses)
// Deduplicate addresses.
.filter(|address| seen_addresses.insert(address.clone()))
.take(MAX_ADDRESSES_TO_PUBLISH)
.collect::<Vec<_>>();

if !addresses.is_empty() {
Expand Down Expand Up @@ -946,6 +957,21 @@ where
}
}

/// Removes the `/p2p/..` from the address if it is present.
fn address_without_p2p(mut address: Multiaddr, local_peer_id: PeerId) -> Multiaddr {
if let Some(multiaddr::Protocol::P2p(peer_id)) = address.iter().last() {
if peer_id != *local_peer_id.as_ref() {
error!(
Copy link
Member

Choose a reason for hiding this comment

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

Having some sort of "context" here to know from where the address is coming from would be nice. Also for things like external_addresses, we should not print anything at all? I mean these addresses are coming from other nodes, but we probably ensure before that these contain our peer id?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep that makes sense! Have added an AddressType enum for better logs 🙏

While at it, have kept the error for all possible address types (including external addresses), since we don't do any validation in the lower components (libp2p / litep2p).

I'll come with a followup, there are a few things I'd like to check:

  • move the external address error directly into libp2p / litep2p discovery mechanism
  • don't emit events for ExternalAddressConfirmed for addresses that have a different peer ID
  • libp2p looks like it adds /p2p/ regardless of the address already containing it

let new_addr = addr.clone().with(Protocol::P2p(self.local_peer_id));

target: LOG_TARGET,
"Network returned external address '{address}' with peer id \
not matching the local peer id '{local_peer_id}'.",
);
}
address.pop();
}
address
}

/// NetworkProvider provides [`Worker`] with all necessary hooks into the
/// underlying Substrate networking. Using this trait abstraction instead of
/// `sc_network::NetworkService` directly is necessary to unit test [`Worker`].
Expand Down
12 changes: 1 addition & 11 deletions substrate/client/cli/src/commands/run_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,7 @@ impl CliConfiguration for RunCmd {
}

fn network_params(&self) -> Option<&NetworkParams> {
let network_params = &self.network_params;
let is_authority = self.role(self.is_dev().ok()?).ok()?.is_authority();
if is_authority && network_params.public_addr.is_empty() {
eprintln!(
"WARNING: No public address specified, validator node may not be reachable.
Consider setting `--public-addr` to the public IP address of this node.
This will become a hard requirement in future versions."
);
Comment on lines -207 to -211
Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, I would keep this warning, because there is no guarantee we will have a global listen address nor that the external address will be discovered correctly (with libp2p address translation). Especially since there is a practice of "hiding" validators behind firewalls / NATs.

}

Some(network_params)
Some(&self.network_params)
}

fn keystore_params(&self) -> Option<&KeystoreParams> {
Expand Down
Loading