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-peerhttp: API RESTdiscovery: 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.