feat: add DNS resolution to TrustedPeer#566
Conversation
8c83bb2 to
5f7e263
Compare
db181cc to
282078f
Compare
|
Not super convinced of this one, as it adds some client-specific logic that others might want to implement differently (round robin). I think it is fair to have an |
282078f to
02004ac
Compare
|
Hi @rustaceanrob , thanks for taking a look. Let me just give you some context about our initial motivation. In our usecase, we connect to a bitcoind behind a Kubernetes service DNS name ( I think there are two ways to land that without baking any rotation policy into kyoto:
I'd personally prefer the first option because it keeps the "DNS re-resolves on reconnect" property without requiring a full node rebuild each time, but the second is strictly simpler and I'm happy to go that way if you prefer. |
|
The latter option is the most general for all users, so I will opt for that. Rather than creating a newtype for a peer from DNS, I am going to extend |
02004ac to
b0ce147
Compare
|
Thanks @rustaceanrob here's the new shape: pub enum PeerAddress {
Addr(AddrV2),
Hostname(String),
}
impl From<AddrV2> for PeerAddress { ... }
pub struct TrustedPeer {
pub address: PeerAddress, // was: AddrV2
pub port: Option<u16>,
pub known_services: ServiceFlags,
}
impl TrustedPeer {
pub fn from_hostname(hostname: impl Into<String>, port: u16) -> Self;
// existing constructors keep working — `new` now takes `impl Into<PeerAddress>`
}Could you please have another look. |
7b6c4bf to
f28526c
Compare
|
I made some adjustments:
I notice we only take the first address resolved from the hostname. Why not add all resolved peers to the whitelist? |
32e2130 to
7470c44
Compare
7470c44 to
2699acd
Compare
Introduce an enum wrapping either a pre-resolved AddrV2 or a DNS hostname, and a new TrustedPeer::from_hostname constructor. Hostnames are resolved via tokio::net::lookup_host inside PeerMap::next_peer at each attempt, so the backing IP can change between reconnections without the caller pre-resolving. Like IP-based trusted peers, hostname peers are consumed on use. Retries and re-resolution across node lifetimes are the client's responsibility (rebuild the node on NoReachablePeers). If a hostname fails to resolve, it is logged and the next whitelist entry is tried; the node only surfaces NoReachablePeers once the entire whitelist is exhausted. Useful when the backing IP of a peer may change between reconnections, e.g. a Kubernetes service whose pod IP rotates.
2699acd to
934a7e0
Compare
|
Thanks for the cleanup @rustaceanrob . my original reasoning (for taking only the first address) was to match the "one whitelist entry = one peer attempt" contract — a hostname consumed like any other But yeah, i think you're right, that's needlessly strict. I've also push some changes (and rebased on master):
|
|
LGTM ACK 934a7e0 |
Summary
Extends
TrustedPeerso a peer can be given as a DNS hostname instead of a pre-resolvedAddrV2. Hostnames are resolved at connection time inPeerMap::next_peerviatokio::net::lookup_host, so the backing IP can change between reconnections without the caller pre-resolving.Replaces the earlier
DnsPeer+ round-robin approach from this PR. The reworked design keeps kyoto free of any client-specific retry policy, matching the reviewer's ask — see this comment.API shape
TrustedPeer::new(AddrV2::Ipv4(...), ..)and everyInto<TrustedPeer>impl keep compiling becausePeerAddress: From<AddrV2>andnewtakesimpl Into<PeerAddress>.TrustedPeer::from_hostname(host, port)for the hostname path.Semantics
next_peerthat pops aPeerAddress::Hostnameentry does a freshlookup_host. No caching inside kyoto.TrustedPeer— popped from the whitelist, never reinstated. Retries and re-resolution across node lifetimes are the client's responsibility (rebuild the node onNoReachablePeers).NoReachablePeersis only surfaced once the whole whitelist is exhausted.AddrV2::TorV3 → SocksConnection::OnionServiceas today. Resolving hostnames inside the proxy (via the Socks50x03domain-name address type) is out of scope for this PR.Breaking changes
Minimal, all on the
TrustedPeersurface:pub address: AddrV2→pub address: PeerAddress. Direct struct-literal construction needs.into()(e.g.TrustedPeer { address: AddrV2::Ipv4(ip).into(), ... }). Callers using any constructor orIntoimpl are unaffected.fn address(&self) -> AddrV2→fn address(&self) -> &PeerAddress.impl From<TrustedPeer> for (AddrV2, Option<u16>)→impl From<TrustedPeer> for (PeerAddress, Option<u16>).Testing
New
trusted_peer_hostname_syncintegration test:TrustedPeer::from_hostname(socket_addr.ip().to_string(), socket_addr.port())— an IP literal used as the hostname so the test is deterministic on CI (on macOS,localhostresolves to::1but bitcoind only listens on IPv4).tokio::net::lookup_hostpath fires.Real-hostname resolution (through the OS resolver) is still covered by the existing
dns_workstest.Full integration suite passes locally (
just test integration, 10/10 green, ~106s).Motivation
In containerized / cloud-native environments, Bitcoin peers often sit behind service hostnames whose backing IPs rotate (Kubernetes Services, ECS service discovery, Consul, etc.).
TrustedPeertoday requires a resolvedSocketAddrat build time; hostname support lets users defer resolution to connection time.Used in Hashi — a decentralized Bitcoin collateralization primitive on Sui, built by Mysten Labs. Hashi's bitcoind sits behind a Kubernetes service DNS name whose backing pod IP rotates on restart; hashi runs a supervisor that rebuilds the kyoto node on
NoReachablePeers, which matches the "consumed on use, rebuild to retry" model this PR settles on. See MystenLabs/hashi#402.