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.