Saltar al contenido principal

Protocolo de Descubrimiento de Nodos

En el vasto océano digital donde los nodos blockchain flotan como barcos en la niebla, el protocolo de descubrimiento actúa como un radar omnipresente, revelando la presencia de otros participantes y guiando las conexiones. Implementado en p2p/discovery.go, este sistema transforma el aislamiento en comunidad, permitiendo que nodos desconocidos se encuentren y colaboren.

La Arquitectura del Descubrimiento

DiscoveryProtocol: El Coordinador Maestro

type DiscoveryProtocol struct {
mutex sync.RWMutex
localNode NodeAnnouncement
knownPeers map[string]*PeerInfo
routingTable *RoutingTable
bootstrapNodes []string
announceInterval time.Duration
discoveryPort int
networkConfig NetworkConfig
natDetector *NATDetector
natResult *NATDetectionResult
holePunchingManager *HolePunchingManager
upnpManager *UPnPManager
}

Esta estructura comprehensiva maneja todos los aspectos del descubrimiento de peers, desde anuncios UDP hasta NAT traversal avanzado.

NodeAnnouncement: La Firma Digital

type NodeAnnouncement struct {
Enode *Enode
ConsensusType string
IsBootstrap bool
Version string
Timestamp time.Time
Uptime string
NetworkID string
ChainID string
}

Cada anuncio es una presentación completa del nodo, incluyendo su identidad, capacidades y estado de red.

El Ciclo de Vida del Descubrimiento

Inicialización del Protocolo

func NewDiscoveryProtocol(nodeID, ipAddress string, tcpPort, udpPort int, consensusType string, isBootstrap bool) *DiscoveryProtocol {
// Crear enode local
localEnode := NewEnode(nodeID, ipAddress, map[string]int{
"p2p": udpPort,
"http": tcpPort,
})

// Crear tabla de enrutamiento
routingTable := NewRoutingTable(localEnode, 20, 256)

// Configuración de red dinámica
networkConfig := NetworkConfig{
UDPPort: udpPort,
HTTPPort: tcpPort,
P2PPort: udpPort,
LocalIP: ipAddress,
KnownPeers: []string{},
AnnounceInterval: 30 * time.Second,
UDPTimeout: 5 * time.Second,
}

return &DiscoveryProtocol{
localNode: NodeAnnouncement{
Enode: localEnode,
ConsensusType: consensusType,
IsBootstrap: isBootstrap,
Version: "v2.0.0-alpha",
Timestamp: time.Now(),
NetworkID: "KDC_NETWORK_1",
ChainID: "KDC_CHAIN_1",
},
knownPeers: make(map[string]*PeerInfo),
routingTable: routingTable,
announceInterval: 30 * time.Second,
discoveryPort: udpPort,
networkConfig: networkConfig,
natDetector: NewNATDetector(),
holePunchingManager: NewHolePunchingManager(nodeID, ipAddress, udpPort),
upnpManager: NewUPnPManager(),
}
}

La inicialización establece todos los componentes necesarios para el funcionamiento del protocolo.

Arranque del Sistema

func (dp *DiscoveryProtocol) Start() error {
fmt.Printf("🔍 Iniciando protocolo de descubrimiento en puerto UDP %d\n", dp.discoveryPort)

// Realizar detección de NAT
if err := dp.DetectNAT(); err != nil {
fmt.Printf("⚠️ Advertencia: Error en detección de NAT: %v\n", err)
}

// Iniciar goroutines
go dp.announceLoop()
go dp.discoveryLoop()
go dp.peerMaintenanceLoop()

return nil
}

El arranque inicia tres procesos concurrentes: anuncios periódicos, escucha de descubrimientos, y mantenimiento de peers.

Anuncios y Comunicación

Bucle de Anuncios

func (dp *DiscoveryProtocol) announceLoop() {
ticker := time.NewTicker(dp.announceInterval)
defer ticker.Stop()

for range ticker.C {
dp.announcePresence()
}
}

Cada 30 segundos, el nodo anuncia su presencia a la red.

Anuncio de Presencia

func (dp *DiscoveryProtocol) announcePresence() {
dp.mutex.RLock()
announcement := dp.localNode
networkConfig := dp.networkConfig
dp.mutex.RUnlock()

// Actualizar timestamp
announcement.Timestamp = time.Now()

// Broadcast del anuncio
dp.broadcastAnnouncement(announcement)

fmt.Printf("📢 Anunciando presencia: %s en %s:%d\n",
announcement.Enode.ID, announcement.Enode.IP, announcement.Enode.Ports["http"])
}

