start method

  1. @override
Future<void> start()
override

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);

          await cab.consumePeerRecord(
              envelope, AddressTTL.permanentAddrTTL);
          _log.fine(
              'Successfully created and persisted self signed peer record to peerstore.');
                  } 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();
}