network/pkg/discovery/discovery.go
2025-08-03 16:24:04 +03:00

177 lines
4.2 KiB
Go

package discovery
import (
"context"
"errors"
"time"
dht "github.com/libp2p/go-libp2p-kad-dht"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"go.uber.org/zap"
)
// Manager handles peer discovery operations
type Manager struct {
host host.Host
dht *dht.IpfsDHT
logger *zap.Logger
cancel context.CancelFunc
}
// Config contains discovery configuration
type Config struct {
DiscoveryInterval time.Duration
MaxConnections int
}
// NewManager creates a new discovery manager
func NewManager(host host.Host, dht *dht.IpfsDHT, logger *zap.Logger) *Manager {
return &Manager{
host: host,
dht: dht,
logger: logger,
}
}
// Start begins periodic peer discovery
func (d *Manager) Start(config Config) error {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
go func() {
// Do initial discovery immediately
d.discoverPeers(ctx, config)
// Continue with periodic discovery
ticker := time.NewTicker(config.DiscoveryInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
d.discoverPeers(ctx, config)
}
}
}()
return nil
}
// Stop stops peer discovery
func (d *Manager) Stop() {
if d.cancel != nil {
d.cancel()
}
}
// discoverPeers discovers and connects to new peers
func (d *Manager) discoverPeers(ctx context.Context, config Config) {
connectedPeers := d.host.Network().Peers()
initialCount := len(connectedPeers)
d.logger.Debug("Starting peer discovery",
zap.Int("current_peers", initialCount))
// Strategy 1: Use DHT to find peers
newConnections := d.discoverViaDHT(ctx, config.MaxConnections)
// Strategy 2: Ask connected peers about their connections
newConnections += d.discoverViaPeerExchange(ctx, config.MaxConnections)
finalPeerCount := len(d.host.Network().Peers())
if newConnections > 0 || finalPeerCount != initialCount {
d.logger.Debug("Peer discovery completed",
zap.Int("new_connections", newConnections),
zap.Int("initial_peers", initialCount),
zap.Int("final_peers", finalPeerCount))
}
}
// discoverViaDHT uses the DHT to find random peers
func (d *Manager) discoverViaDHT(ctx context.Context, maxConnections int) int {
if d.dht == nil {
return 0
}
connected := 0
// Get peers from routing table
routingTablePeers := d.dht.RoutingTable().ListPeers()
d.logger.Debug("DHT routing table has peers", zap.Int("count", len(routingTablePeers)))
for _, peerID := range routingTablePeers {
if peerID == d.host.ID() {
continue
}
if connected >= maxConnections {
break
}
// Check if we're already connected
if d.host.Network().Connectedness(peerID) != network.NotConnected {
continue
}
// Try to connect
if err := d.connectToPeer(ctx, peerID); err == nil {
connected++
}
}
return connected
}
// discoverViaPeerExchange asks connected peers about their connections
func (d *Manager) discoverViaPeerExchange(ctx context.Context, maxConnections int) int {
connected := 0
connectedPeers := d.host.Network().Peers()
for _, peerID := range connectedPeers {
if connected >= maxConnections {
break
}
// Get peer connections (this is a simplified implementation)
// In a real implementation, you might use a custom protocol
peerInfo := d.host.Peerstore().PeerInfo(peerID)
for _, addr := range peerInfo.Addrs {
if connected >= maxConnections {
break
}
// Extract peer ID from multiaddr and try to connect
// This is simplified - in practice you'd need proper multiaddr parsing
_ = addr // Placeholder for actual implementation
}
}
return connected
}
// connectToPeer attempts to connect to a specific peer
func (d *Manager) connectToPeer(ctx context.Context, peerID peer.ID) error {
// Get peer info from DHT
peerInfo := d.host.Peerstore().PeerInfo(peerID)
if len(peerInfo.Addrs) == 0 {
return errors.New("no addresses for peer")
}
// Attempt connection
if err := d.host.Connect(ctx, peerInfo); err != nil {
d.logger.Debug("Failed to connect to DHT peer",
zap.String("peer_id", peerID.String()[:8]+"..."),
zap.Error(err))
return err
}
d.logger.Debug("Successfully connected to DHT peer",
zap.String("peer_id", peerID.String()[:8]+"..."))
return nil
}