 17673c38a6
			
		
	
	17673c38a6
	
	
	
		
			
			## P2P Connectivity Fixes - **Root Cause**: mDNS discovery was conditionally disabled in Task Execution Engine implementation - **Solution**: Restored always-enabled mDNS discovery from working baseline (eb2e05f) - **Result**: 9/9 Docker Swarm replicas with working P2P mesh, democratic elections, and leader consensus ## Dynamic Version System - **Problem**: Hardcoded version "0.1.0-dev" in 1000+ builds made debugging impossible - **Solution**: Implemented build-time version injection via ldflags - **Features**: Shows commit hash, build date, and semantic version - **Example**: `CHORUS-agent 0.5.5 (build:9dbd361, 2025-09-26_05:55:55)` ## Container Compatibility - **Issue**: Binary execution failed in Alpine due to glibc/musl incompatibility - **Solution**: Added Ubuntu-based Dockerfile for proper glibc support - **Benefit**: Reliable container execution across Docker Swarm nodes ## Key Changes - `internal/runtime/shared.go`: Always enable mDNS discovery, dynamic version vars - `cmd/agent/main.go`: Build-time version injection and display - `p2p/node.go`: Restored working "🐝 Bzzz Node Status" logging format - `Makefile`: Updated version to 0.5.5, proper ldflags configuration - `Dockerfile.ubuntu`: New glibc-compatible container base - `docker-compose.yml`: Updated to latest image tag for Watchtower auto-updates ## Verification ✅ P2P mesh connectivity: Peers exchanging availability broadcasts ✅ Democratic elections: Candidacy announcements and leader selection ✅ BACKBEAT integration: Beat synchronization and degraded mode handling ✅ Dynamic versioning: All containers show v0.5.5 with build metadata ✅ Task Execution Engine: All Phase 4 functionality preserved and working Fixes P2P connectivity regression while preserving complete Task Execution Engine implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			202 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package p2p
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"time"
 | |
| 
 | |
| 	"chorus/pkg/dht"
 | |
| 
 | |
| 	"github.com/libp2p/go-libp2p"
 | |
| 	kaddht "github.com/libp2p/go-libp2p-kad-dht"
 | |
| 	"github.com/libp2p/go-libp2p/core/host"
 | |
| 	"github.com/libp2p/go-libp2p/core/peer"
 | |
| 	"github.com/libp2p/go-libp2p/p2p/security/noise"
 | |
| 	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
 | |
| 	"github.com/multiformats/go-multiaddr"
 | |
| )
 | |
| 
 | |
| // Node represents a CHORUS P2P node
 | |
| type Node struct {
 | |
| 	host   host.Host
 | |
| 	ctx    context.Context
 | |
| 	cancel context.CancelFunc
 | |
| 	config *Config
 | |
| 	dht    *dht.LibP2PDHT // Optional DHT for distributed discovery
 | |
| }
 | |
| 
 | |
| // NewNode creates a new P2P node with the given configuration
 | |
