En un momento dado, ¿a cuántos nodos está conectado mi nodo? A medida que aumenta el número de nodos en la red, ¿será más rápida la comunicación entre ellos?

He estado leyendo la documentación de DEVp2p y parece que no puedo entender algunos aspectos de la red. Específicamente, ¿a cuántos nodos está conectado mi nodo? ¿Este número cambia cuando realizo una transacción? A medida que hay más nodos disponibles, ¿mi nodo se conecta a los más convenientes en términos de velocidad? ¿Qué otros recursos puedo usar para obtener más información sobre la infraestructura subyacente?

He estado leyendo las siguientes páginas pero parece que no puedo entenderlo: 1 2

Respuestas (1)

Puede ver los compañeros conectados escribiendo admin.peersen la consola Geth. El número máximo de pares se establece mediante la -maxpeers nbandera en Geth. Hay un proceso de descubrimiento basado en Kademlia para encontrar nodos, luego un proceso de negociación mediante el cual determinan qué protocolos devp2p admiten (Eth, Bzz, Shh). La capa P2P monitorea la calidad de servicio de cada nodo. Calcula estadísticas, elimina o incluso prohíbe los nodos defectuosos y trata de conservar los buenos, es decir, aquellos con un alto tiempo de actividad y que responden rápidamente a los mensajes de ping. Por lo tanto, la cantidad de pares variará a medida que los nodos se desconecten o cambie la calidad del servicio. Entonces, lo mejor que puede hacer es permanecer en línea, en cuyo caso la calidad de los pares a los que se conecta mejorará gradualmente y también mejorará su calificación de calidad.


En la segunda parte de su pregunta, pregunta sobre el protocolo de cable. Yo mismo no lo entiendo completamente, sin embargo, revisé la documentación y el código fuente de varios clientes. Por lo tanto, realmente no puedo explicar todo el funcionamiento de p2p, pero así es como creo que funciona el protocolo de descubrimiento Dev p2p.

Protocolo de descubrimiento p2p de desarrollo

https://github.com/ethereum/go-ethereum/wiki/Peer-to-Peer

El protocolo RLPx, que se utiliza para crear la red P2P y proporciona la base para protocolos como ethereum sync ( 'eth' ), susurro ( 'shh' ) y swarm ( 'bzz' ).

Entonces, lo primero que debemos hacer es formar una red P2P y encontrar a nuestros pares y aquí es donde entra en juego el protocolo de descubrimiento . Los documentos dicen

RLPx utiliza un enrutamiento similar al de Kademlia , que se ha reutilizado como un protocolo de descubrimiento de vecinos p2p . El descubrimiento de RLPx utiliza claves públicas de 512 bits como ID de nodo y sha3 (id de nodo) para la métrica xor. Las características de DHT no están implementadas.

El protocolo similar a Kademlia , donde cada par mantiene una tabla de enrutamiento que contiene el conjunto completo de todos los nodos más cercanos a él, se vuelve exponencialmente más disperso a medida que aumenta la distancia. Desde el punto de vista de la mensajería, el protocolo consta de las siguientes funciones RPC basadas en UDP:

  • Ping: sondea un nodo para ver si está en línea. El receptor debe responder con un paquete Pong. Si un nodo recibe un ping, después de responder con un pong, enviará su propio ping al primer nodo.
  • Pong - Responde a un ping
  • Find_node: los paquetes Find Node se envían para localizar nodos cerca de una ID de destino dada. El receptor debe responder con un paquete Vecinos que contenga los k nodos más cercanos al objetivo que conozca.
  • Vecinos -Es la respuesta a Find Node. Contiene hasta k nodos que el remitente sabe cuáles son los más cercanos al Target solicitado.

Decodificación de un paquete UDP

El par escucha los datagramas UDP en port 30303 by default.

Los paquetes se enmarcan de la siguiente manera:

|-------|-hash----| signature | packet-type| packet-data   |
|-------|---------| ----------| ---------- |               |
|length | 32 bytes| 65 bytes  | single byte| rest of packet|
|offset | 0       | 32        | 97         | 98            |

Comprobar la integridad y autenticidad del mensaje

Definiendo messagecomo una matriz de bytes, podemos extraer estos componentes de la siguiente manera: lo primero que debe hacer es verificar que el hash sea message[:32]igual al hash SHA3-256 (también conocido como Keccak256 ) del resto del mensaje ( message[32:]). Si no es así, el mensaje está corrupto y descartamos el paquete.

En python esto es:

mdc_hash = message[:32] //bytes zero to 32
the_rest = message[32:] //bytes 32 to length-1
assert mdc == sha3_256(the_rest).digest()

Lo siguiente a comprobar es la firma. El mensaje se firma utilizando el algoritmo de firma digital de curva elíptica ( ECDSA ), específicamente secp256k1, donde la clave pública es la identificación de nodo del remitente. Dada la firma ( 65-byte compact ECDSA signature containing the recovery id as the last element.) y el mensaje que se firmó ( 32 byte sha3 hash of the message), y el conocimiento de la curva, es posible recuperar la clave pública correspondiente a la clave privada utilizada para firmar el mensaje:

