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
Puede ver los compañeros conectados escribiendo admin.peers
en la consola Geth. El número máximo de pares se establece mediante la -maxpeers n
bandera 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.
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:
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 message
como 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
libsecp256k1
mientras 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í:
replyToken
receptor del mensaje pong lo utiliza para vincularlo a su mensaje ping y es simplemente el mdc_hash
del paquete verificado anteriormente.to
campo debe reflejar la dirección del sobre UDP del paquete de ping, no el from
punto final indicado en el ping
mensaje 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.
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 ID
de 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 ^ pubkB
pero 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.
bond
es 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.id, IP, discover port, tcp port
en la base de datos.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.
Kademlia concurrency factor
nodos de esta listafindnode
un mensaje a estos nodos remotos y espere el mensaje de k vecinos.bond
, es decir, haga ping al lado remoto y espere un pong. Luego agregue al cubo correcto.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
}
Sotavento
usuario5135
Nulik