Análisis del código fuente de Ethereum: descubrimiento de nodos p2p
La función de descubrimiento de nodos involucra principalmente varias estructuras de datos, como Servidor \ Tabla \ udp. Tienen sus propios bucles de respuesta a eventos y la función de descubrimiento de nodos se completa con su cooperación. Entre ellos, cada cliente Ethereum ejecutará un servidor localmente después del inicio y tratará los nodos adyacentes en la topología de la red como Nodo, mientras que Table es el contenedor de Node y udp es responsable de mantener la conexión subyacente. Lo siguiente se centra en describir sus campos importantes y partes clave del procesamiento de bucles de eventos.
PrivateKey: la clave privada de este nodo, utilizada para negociaciones de protocolo de enlace al establecer con otros nodos
Protocolos: todos los protocolos de capa superior admitidos
StaticNodes: predeterminado Static Peer, cuando se inicia el nodo, primero iniciará conexiones con ellos y establecerá relaciones de vecino
newTransport: la implementación de la capa de transporte inferior, que define el método de cifrado y descifrado de datos durante el proceso de protocolo de enlace. la implementación de la capa utiliza newRLPX( ) rlpx creado por Información, incluido el número de versión del nodo local y los protocolos de capa superior admitidos
addpeer: una vez completado el protocolo de enlace de conexión, el proceso de conexión notifica al servidor a través de este canal
El bucle de escucha del servidor inicia el socket de escucha subyacente. Cuando se recibe una solicitud de conexión, se llama a setupConn() después de aceptar para iniciar el proceso de establecimiento de la conexión.
Función y procesamiento de eventos principales del servidor bucle de implementación
El nodo representa de forma única un nodo en la red
IP - dirección IP
UDP/TCP - número de puerto UDP/TCP utilizado para la conexión p>
ID: identifica de forma única un nodo en la red Ethereum, esencialmente una clave pública de curva elíptica (PublicKey), correspondiente a la clave privada del servidor. La dirección IP de un nodo no es necesariamente fija, pero el ID es único.
sha: se utiliza para calcular la distancia entre nodos
La tabla se utiliza principalmente para gestionar el establecimiento, actualización y eliminación de conexiones entre este nodo y otros nodos
depósito - Todos los pares se colocan en diferentes depósitos según su distancia del nodo. Para obtener más información, consulte el mantenimiento del nodo posterior
refrescoReq - Canal de solicitud de actualización de la tabla
Bucle de eventos principales de la tabla. , principal responsable de controlar los procesos de actualización y revalidación.
refresh.C: un temporizador que inicia el proceso de actualización del par con regularidad (30 segundos).
refreshReq: recibe notificaciones de otros subprocesos para actualizar la conexión del par entregada a la tabla cuando se envía la notificación. se recibe Inicie la actualización Para obtener más detalles, consulte la relación de actualización de vecinos más adelante.
revalidate.C: un temporizador que vuelve a verificar periódicamente la validez del nodo conectado. Para obtener más detalles, consulte la vida posterior. detección
udp es responsable de El control de mensajes subyacente de la comunicación entre nodos es el componente subyacente del protocolo Kademlia ejecutado por Table
conn: la conexión del puerto de escucha subyacente
addpending: el canal utilizado por udp para recibir pendientes.
El escenario de uso es: cuando enviamos un paquete a otro nodo, podemos esperar recibir una respuesta de él. Pendiente se utiliza para registrar una respuesta que aún no ha llegado. Por ejemplo, cuando enviamos un paquete ping, siempre esperamos que la otra parte responda con un paquete ping. En este momento, se puede construir una estructura pendiente, que contiene la información del paquete pong que se espera recibir y la función de devolución de llamada correspondiente, y el pendiente se entrega al canal UDP. Después de recibir el pong correspondiente, udp ejecuta la devolución de llamada preestablecida.
gotreply: udp es un canal utilizado para recibir respuestas de otros nodos. Junto con el addpending anterior, después de recibir la respuesta, atraviesa la lista vinculada pendiente existente para ver si hay un pendiente coincidente.
Tabla: es la misma tabla que ntab en el servidor
El bucle de procesamiento de udp es responsable de controlar el envío ascendente y el control de envío y recepción de mensajes
La aceptación subyacente del ciclo de paquetes de datos udp, responsable de recibir paquetes de otros nodos.
Ethereum utiliza el protocolo de almacenamiento de enrutamiento distribuido Kademlia para mantener la topología de la red. Para comprender este protocolo, se recomienda leer Fácil de entender. Distribución primero. Se puede encontrar información más autorizada en wiki. En general, este protocolo:
Todos los depósitos se guardan mediante la estructura de tabla en el código fuente. La estructura del depósito es la siguiente
Los nodos se pueden convertir en entradas y reemplazos si se valida. falla para un nodo de entradas, entonces será reemplazado por un nodo que estaba originalmente en la matriz de reemplazos.
La detección de validez consiste en utilizar mensajes de ping para detectar actividad. Table.loop() inicia un temporizador (0 ~ 10 s), selecciona aleatoriamente un depósito con regularidad y envía un mensaje de ping al nodo al final de sus entradas. Si la otra parte responde con pong, la detección es exitosa.
Table.loop() actualizará las relaciones de vecinos (descubre nuevos vecinos) regularmente (se agota el tiempo de espera del temporizador) o irregularmente (recibe refrescoReq), los cuales llaman al método doRefresh(), que encuentra los nodos más cercanos a sí mismo y tres nodos aleatorios en la red.
El método lookup() de Table se utiliza para implementar la búsqueda de nodos de destino. Su implementación es el protocolo Kademlia, que se acerca al objetivo paso a paso a través de retransmisiones entre nodos.
Cuando se inicia un nodo, primero iniciará una conexión con el nodo estático configurado. El proceso de inicio de la conexión se llama Dial. Este proceso se rastrea en el código fuente mediante la creación de dialTask.
dialTask representa una vez La tarea de iniciar activamente conexiones a otros nodos
Cuando se inicia el servidor, se llamará a newDialState () para inicializar un lote de dialTasks basado en los StaticNodes preconfigurados, y estas tareas se iniciará en el método Server.run().
El proceso de marcado necesita conocer la dirección IP del nodo de destino (dest). Si no la conoce, primero debe usar recolve() para resolver la dirección IP de destino. Es decir, primero utilice el protocolo Kademlia para encontrar el nodo de destino en la red.
Después de obtener la IP del nodo de destino, el siguiente paso es establecer la conexión. Esto es establecer la conexión a través de dialTask.dial()
El proceso de establecimiento de conexión. se divide en dos etapas implementadas en SetupConn()
La primera fase es el establecimiento de la clave ECDH:
La segunda fase es el protocolo de enlace, el intercambio de protocolos de capa superior compatibles.
Si ambos apretones de manos pasan, dialTask enviará información del par al canal addpeer del servidor