start method
Starts the host's background tasks.
Implementation
@override
Future<void> start() async {
_log.fine('[BasicHost start] BEGIN. Host ID: ${id.toString()}, network.hashCode: ${_network.hashCode}, initial network.listenAddresses: ${_network.listenAddresses}');
_log.fine('[BasicHost start] Initial _config.listenAddrs: ${_config.listenAddrs}'); // Added log
// If this host is configured with listen addresses, start listening on them.
// Assuming _config.listenAddrs is List<MultiAddr> and defaults to empty list if not set.
if (_config.listenAddrs.isNotEmpty) {
_log.fine('[BasicHost start] Configured with listenAddrs: ${_config.listenAddrs}. Attempting to listen via _network.listen().');
_log.fine('[BasicHost start] INVOKING _network.listen() with: ${_config.listenAddrs}'); // Added log
try {
await _network.listen(_config.listenAddrs);
_log.fine('[BasicHost start] _network.listen() completed. Current network.listenAddresses: ${_network.listenAddresses}');
} catch (e, s) {
_log.severe('[BasicHost start] Error during _network.listen(): $e\n$s');
// Rethrowing to indicate a fundamental setup issue.
// Services depending on listen addresses might not function correctly.
rethrow;
}
} else {
_log.fine('[BasicHost start] No listenAddrs configured in host config. Skipping explicit _network.listen() call from BasicHost.start().');
}
// Start IDService
_log.fine('[BasicHost start] Before _idService.start. Current network.listenAddresses: ${_network.listenAddresses}');
// await _idService.start();
_log.fine('[BasicHost start] After _idService.start. Current network.listenAddresses: ${_network.listenAddresses}');
// Persist a signed peer record for self to the peerstore if enabled.
// This ensures that when IdentifyService requests our own record, it's available.
if (!(_config.disableSignedPeerRecord ?? false)) {
_log.fine('Attempting to create and persist self signed peer record.');
if (peerStore.addrBook is CertifiedAddrBook) {
final cab = peerStore.addrBook as CertifiedAddrBook;
final selfId = id; // Host's own PeerId
final privKey = await peerStore.keyBook.privKey(selfId);
if (privKey == null) {
_log.fine('Unable to access host private key for selfId $selfId; cannot create self signed record.');
} else {
final currentAddrs = addrs; // Uses the host's addrs getter, which should be up-to-date
if (currentAddrs.isEmpty) {
_log.fine('Host has no addresses at the moment of self-record creation; record will reflect this.');
}
try {
// Create PeerRecord payload
// Note: The actual structure of PeerRecord and how it's created from AddrInfo
// or directly might differ slightly from Go. This assumes a Dart equivalent.
// The key is to get PeerId, sequence number, and addresses into a signable format.
final recordPayload = peer_record.PeerRecord(
peerId: selfId, // Corrected: expects PeerId object
seq: DateTime.now().millisecondsSinceEpoch, // Using timestamp for sequence number
addrs: currentAddrs, // Corrected: expects List<MultiAddr> and param name is 'addrs'
);
// Create and sign the Envelope
// Envelope.seal should handle marshalling the recordPayload and signing
final envelope = await Envelope.seal(recordPayload, privKey);
if (envelope != null) {
await cab.consumePeerRecord(envelope, AddressTTL.permanentAddrTTL);
_log.fine('Successfully created and persisted self signed peer record to peerstore.');
} else {
_log.fine('Failed to create or seal self signed peer record envelope (seal returned null).');
}
} catch (e, s) {
_log.severe('Error creating or persisting self signed peer record: $e\n$s');
}
}
} else {
_log.fine('Peerstore AddrBook is not a CertifiedAddrBook; cannot persist self signed record.');
}
}
// PingService is started implicitly by its constructor registering a handler.
// Initialize RelayManager if enabled
if (_config.enableRelay) {
_log.fine('[BasicHost start] Before RelayManager.create. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
_relayManager = await RelayManager.create(this);
_log.fine('[BasicHost start] After RelayManager.create. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
_log.fine('RelayManager created and service monitoring started.');
}
// Handle forceReachability option for edge cases (e.g., relay servers)
if (_config.forceReachability != null) {
_log.fine('[BasicHost start] Force reachability set to ${_config.forceReachability}');
final reachabilityEmitter = await _eventBus.emitter(EvtLocalReachabilityChanged);
await reachabilityEmitter.emit(
EvtLocalReachabilityChanged(reachability: _config.forceReachability!)
);
await reachabilityEmitter.close();
_log.fine('[BasicHost start] Emitted forced reachability event');
}
// Initialize AutoNAT v2 service if enabled
if (_config.enableAutoNAT) {
_log.fine('[BasicHost start] Before AutoNATv2 creation. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
// First create the underlying AutoNATv2 protocol implementation
// This starts the AutoNAT v2 SERVER (to provide service to other peers)
final autoNATv2 = AutoNATv2Impl(
this, // host
this, // dialerHost - same as host since they have same dialing capabilities
options: _config.autoNATv2Options, // Use options from config
);
await autoNATv2.start();
_log.fine('[BasicHost start] AutoNATv2 server started (providing service to other peers)');
// Only wrap with ambient orchestrator if forceReachability is NOT set
// CRITICAL: Skip ambient probing when forceReachability is set to avoid contradicting the forced status
if (_config.forceReachability == null) {
// Wrap with ambient orchestrator for automatic probing and event emission
_autoNATService = await AmbientAutoNATv2.create(
this,
autoNATv2,
config: _config.ambientAutoNATConfig,
);
_log.fine('[BasicHost start] AmbientAutoNATv2 orchestrator created for ambient probing');
} else {
_log.fine('[BasicHost start] Skipping AmbientAutoNATv2 orchestrator because forceReachability is set to ${_config.forceReachability}');
_log.fine('[BasicHost start] AutoNATv2 server is active, but ambient probing is disabled');
}
_log.fine('[BasicHost start] After AutoNATv2 creation. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
}
// Initialize HolePunchService if enabled
if (_config.enableHolePunching) {
_log.fine('[BasicHost start] Before _holePunchService.start. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
_holePunchService = holepunch_impl.HolePunchServiceImpl(
this,
_idService, // Pass the existing IDService instance
() => publicAddrs, // Pass a function that returns only public/observed addrs
options: const holepunch_impl.HolePunchOptions(), // Default options for now
);
await _holePunchService!.start(); // Call start as per its interface
_log.fine('[BasicHost start] After _holePunchService.start. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
_log.fine('HolePunch service started.');
}
// Initialize CircuitV2Client if relay or autoRelay is enabled
// This registers circuit relay as a transport so peers can dial /p2p-circuit addresses
if (_config.enableRelay || _config.enableAutoRelay) {
_log.fine('[BasicHost start] Initializing CircuitV2Client transport...');
_circuitV2Client = CircuitV2Client(
host: this,
upgrader: _upgrader,
connManager: _cmgr,
metricsObserver: _config.relayMetricsObserver,
);
// Start the CircuitV2Client to register STOP protocol handler
await _circuitV2Client!.start();
// Add CircuitV2Client as a transport to the network (Swarm)
// This allows dialing addresses with /p2p-circuit
// CircuitV2Client now properly implements Transport interface
(_network as Swarm).addTransport(_circuitV2Client!);
// Listen on the circuit address to accept incoming relayed connections
// This creates a listener that will accept and upgrade incoming RelayedConn instances
final circuitAddr = MultiAddr('/p2p-circuit');
await _network.listen([circuitAddr]);
_log.fine('[BasicHost start] CircuitV2Client initialized, transport registered, and listening on circuit address');
}
// Connect to configured relay servers (if any)
if (_config.relayServers.isNotEmpty) {
_log.fine('[BasicHost start] Connecting to ${_config.relayServers.length} configured relay servers...');
await _connectToConfiguredRelays(_config.relayServers);
_log.fine('[BasicHost start] Relay server connections completed');
}
// Initialize AutoRelay if enabled
if (_config.enableAutoRelay) {
_log.fine('[BasicHost start] Initializing AutoRelay...');
// Create AutoRelay configuration
// Use a peer source callback that returns relay peers from the peerstore
final autoRelayConfig = AutoRelayConfig(
peerSourceCallback: (int numPeers) async* {
// Return connected peers as potential relay candidates
_log.fine('[BasicHost AutoRelay] Peer source callback called, requesting $numPeers candidates');
final connectedPeers = _network.peers;
_log.fine('[BasicHost AutoRelay] Found ${connectedPeers.length} connected peers');
int yielded = 0;
for (final peerId in connectedPeers) {
if (yielded >= numPeers) break;
// Get peer's addresses from peerstore
final addrs = await _network.peerstore.addrBook.addrs(peerId);
if (addrs.isNotEmpty) {
_log.fine('[BasicHost AutoRelay] Yielding candidate: $peerId with ${addrs.length} addresses');
yield AddrInfo(peerId, addrs);
yielded++;
}
}
_log.fine('[BasicHost AutoRelay] Peer source callback yielded $yielded candidates');
},
// Use shorter boot delay for faster relay establishment (default is 3 minutes)
bootDelay: Duration(seconds: 5),
// Check for candidates more frequently (default is 30 seconds)
minInterval: Duration(seconds: 10),
);
// Create AutoRelay instance with the upgrader
_autoRelay = AutoRelay(this, _upgrader, userConfig: autoRelayConfig);
// Subscribe to AutoRelay address updates BEFORE starting
_autoRelayAddrsSubscription = _eventBus
.subscribe(EvtAutoRelayAddrsUpdated)
.stream
.listen((event) async {
if (event is EvtAutoRelayAddrsUpdated) {
_log.fine('[BasicHost] Received AutoRelay address update: ${event.advertisableAddrs.length} addresses');
_autoRelayAddrs = List.from(event.advertisableAddrs);
// Regenerate signed peer record with new circuit addresses
await _regenerateSelfSignedPeerRecord();
// Trigger address change to notify other components
_addrChangeChan.add(null);
}
});
// Subscribe to connection events to notify AutoRelay of new potential relays
_autoRelayConnSubscription = _eventBus
.subscribe(EvtPeerConnectednessChanged)
.stream
.listen((event) {
if (event is EvtPeerConnectednessChanged) {
_log.fine('[BasicHost] Peer connectedness changed: ${event.peer}, ${event.connectedness}');
// When a new peer connects, trigger AutoRelay to check for new relay candidates
if (event.connectedness == Connectedness.connected) {
_log.fine('[BasicHost] New peer connected, notifying AutoRelay to check for relay candidates');
// The AutoRelay will call the peerSourceCallback on its next cycle
}
}
});
// Start AutoRelay
await _autoRelay!.start();
_log.fine('[BasicHost start] AutoRelay service created and started.');
}
_log.fine('[BasicHost start] Before calling _startBackground. network.hashCode: ${_network.hashCode}, network.listenAddresses: ${_network.listenAddresses}');
// Start other background tasks
return await _startBackground();
}