Nota: El cliente de python importa una biblioteca de bitcoin para ECDSA. El cliente Go se envuelve libsecp256k1mientras que el cliente Java adapta el código del proyecto bitcoinj .

signature = message[32:97]
signed_data = sha3_256(message[97:])
remote_pubkey = crypto.ecdsa_recover(signed_data, signature)

Si no logramos recuperar una clave, soltamos el mensaje

assert len(remote_pubkey) == 512 / 8

Ahora conocemos el ID de nodo del remitente, pero aún debemos verificar el mensaje firmado y generar un error si el mensaje no se puede autenticar.

if not crypto.verify(remote_pubkey, signature, signed_data):
    raise InvalidSignature()

Averiguar qué tipo de mensaje es y decodificarlo en la estructura de mensaje adecuada

El tipo de mensaje viene dado por un solo byte message[97]y simplemente lo buscamos.

|value | 1   | 2   | 3         | 4         |
|--| --- | --- | ---| ---|
|type | ping| pong| find_node | neighbours|

Esto se puede hacer con un diccionario simple o una declaración de cambio basada en una enumeración y si el valor no está en el rango 1-4, el mensaje se descarta.

** Descifrarlo en la estructura de mensaje apropiada**

Los datos del paquete son una lista codificada de prefijo lineal recursivo (RLP) . Que es una estructura de datos específica de Ethereum para serializar matrices arbitrariamente anidadas de datos binarios, por ejemplo

[
  "some string",
  "some bytes",
  ["element1","element2"],
  [["a","b"],["c"]],
]

Entonces, primero comprendamos cómo funciona la codificación y las especificaciones nos dan el siguiente código de Python como ejemplo.

def rlp_encode(input):
if isinstance(input,str):
    if len(input) == 1 and ord(input) < 128: return input
    else: return encode_length(len(input),128) + input
elif isinstance(input,list):
    output = ''
    for item in input: output += rlp_encode(item)
    return encode_length(len(output),192) + output

def encode_length(L,offset):
if L < 56:
     return chr(L + offset)
elif L < 256**8:
     BL = to_binary(L)
     return chr(len(BL) + offset + 55) + BL
else:
     raise Exception("input too long")

def to_binary(x):
return '' if x == 0 else to_binary(int(x / 256)) + chr(x % 256)

Por lo tanto, el punto clave para la decodificación es observar el rango del primer byte y, dependiendo de eso, decodificaría el resto de la carga útil de manera diferente.

|range| meaning | encoding
|-----|----- |
|0x00, 0x7f| Single byte as its self | single byte|
|0x80, 0xb7|string 0-55 bytes long | 0x80 + length of string | rest of string|
|0xb8, 0xbf |string more than 55 bytes long| 0xb7 plus how many bytes are required to represent the length | length of the string as byte array | the string |
|0xc0, 0xf7 | payload e.g. list or list of lists the combined encoded length of which is 0-55 bytes long| length of total rlp encoded payload | RLP encoded payload|
|0xf8, 0xff | payload e.g. list or list of lists the combined encoded length of which is more than 55 bytes long| 0xf7 plus the length in bytes required to represent the length of the payload in binary form | length of the payload as byte array | RLP encoded payload |

** Tienes un paquete de ping **

Los mensajes de ping tienen la siguiente estructura, por lo que debe leer los datos de bytes en los tipos de datos correctos.

[
 uint Version, // big-endian encoded 32 bit integer protocol version reject if doesn't match own
 Endpoint to,
  Endpoint from,
  uint32 expirationTimestamp //big-endian encoded 32 bit integer reject if time() > timestamp to prevent replay attacks.
}

Por brevedad y siguiendo el código Go, definimos el tipo Endpoint como la siguiente estructura:

[
  bytes ip, // big-endian encoded or  4-byte (32-bit)  or 16-byte (128-bit) address (size determines ipv4 vs ipv6)
  uint16 udp-port, //big-endian encoded 16 bit integer
  uint16 tcp-port //big-endian encoded 16 bit integer
]

En el protocolo Kademlia original, el receptor del mensaje ping actualizaría el depósito correspondiente al remitente. Sin embargo, para protegerse contra la suplantación de direcciones IP, el protocolo Ethereum no lo hace. En su lugar, el receptor de un mensaje de ping debe simplemente responder con un mensaje de ping y luego enviar su propio ping más tarde:

[
  Byte[] replyToken, // This contains the hash of the ping packet.
  Endpoint to, //The endpoint that sent the ping message
  uint32 expirationTimestamp
]

Así que hay dos cosas a tener en cuenta aquí:

  • El replyTokenreceptor del mensaje pong lo utiliza para vincularlo a su mensaje ping y es simplemente el mdc_hashdel paquete verificado anteriormente.
  • El tocampo debe reflejar la dirección del sobre UDP del paquete de ping, no el frompunto final indicado en el pingmensaje para proporcionar una forma de descubrir la dirección externa (después de NAT).

** Tienes un paquete de pong **

