fix: retain peer address so a transient gossip Leave can't strand the dialer

The dialer-side supervisor dialed peers by bare EndpointId, forcing iroh to
resolve the address through the gossip-fed MemoryLookup. A transient drop that
also triggered a gossip Leave/NeighborDown purged the peer from that lookup, so
the supervisor redialed forever with "no address" (observed in the 2026-05-30
field test: ~4.5 min of "No address lookup configured; retrying in 5s").

The transport now retains each peer's full EndpointAddr (relay + direct addrs),
refreshed on join and on every re-announce (so a rejoin on a new address updates
the dial target), and the dialer dials that retained address directly — which
bypasses the lookup entirely. connect_peer now takes an EndpointAddr; core hands
over state.addr on PeerJoined and PeerUpdated.

New regression test dials and reconnects with the dialer's lookup deliberately
empty and the relay disabled, so only the retained address can carry the dial:
it passes with the fix and times out under the old bare-id behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 15:39:51 -04:00
co-authored by Claude Opus 4.8
parent 66c912e279
commit 09acefd2b3
4 changed files with 118 additions and 9 deletions
+7 -2
View File
@@ -82,8 +82,13 @@ impl FromStr for PeerSpeakTicket {
#[async_trait]
pub trait NetworkTransport: Send + Sync {
/// Establish (or ensure) a connection to a peer and set up its send path.
/// Idempotent; safe to call again for an already-connected peer.
async fn connect_peer(&self, peer_id: EndpointId);
/// Idempotent; safe to call again for an already-connected peer — a repeat
/// call refreshes the retained dial address, so calling it again when a peer
/// re-announces (e.g. on a new address) keeps the dialer able to re-reach it.
/// The full `EndpointAddr` (relay + direct addrs) is retained so reconnects
/// dial it directly instead of depending on a lookup that a transient gossip
/// `Leave`/`NeighborDown` may have purged.
async fn connect_peer(&self, addr: iroh::EndpointAddr);
/// Tear down the connection and send path for a peer that has left.
async fn disconnect_peer(&self, peer_id: EndpointId);