El anuncio incluye toda la información necesaria para que otros nodos puedan conectarse.

Broadcast de Anuncios

func (dp *DiscoveryProtocol) broadcastAnnouncement(announcement NodeAnnouncement) {
data, err := json.Marshal(announcement)
if err != nil {
return
}

// Enviar a peers conocidos
for _, peerAddress := range networkConfig.KnownPeers {
go func(addr string) {
dp.sendAnnouncementToPeer(announcement, addr, data)
}(peerAddress)
}

// Enviar a peers conectados
dp.mutex.RLock()
connectedPeers := make([]string, 0)
for _, peer := range dp.knownPeers {
if peer.IsConnected {
peerAddr := fmt.Sprintf("%s:%d", peer.NodeAnnouncement.Enode.IP, peer.NodeAnnouncement.Enode.Ports["p2p"])
connectedPeers = append(connectedPeers, peerAddr)
}
}
dp.mutex.RUnlock()

for _, peerAddr := range connectedPeers {
go func(addr string) {
dp.sendAnnouncementToPeer(announcement, addr, data)
}(peerAddr)
}
}

El broadcast se realiza tanto a peers configurados como a peers conectados dinámicamente.

Escucha y Procesamiento

Bucle de Descubrimiento

func (dp *DiscoveryProtocol) discoveryLoop() {
udpAddr := &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0), // Escuchar en todas las interfaces IPv4
Port: dp.discoveryPort,
}

conn, err := net.ListenUDP("udp4", udpAddr)
if err != nil {
fmt.Printf("❌ Error escuchando en puerto UDP %d: %v\n", dp.discoveryPort, err)
return
}
defer conn.Close()

fmt.Printf("👂 Escuchando anuncios en puerto UDP %d (IPv4)\n", dp.discoveryPort)

buffer := make([]byte, 4096)

for {
n, remoteAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
continue
}

var announcement NodeAnnouncement
if err := json.Unmarshal(buffer[:n], &announcement); err != nil {
continue
}

dp.processAnnouncement(announcement, remoteAddr)
}
}

El bucle de escucha procesa anuncios entrantes y actualiza la lista de peers conocidos.

Procesamiento de Anuncios

func (dp *DiscoveryProtocol) processAnnouncement(announcement NodeAnnouncement, remoteAddr *net.UDPAddr) {
dp.mutex.Lock()
defer dp.mutex.Unlock()

// Verificar que no sea nuestro propio anuncio
if announcement.Enode.ID == dp.localNode.Enode.ID {
return
}

// Crear o actualizar peer
peerKey := fmt.Sprintf("%s:%d", remoteAddr.IP.String(), announcement.Enode.Ports["http"])

peer, exists := dp.knownPeers[peerKey]
if !exists {
peer = &PeerInfo{
NodeAnnouncement: announcement,
LastSeen: time.Now(),
IsConnected: false,
TrustScore: 50,
}
dp.knownPeers[peerKey] = peer

fmt.Printf("🆕 Nuevo peer descubierto: %s en %s:%d\n",
announcement.Enode.ID, announcement.Enode.IP, announcement.Enode.Ports["http"])

go dp.autoAddAsContact(peer, remoteAddr)
} else {
peer.NodeAnnouncement = announcement
peer.LastSeen = time.Now()
peer.TrustScore = min(100, peer.TrustScore+1)
}

dp.sendAnnouncementResponse(remoteAddr)
}

Cada anuncio válido resulta en la actualización o creación de un registro de peer.

Gestión de NAT y Conectividad

Detección de NAT

func (dp *DiscoveryProtocol) DetectNAT() error {
fmt.Println("🔍 Iniciando detección de NAT...")

result, err := dp.natDetector.DetectNAT()
if err != nil {
return err
}

dp.mutex.Lock()
dp.natResult = result
dp.mutex.Unlock()

fmt.Printf("✅ Detección de NAT completada:\n")
fmt.Printf(" 📍 IP Local: %s\n", result.LocalIP)
fmt.Printf(" 🌐 IP Pública: %s\n", result.PublicIP)
fmt.Printf(" 🔒 Detrás de NAT: %t\n", result.IsBehindNAT)
fmt.Printf(" 🏷️ Tipo de NAT: %s\n", result.NATType)

if result.IsBehindNAT {
dp.updateNetworkConfigForNAT(result)
}

return nil
}

La detección de NAT determina la estrategia de conectividad apropiada.