Al recibir un paquete pong, el nodo verifica si se solicitó, es decir, si envió un ping a ese nodo, está esperando un pong. Luego actualiza el depósito para ese nodo.

red P2P

Al unirse por primera vez a la red, cada nodo genera un par de claves ECDSA que se guarda y utiliza en conexiones posteriores, enode://<hex node id>por ejemplo. La clave privada se utilizará para firmar mensajes y la clave pública de 512 bits para identificar el nodo. También conocerá la IP address, UDP port, and node IDde algunos nodos de arranque.

Cada nodo mantiene una tabla de enrutamiento que contiene una lista de pares conocidos conocidos como contactos.

Las tablas de enrutamiento constan de una lista (depósito) para cada bit de la ID del nodo.

La tabla de pares consta de filas, inicialmente solo una, como máximo 255 (normalmente mucho menos). Cada fila contiene como máximo k pares (estructuras de datos que contienen información sobre dicho par, como su dirección de par, dirección de red, una marca de tiempo, firma del par y posiblemente otros metadatos), donde k es un parámetro (no necesariamente global) con valores típicos entre 5 y 20.

La numeración de filas comienza con 0. Cada número de fila i contiene pares cuya dirección coincide con los primeros i bits de la dirección de este nodo. El bit i+1 de la dirección debe diferir de la dirección de este nodo en todas las filas excepto en la última.

Cada lista corresponde a una distancia específica del nodo, por lo tanto, el n -ésimo segmento difiere en el n -ésimo bit del ID del nodo del ID del nodo.

En Kademlia, la distancia está definida y XOR bit a bit, dist(pubk-A,pubk-e) = pubkA ^ pubkBpero debido a que la clave ECDSA no se distribuye uniformemente, Devp2p usadist(pubkA, pubkB) = sha(pubkA) ^ sha(pubkB)

A continuación se muestra un ejemplo de 4 bits con un tamaño de depósito de 2:

El cubo más a la derecha (el cubo 0 ) contiene el nodo en sí. El cubo más a la izquierda cubre los nodos espaciales que difieren en el bit más significativo. Observe que el espacio de direcciones potencialmente cubierto por los cubos se expande exponencialmente con la distancia.

|Bucket|4 |3 |2 |1|
|-|- |- |- |-|
|Number of notes it could include if bucket size was unlimited  |8 |4 |2 |1|
|Number of notes it can include when bucket is full |2 |2 |2 |1|

Sin embargo, los cubos tienen un tamaño fijo (en este ejemplo 2) y, por lo tanto, está claro que la tabla de enrutamiento del nodo tiene el conjunto completo de todos los nodos más cercanos, se vuelve exponencialmente más escaso a medida que aumenta la distancia. Esta característica es clave para el algoritmo de Kademlia.

oreja

  1. Agregar nodos de arranque. A la mesa.
    1. bondes decir, haga ping al lado remoto y espere un pong. Dales la oportunidad de enviarnos un ping. Si ya nos conocen, no devolverán un ping.
    2. Insertar el triple id, IP, discover port, tcp porten la base de datos.
    3. Agregue el nodo dado a su depósito correspondiente. Si el depósito tiene espacio disponible, la adición del nodo se realiza correctamente de inmediato y el nodo se agrega a la cola. De lo contrario, el nodo se agrega si el nodo activo menos recientemente en el depósito no responde a un paquete de ping.
    4. Si el nodo anterior responde, consérvelo (¿moverlo a la cola?) y no agregue el nuevo nodo.
  2. Realice una autobúsqueda para llenar los cubos.

Buscar

Lookup realiza una búsqueda en la red de nodos cercanos al objetivo dado. Se acerca al objetivo consultando los nodos que están más cerca de él en cada iteración. El objetivo dado no necesita ser un identificador de nodo real.

  1. Determine los n nodos en su propia tabla que están más cerca de la identificación dada
  2. Elija 3 Kademlia concurrency factornodos de esta lista
  3. Envíe findnodeun mensaje a estos nodos remotos y espere el mensaje de k vecinos.
  4. Para cada uno de los nuevos nodos bond, es decir, haga ping al lado remoto y espere un pong. Luego agregue al cubo correcto.
  5. Agregue estos nuevos nodos a la lista n nodos más cercanos para consultar.
  6. Cuando no haya más nodos que pedir pare.

Procesando un mensaje de Buscar nodo.

Si el par es desconocido, no procesamos el paquete. Encontramos los n nodos del armario. Los enviamos como un número de mensajes vecinos.

neighbors struct {
Nodes      []rpcNode
Expiration uint64
}
Buena respuesta. Re " Sha3-256 hash (también conocido como Keccak256) ": creo que en realidad hay una distinción sutil .
Gracias por el comentario adicional. He examinado esto y me ha ayudado a señalarme la respuesta.
Lo leí pero sigo sin entender, ¿cómo encuentran los clientes a su primer par? ¿Escanea todas las IP que intentan conectarse al puerto 30303? Porque eliminé los nodos de arranque de params/bootnodes.go, volví a compilar geth, lo reinicié y muestra algo de tráfico en el puerto 30303 en la consola tcpdump.