Saltar al contenido principal

Sistema Enode

En el vasto ecosistema de la computación distribuida, donde nodos aparecen y desaparecen como estrellas en el cielo nocturno, el sistema Enode emerge como el pasaporte digital que identifica inequívocamente a cada participante. Implementado en p2p/enode.go, este sistema transforma direcciones IP y puertos en identidades persistentes y verificables.

La Arquitectura de la Identidad

Enode: La Estructura Fundamental

type Enode struct {
ID string // ID único del nodo
IP string // Dirección IP del nodo
Ports map[string]int // Puertos del nodo (http, p2p, etc.)
Capabilities []string // Capacidades del nodo
LastSeen time.Time // Última vez que se vio el nodo
TrustScore int // Puntuación de confianza
Metadata map[string]string // Metadatos adicionales
}

Esta estructura encapsula toda la información necesaria para identificar y conectar con un nodo en la red.

Creación de Enodes

func NewEnode(id, ip string, ports map[string]int) *Enode {
return &Enode{
ID: id,
IP: ip,
Ports: ports,
Capabilities: []string{"p2p", "http"},
LastSeen: time.Now(),
TrustScore: 50,
Metadata: make(map[string]string),
}
}

La creación establece capacidades básicas y un score de confianza inicial.

Representación y Serialización

Formato String Estándar

func (e *Enode) ToString() string {
capabilities := strings.Join(e.Capabilities, ",")
return fmt.Sprintf("enode://%s@%s:%d?http_port=%d&capabilities=%s",
e.ID, e.IP, e.Ports["p2p"], e.Ports["http"], capabilities)
}

El formato string sigue el estándar Ethereum, permitiendo interoperabilidad y parsing consistente.

Parsing desde String

func FromString(enodeStr string) (*Enode, error) {
if !strings.HasPrefix(enodeStr, "enode://") {
return nil, fmt.Errorf("formato de enode inválido")
}

enodeStr = strings.TrimPrefix(enodeStr, "enode://")

parts := strings.Split(enodeStr, "?")
if len(parts) < 1 {
return nil, fmt.Errorf("formato de enode inválido")
}

mainPart := parts[0]
idIPPort := strings.Split(mainPart, "@")
if len(idIPPort) != 2 {
return nil, fmt.Errorf("formato de enode inválido")
}

id := idIPPort[0]
ipPort := strings.Split(idIPPort[1], ":")
if len(ipPort) != 2 {
return nil, fmt.Errorf("IP:PUERTO inválido")
}

ip := ipPort[0]
p2pPort, err := strconv.Atoi(ipPort[1])
if err != nil {
return nil, fmt.Errorf("puerto P2P inválido: %v", err)
}

ports := map[string]int{"p2p": p2pPort}
capabilities := []string{"p2p"}

if len(parts) > 1 {
params := strings.Split(parts[1], "&")
for _, param := range params {
keyValue := strings.Split(param, "=")
if len(keyValue) == 2 {
key, value := keyValue[0], keyValue[1]
switch key {
case "http_port":
if httpPort, err := strconv.Atoi(value); err == nil {
ports["http"] = httpPort
capabilities = append(capabilities, "http")
}
case "capabilities":
if value != "" {
capabilities = strings.Split(value, ",")
}
}
}
}
}

return &Enode{
ID: id,
IP: ip,
Ports: ports,
Capabilities: capabilities,
LastSeen: time.Now(),
TrustScore: 50,
Metadata: make(map[string]string),
}, nil
}

El parsing robusto maneja todos los componentes opcionales del formato enode.

Generación de Identidades

ID Único del Nodo

func GenerateNodeID() string {
bytes := make([]byte, 32)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}

La generación usa entropía criptográfica para crear IDs únicos de 64 caracteres hexadecimales.

Detección de IP Local

func GetLocalIP() string {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "127.0.0.1"
}
defer conn.Close()

localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}

Esta función determina la IP local conectándose a un servidor externo, útil para configuración automática.

Validación y Estado

Verificación de Validez

func (e *Enode) IsValid() bool {
return e.ID != "" && e.IP != "" && len(e.Ports) > 0
}

La validación asegura que el enode tenga toda la información necesaria para ser útil.

Gestión de Estado Temporal

func (e *Enode) UpdateLastSeen() {
e.LastSeen = time.Now()
}

func (e *Enode) GetAge() time.Duration {
return time.Since(e.LastSeen)
}

func (e *Enode) IsStale() bool {
return e.GetAge() > time.Hour
}

Estas funciones permiten determinar si un enode está actualizado o si necesita refresco.

Serialización JSON

Conversión a JSON

func (e *Enode) ToJSON() ([]byte, error) {
return json.Marshal(e)
}

Serialización completa para almacenamiento y transmisión.

Parsing desde JSON

func FromJSON(data []byte) (*Enode, error) {
var enode Enode
err := json.Unmarshal(data, &enode)
return &enode, err
}

Parsing completo que restaura el estado del enode.

El Rol del Sistema Enode

Identidad Persistente

Los enodes proporcionan identidades que sobreviven cambios de IP y puerto, permitiendo que nodos mantengan su reputación y conexiones a través del tiempo.

Descubrimiento de Servicios

Los puertos y capacidades permiten que otros nodos sepan qué servicios ofrece cada peer:

  • p2p: Conexiones peer-to-peer
  • http: API REST
  • discovery: Protocolo de descubrimiento UDP

Sistema de Confianza

El TrustScore permite implementar reputación y prevención de ataques Sybil.

Integración con la Red

En Tablas de Enrutamiento

Los enodes se almacenan en tablas de enrutamiento Kademlia, permitiendo búsquedas eficientes por proximidad.

En Anuncios de Red

Cada anuncio de presencia incluye el enode completo del nodo emisor.

En Conexiones P2P

Los enodes se resuelven a direcciones de conexión reales cuando se establece comunicación.

El Arte de la Identidad

Cada enode representa una declaración de existencia en el ciberespacio: "Aquí estoy, así soy, y esto es lo que puedo hacer". Es la firma digital que transforma direcciones técnicas en identidades reconocibles.

Desde el primer GenerateNodeID() que crea una identidad única, hasta el ToString() que la comparte con el mundo, el sistema enode es el puente entre la individualidad técnica y la comunidad distribuida.

En KodeChain, los enodes no son solo direcciones; son las estrellas que guían a los navegantes a través del vasto océano de la red distribuida, asegurando que ningún nodo se pierda en la inmensidad digital.