Actualización por NAT

func (dp *DiscoveryProtocol) updateNetworkConfigForNAT(result *NATDetectionResult) {
if result.PublicIP != "" {
dp.networkConfig.LocalIP = result.PublicIP
}

// Ajustar timeouts según tipo de NAT
switch result.NATType {
case NATTypeSymmetric:
dp.networkConfig.UDPTimeout = 10 * time.Second
case NATTypeRestrictedCone, NATTypePortRestricted:
dp.networkConfig.UDPTimeout = 7 * time.Second
}
}

La configuración se adapta dinámicamente según las características del NAT detectado.

Soporte UPnP

Descubrimiento de IGD

func (dp *DiscoveryProtocol) DiscoverUPnPIGD() error {
if dp.upnpManager == nil {
return fmt.Errorf("UPnP manager no está inicializado")
}

igd, err := dp.upnpManager.DiscoverIGD()
if err != nil {
return err
}

fmt.Printf("🔍 IGD descubierto: %s (%s %s)\n", igd.Device.FriendlyName, igd.Device.Manufacturer, igd.Device.ModelName)
return nil
}

UPnP permite configuración automática de port forwarding en routers compatibles.

Mapeo de Puertos UPnP

func (dp *DiscoveryProtocol) AddUPnPPortMapping(externalPort, internalPort int, protocol, description string, leaseDuration int) error {
internalClient := dp.networkConfig.LocalIP
return dp.upnpManager.AddPortMapping(externalPort, internalPort, protocol, internalClient, description, leaseDuration)
}

Los mapeos de puerto permiten conexiones entrantes a través de NAT.

Hole Punching Avanzado

Gestión de Conexiones NAT

func (dp *DiscoveryProtocol) RequestHolePunchingConnection(targetPeerID, protocol string) (interface{}, error) {
if dp.holePunchingManager == nil {
return nil, fmt.Errorf("hole punching manager no está inicializado")
}

return dp.holePunchingManager.RequestConnection(targetPeerID, protocol)
}

Hole punching permite conexiones directas incluso con NAT restrictivo.

Mantenimiento y Limpieza

Mantenimiento de Peers

func (dp *DiscoveryProtocol) peerMaintenanceLoop() {
ticker := time.NewTicker(60 * time.Second)
defer ticker.Stop()

for range ticker.C {
dp.cleanupInactivePeers()
}
}

Cada minuto, se eliminan peers inactivos para mantener la lista actualizada.

Limpieza de Peers Inactivos

func (dp *DiscoveryProtocol) cleanupInactivePeers() {
dp.mutex.Lock()
defer dp.mutex.Unlock()

now := time.Now()
threshold := 5 * time.Minute

for peerKey, peer := range dp.knownPeers {
if now.Sub(peer.LastSeen) > threshold {
delete(dp.knownPeers, peerKey)
fmt.Printf("🗑️ Peer inactivo removido: %s\n", peer.NodeAnnouncement.Enode.ID)
}
}
}

Peers no vistos en 5 minutos se consideran inactivos y se eliminan.

Información y Estadísticas

Peers Conocidos

func (dp *DiscoveryProtocol) GetKnownPeers() map[string]*PeerInfo {
dp.mutex.RLock()
defer dp.mutex.RUnlock()

result := make(map[string]*PeerInfo)
for k, v := range dp.knownPeers {
result[k] = v
}
return result
}

Proporciona acceso seguro a la lista de peers conocidos.

Anuncio Local

func (dp *DiscoveryProtocol) GetLocalAnnouncement() NodeAnnouncement {
dp.mutex.RLock()
defer dp.mutex.RUnlock()
return dp.localNode
}

Retorna la información de anuncio del nodo local.

El Arte del Descubrimiento

El protocolo de descubrimiento de KodeChain transforma el problema de encontrar peers en una red distribuida en una solución elegante y robusta. Desde el primer anuncio UDP hasta el establecimiento de conexiones a través de NAT complejo, cada componente del sistema está diseñado para maximizar la conectividad mientras minimiza la sobrecarga.

Cada anuncio enviado, cada peer descubierto, cada conexión NAT atravesada es un paso hacia la creación de una red verdaderamente distribuida y resistente. El protocolo no solo encuentra nodos; construye la infraestructura que hace posible la blockchain.

En KodeChain, el descubrimiento de nodos no es un simple directorio; es el latido que mantiene viva la red, el radar que revela oportunidades de colaboración en el vasto océano digital.