| func NewNode(ctx context.Context, opts ...Option) (*Node, error) {
 | |
| 	config := DefaultConfig()
 | |
| 	for _, opt := range opts {
 | |
| 		opt(config)
 | |
| 	}
 | |
| 
 | |
| 	nodeCtx, cancel := context.WithCancel(ctx)
 | |
| 
 | |
| 	// Build multiaddresses for listening
 | |
| 	var listenAddrs []multiaddr.Multiaddr
 | |
| 	for _, addr := range config.ListenAddresses {
 | |
| 		ma, err := multiaddr.NewMultiaddr(addr)
 | |
| 		if err != nil {
 | |
| 			cancel()
 | |
| 			return nil, fmt.Errorf("invalid listen address %s: %w", addr, err)
 | |
| 		}
 | |
| 		listenAddrs = append(listenAddrs, ma)
 | |
| 	}
 | |
| 
 | |
| 	// Create libp2p host with security and transport options
 | |
| 	h, err := libp2p.New(
 | |
| 		libp2p.ListenAddrs(listenAddrs...),
 | |
| 		libp2p.Security(noise.ID, noise.New),
 | |
| 		libp2p.Transport(tcp.NewTCPTransport),
 | |
| 		libp2p.DefaultMuxers,
 | |
| 		libp2p.EnableRelay(),
 | |
| 	)
 | |
| 	if err != nil {
 | |
| 		cancel()
 | |
| 		return nil, fmt.Errorf("failed to create libp2p host: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	node := &Node{
 | |
| 		host:   h,
 | |
| 		ctx:    nodeCtx,
 | |
| 		cancel: cancel,
 | |
| 		config: config,
 | |
| 	}
 | |
| 
 | |
| 	// Initialize DHT if enabled
 | |
| 	if config.EnableDHT {
 | |
| 		var dhtMode kaddht.ModeOpt
 | |
| 		switch config.DHTMode {
 | |
| 		case "client":
 | |
| 			dhtMode = kaddht.ModeClient
 | |
| 		case "server":
 | |
| 			dhtMode = kaddht.ModeServer
 | |
| 		default:
 | |
| 			dhtMode = kaddht.ModeAuto
 | |
| 		}
 | |
| 
 | |
| 		dhtOpts := []dht.Option{
 | |
| 			dht.WithProtocolPrefix(config.DHTProtocolPrefix),
 | |
| 			dht.WithMode(dhtMode),
 | |
| 			dht.WithBootstrapPeersFromStrings(config.DHTBootstrapPeers),
 | |
| 			dht.WithAutoBootstrap(len(config.DHTBootstrapPeers) > 0),
 | |
| 		}
 | |
| 
 | |
| 		var err error
 | |
| 		node.dht, err = dht.NewLibP2PDHT(nodeCtx, h, dhtOpts...)
 | |
| 		if err != nil {
 | |
| 			cancel()
 | |
| 			h.Close()
 | |
| 			return nil, fmt.Errorf("failed to create DHT: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Start background processes
 | |
| 	go node.startBackgroundTasks()
 | |
| 
 | |
| 	return node, nil
 | |
| }
 | |
| 
 | |
| // Host returns the underlying libp2p host
 | |
| func (n *Node) Host() host.Host {
 | |
| 	return n.host
 | |
| }
 | |
| 
 | |
| // ID returns the peer ID of this node
 | |
| func (n *Node) ID() peer.ID {
 | |
| 	return n.host.ID()
 | |
| }
 | |
| 
 | |
| // Addresses returns the multiaddresses this node is listening on
 | |
| func (n *Node) Addresses() []multiaddr.Multiaddr {
 | |
| 	return n.host.Addrs()
 | |
| }
 | |
| 
 | |
| // Connect connects to a peer at the given multiaddress
 | |
| func (n *Node) Connect(ctx context.Context, addr string) error {
 | |
| 	ma, err := multiaddr.NewMultiaddr(addr)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("invalid multiaddress %s: %w", addr, err)
 | |
| 	}
 | |
| 
 | |
| 	addrInfo, err := peer.AddrInfoFromP2pAddr(ma)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("failed to parse addr info: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return n.host.Connect(ctx, *addrInfo)
 | |
| }
 | |
| 
 | |
| // Peers returns the list of connected peers
 | |
| func (n *Node) Peers() []peer.ID {
 | |
| 	return n.host.Network().Peers()
 | |
| }
 | |
| 
 | |
| // ConnectedPeers returns the number of connected peers
 | |
| func (n *Node) ConnectedPeers() int {
 | |
| 	return len(n.Peers())
 | |
| }
 | |
| 
 | |
| // startBackgroundTasks starts background maintenance tasks
 | |
| func (n *Node) startBackgroundTasks() {
 | |
| 	ticker := time.NewTicker(30 * time.Second)
 | |
| 	defer ticker.Stop()
 | |
| 
 | |
| 	for {
 | |
| 		select {
 | |
| 		case <-n.ctx.Done():
 | |
| 			return
 | |
| 		case <-ticker.C:
 | |
| 			// Periodic maintenance tasks
 | |
| 			n.logConnectionStatus()
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // logConnectionStatus logs the current connection status
 | |
| func (n *Node) logConnectionStatus() {
 | |
| 	peers := n.Peers()
 | |
| 	fmt.Printf("🐝 Bzzz Node Status - ID: %s, Connected Peers: %d\n",
 | |
| 		n.ID().ShortString(), len(peers))
 | |
| 
 | |
| 	if len(peers) > 0 {
 | |
| 		fmt.Printf("   Connected to: ")
 | |
| 		for i, p := range peers {
 | |
| 			if i > 0 {
 | |
| 				fmt.Printf(", ")
 | |
| 			}
 | |
| 			fmt.Printf("%s", p.ShortString())
 | |
| 		}
 | |
| 		fmt.Println()
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // DHT returns the DHT instance (if enabled)
 | |
| func (n *Node) DHT() *dht.LibP2PDHT {
 | |
| 	return n.dht
 | |
| }
 | |
| 
 | |
| // IsDHTEnabled returns whether DHT is enabled and active
 | |
| func (n *Node) IsDHTEnabled() bool {
 | |
| 	return n.dht != nil
 | |
| }
 | |
| 
 | |
| // Bootstrap bootstraps the DHT (if enabled)
 | |
| func (n *Node) Bootstrap() error {
 | |
| 	if n.dht != nil {
 | |
| 		return n.dht.Bootstrap()
 | |
| 	}
 | |
| 	return fmt.Errorf("DHT not enabled")
 | |
| }
 | |
| 
 | |
| // Close shuts down the node
 | |
| func (n *Node) Close() error {
 | |
| 	if n.dht != nil {
 | |
| 		n.dht.Close()
 | |
| 	}
 | |
| 	n.cancel()
 | |
| 	return n.host.Close()
 | |
| }
 |