connect method
Connect ensures there is a connection between this host and the peer with given peer.ID. Connect will absorb the addresses in pi into its internal peerstore. If there is not an active connection, Connect will issue a h.Network.Dial, and block until a connection is open, or an error is returned.
If context is not provided, a new Context will be created.
Implementation
@override
Future<void> connect(AddrInfo pi, {Context? context}) async {
final startTime = DateTime.now();
// Prevent self-dialing
if (pi.id == id) {
_log.info('Preventing self-dial attempt to ${pi.id}');
return;
}
// Phase 1: Address Filtering and Peerstore Update
final filterStartTime = DateTime.now();
// _addrsFactory transforms how THIS host presents its OWN addresses to
// others (see `get addrs => _addrsFactory(allAddrs)` above) — it must
// never run on `pi.addrs`, the addresses of the PEER being connected to.
// The original code called it here anyway, so every connect() to any
// peer (relay, hole-punch target, anyone) appended this host's own
// AddrsFactory-derived addresses into THAT PEER's peerstore entry.
// Swarm.dialPeer then raced this host's own address as a bogus dial
// candidate for every subsequent dial to that peer — observed as a host
// attempting to dial its own external address.
final filteredAddrs = pi.addrs;
await peerStore.addrBook.addAddrs(pi.id, filteredAddrs, Duration(minutes: 5));
final filterTime = DateTime.now().difference(filterStartTime);
// Phase 2: Connection Check
final connectedness = _network.connectedness(pi.id);
// This early return fires even for a *relay* connection
// (Connectedness.limited-worthy, but connectedness() doesn't distinguish
// it from a full connection here) — meaning DCUtR's forceDirectDial punch
// dial never even reached Swarm.dialPeer (where its own forceDirectDial
// check applies). ForceDirectDial's entire purpose is "dial anyway, even
// though a connection exists" — honor that here, one layer up from where
// it was already checked.
final skipEarlyReturn = context?.getForceDirectDial().$1 ?? false;
if (connectedness == Connectedness.connected && !skipEarlyReturn) {
final totalTime = DateTime.now().difference(startTime);
return;
}
// Phase 3: Context Creation
final ctx = context ?? Context();
// Phase 4: Dial Peer
final dialStartTime = DateTime.now();
try {
await _dialPeer(pi.id, ctx);
final dialTime = DateTime.now().difference(dialStartTime);
final totalTime = DateTime.now().difference(startTime);
} on IdentifyTimeoutException catch (e, stackTrace) {
// Handle identify timeout gracefully - log warning instead of error
final dialTime = DateTime.now().difference(dialStartTime);
final totalTime = DateTime.now().difference(startTime);
_log.warning('⏱️ [CONNECT-TIMEOUT] Connection to ${pi.id} timed out during identify after ${totalTime.inMilliseconds}ms (dial: ${dialTime.inMilliseconds}ms): $e');
// Rethrow the typed exception so callers can handle it specifically
rethrow;
} on IdentifyException catch (e, stackTrace) {
// Handle other identify exceptions
final dialTime = DateTime.now().difference(dialStartTime);
final totalTime = DateTime.now().difference(startTime);
_log.severe('❌ [CONNECT-IDENTIFY-ERROR] Connection failed due to identify error after ${totalTime.inMilliseconds}ms (dial: ${dialTime.inMilliseconds}ms): $e\n$stackTrace');
rethrow;
} catch (e, stackTrace) {
final dialTime = DateTime.now().difference(dialStartTime);
final totalTime = DateTime.now().difference(startTime);
// A failed hole punch or punch pre-dial is normal behind many NATs.
if (ctx.getForceDirectDial().$1) {
_log.fine('[CONNECT-ERROR] Direct connection failed after ${totalTime.inMilliseconds}ms: $e');
} else {
_log.severe('❌ [CONNECT-ERROR] Connection failed after ${totalTime.inMilliseconds}ms (dial: ${dialTime.inMilliseconds}ms): $e\n$stackTrace');
}
rethrow;
}
}