Mit wie vielen Knoten ist mein Knoten zu einem bestimmten Zeitpunkt verbunden? Wird die Kommunikation zwischen ihnen schneller, wenn die Anzahl der Knoten im Netzwerk zunimmt?

Ich habe die DEVp2p-Dokumentation durchgelesen und kann einige Aspekte des Netzwerks nicht verstehen. Mit wie vielen Knoten ist mein Knoten verbunden? Ändert sich diese Nummer, wenn ich eine Transaktion durchführe? Wenn mehr Knoten verfügbar werden, verbindet sich mein Knoten mit den bequemsten in Bezug auf die Geschwindigkeit? Welche anderen Ressourcen kann ich verwenden, um mehr über die zugrunde liegende Infrastruktur zu erfahren?

Ich habe die folgenden Seiten gelesen, kann es aber nicht fassen: 1 2

Antworten (1)

Sie können die verbundenen Peers sehen, indem Sie admin.peersin der Geth-Konsole eingeben. Die maximale Anzahl von Peers wird über das -maxpeers nFlag in Geth festgelegt. Es gibt einen Discovery-Prozess, der auf Kademlia basiert , um Knoten zu finden, dann einen Handshaking-Prozess, mit dem sie bestimmen, welche devp2p-Protokolle sie unterstützen (Eth, Bzz, Shh). Die P2P-Schicht überwacht die Dienstgüte jedes Knotens. Es berechnet Statistiken, verwirft oder bannt sogar schlechte Nodes und versucht, gute Nodes zu behalten, dh solche mit hoher Betriebszeit, die schnell auf Ping-Nachrichten reagieren. Die Anzahl der Peers wird daher variieren, wenn Knoten offline gehen oder sich die Dienstqualität ändert. Das Beste, was Sie tun können, ist also, online zu bleiben. In diesem Fall wird sich die Qualität der Peers, mit denen Sie sich verbinden, allmählich verbessern und auch Ihre Qualitätsbewertung wird sich verbessern.


Im zweiten Teil Ihrer Frage fragen Sie nach dem Drahtprotokoll. Ich verstehe es selbst nicht ganz, aber ich habe die Dokumentation und den Quellcode verschiedener Clients durchgesehen. Ich kann daher nicht wirklich erklären, wie p2p funktioniert, aber ich glaube, wie das Dev-P2P-Erkennungsprotokoll funktioniert.

Dev p2p-Erkennungsprotokoll

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

Das RLPx-Protokoll , das zum Erstellen des P2P-Netzwerks verwendet wird und die Grundlage für Protokolle wie Ethereum Sync( 'eth' ), Whisper( "shh" ) und Swarm( "bzz" ) bildet.

Das erste, was wir also tun müssen, ist, ein P2P-Netzwerk zu bilden und unsere Kollegen zu finden, und hier kommt das Erkennungsprotokoll ins Spiel. Sagen die Dokumente

RLPx verwendet Kademlia- ähnliches Routing , das als P2P- Nachbarerkennungsprotokoll umfunktioniert wurde . Die RLPx-Erkennung verwendet öffentliche 512-Bit-Schlüssel als Knoten-IDs und sha3 (Knoten-ID) für die xor-Metrik. DHT-Features sind nicht implementiert.

Das Kademlia -ähnliche Protokoll, bei dem jeder Peer eine Routing-Tabelle verwaltet, die den vollständigen Satz aller Knoten enthält, die ihm am nächsten sind, wird mit zunehmender Entfernung exponentiell spärlicher. Aus Messaging-Sicht besteht das Protokoll aus den folgenden UDP-basierten RPC-Funktionen:

  • Ping – Prüft einen Knoten, um zu sehen, ob er online ist. Der Empfänger sollte mit einem Pong-Paket antworten. Wenn ein Knoten einen Ping empfängt, sendet er nach der Antwort mit einem Pong seinen eigenen Ping an den ersten Knoten.
  • Pong - Antworten auf einen Ping
  • Find_node – Find Node-Pakete werden gesendet, um Knoten in der Nähe einer gegebenen Ziel-ID zu lokalisieren. Der Empfänger sollte mit einem Neighbors-Paket antworten, das die k Knoten enthält, die dem Ziel am nächsten sind, von denen er weiß.
  • Nachbarn -Ist die Antwort auf Find Node. Es enthält bis zu k Knoten, die dem Sender bekannt sind und die dem angeforderten Ziel am nächsten sind.

Dekodierung eines UDP-Pakets

Der Peer wartet auf UDP-Datagramme auf port 30303 by default.

Die Pakete sind wie folgt gerahmt:

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

Überprüfen Sie die Integrität und Authentizität der Nachricht

Durch die Definition messageals Byte-Array können wir diese Komponenten wie folgt extrahieren: Als Erstes müssen Sie überprüfen, ob der Hash message[:32]dem SHA3-256- Hash (auch bekannt als Keccak256 ) des Rests der Nachricht entspricht ( message[32:]). Wenn dies nicht der Fall ist, ist die Nachricht beschädigt und wir verwerfen das Paket.

In Python ist dies:

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

Das nächste, was zu überprüfen ist, ist die Signatur. Die Nachricht wird mit dem Elliptic Curve Digital Signature Algorithm ( ECDSA ) signiert, insbesondere mit secp256k1 , wobei der öffentliche Schlüssel die Knoten-ID des Absenders ist. Angesichts der Signatur ( 65-byte compact ECDSA signature containing the recovery id as the last element.) und der signierten Nachricht ( 32 byte sha3 hash of the message) und der Kenntnis der Kurve ist es möglich, den öffentlichen Schlüssel wiederherzustellen, der dem privaten Schlüssel entspricht, der zum Signieren der Nachricht verwendet wurde:

Hinweis: Der Python-Client importiert eine Bibliothek von Bitcoin für ECDSA. Der Go-Client umschließtlibsecp256k1 , während der Java-Client Code aus dem bitcoinj - Projekt anpasst.

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

Wenn wir einen Schlüssel nicht wiederherstellen können, verwerfen wir die Nachricht

assert len(remote_pubkey) == 512 / 8

Jetzt kennen wir die Knoten-ID des Absenders, aber wir müssen die signierte Nachricht noch verifizieren und einen Fehler ausgeben, wenn die Nachricht nicht authentifiziert werden kann.

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

Finden Sie heraus, um welche Art von Nachricht es sich handelt, und decodieren Sie sie in die entsprechende Nachrichtenstruktur

Der Nachrichtentyp wird durch ein einzelnes Byte angegeben message[97]und wir schauen einfach nach.

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

Dies kann mit einer einfachen wörterbuch- oder aufzählungsbasierten switch-Anweisung erfolgen, und wenn der Wert nicht im Bereich 1-4 liegt, wird die Nachricht verworfen.

** Entschlüsseln Sie es in die entsprechende Nachrichtenstruktur **

Die Paketdaten sind eine codierte Liste mit rekursivem linearem Präfix (RLP) . Dies ist eine Ethereum-spezifische Datenstruktur zum Serialisieren beliebig verschachtelter Arrays von Binärdaten, z

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

Lassen Sie uns also zunächst verstehen, wie die Codierung funktioniert, und die Spezifikationen geben uns den folgenden Python-Code als Beispiel.

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)

Daher ist der Schlüsselpunkt für die Dekodierung, den Bereich des ersten Bytes zu betrachten und abhängig davon würden Sie den Rest der Nutzlast anders dekodieren.

|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 |

** Sie haben ein Ping-Paket erhalten **

Die Ping-Nachrichten haben die folgende Struktur, sodass Sie die Bytes-Daten in die richtigen Datentypen einlesen müssen.

[
 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.
}

Der Kürze halber und nach dem Go-Code definieren wir den Endpoint-Typ als die folgende Struktur:

[
  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
]

Im ursprünglichen Kademlia-Protokoll würde der Empfänger der Ping-Nachricht den Bucket aktualisieren, der dem Absender entspricht. Das Ethereum-Protokoll schützt jedoch nicht vor IP-Adressen-Spoofing. Stattdessen sollte der Empfänger einer Ping-Nachricht einfach mit einer Pong-Nachricht antworten und später seinen eigenen Ping senden:

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

Hier sind also zwei Dinge zu beachten:

  • Der replyTokenwird vom Empfänger der Pong-Nachricht verwendet, um sie mit seiner Ping-Nachricht zu verknüpfen, und ist einfach der mdc_hashdes zuvor geprüften Pakets.
  • Das toFeld sollte die UDP-Envelope-Adresse des Ping-Pakets und nicht den fromin der Nachricht angegebenen Endpunkt widerspiegeln ping, um eine Möglichkeit zu bieten, die externe Adresse (nach NAT) zu ermitteln.

** Du hast ein Pong-Paket **

Beim Empfang eines Pong-Pakets prüft der Knoten, ob er angefordert wurde, dh ob er einen Ping an diesen Knoten gesendet hat und auf einen Pong wartet. Anschließend wird der Bucket für diesen Knoten aktualisiert

P2P-Netzwerk

Beim ersten Beitritt zum Netzwerk generiert jeder Knoten ein ECDSA-Schlüsselpaar, das gespeichert und beispielsweise bei nachfolgenden Verbindungen verwendet wird enode://<hex node id>. Der private Schlüssel wird zum Signieren von Nachrichten und der öffentliche 512-Bit-Schlüssel zum Identifizieren des Knotens verwendet. Es kennt auch die IP address, UDP port, and node IDeiniger Bootstrap-Knoten.

Jeder Knoten verwaltet eine Routing-Tabelle, die eine Liste bekannter Peers enthält, die als Kontakte bekannt sind.

Routing-Tabellen bestehen aus einer Liste (Bucket) für jedes Bit der Knoten-ID.

Die Peer-Tabelle besteht aus Zeilen, anfänglich nur einer, höchstens 255 (normalerweise viel weniger). Jede Zeile enthält höchstens k Peers (Datenstrukturen, die Informationen über diesen Peer enthalten, wie z. B. seine Peer-Adresse, Netzwerkadresse, einen Zeitstempel, Signatur des Peers und möglicherweise verschiedene andere Metadaten), wobei k ein Parameter ist (nicht unbedingt global) mit typischen Werten zwischen 5 und 20.

Die Zeilennummerierung beginnt mit 0. Jede Zeilennummer i enthält Peers, deren Adresse mit den ersten i Bits der Adresse dieses Knotens übereinstimmt. Das i+1. Bit der Adresse muss sich in allen Zeilen außer der letzten von dieser Knotenadresse unterscheiden.

Jede Liste entspricht einer bestimmten Entfernung vom Knoten, daher unterscheidet sich der n - te Eimer im n -ten Bit der Knoten-ID von der Knoten-ID

In Kademlia ist die Distanz definiert und bitweise XOR dist(pubk-A,pubk-e) = pubkA ^ pubkBaber da die ECDSA Schlüssel nicht einheitlich verteilt sind Devp2p nutztdist(pubkA, pubkB) = sha(pubkA) ^ sha(pubkB)

Ein 4-Bit-Beispiel mit einer Bucket-Größe von 2 ist unten dargestellt:

Der Bucket ganz rechts (der 0. Bucket ) enthält den Knoten selbst. Der am weitesten links liegende Eimer deckt die Raumknoten ab, die sich im höchstwertigen Bit unterscheiden. Beachten Sie, dass der potenziell von den Buckets abgedeckte Adressraum exponentiell mit der Entfernung wächst.

|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|

Die Buckets haben jedoch eine feste Größe (in diesem Beispiel 2) und daher ist klar, dass die Routing-Tabelle des Knotens den vollständigen Satz aller ihm am nächsten liegenden Knoten enthält und mit zunehmender Entfernung exponentiell spärlicher wird. Diese Funktion ist der Schlüssel zum Kademlia-Algorithmus.

Bootstrap

  1. Fügen Sie Bootstrap-Knoten hinzu. Zum Tisch.
    1. bonddh pingen Sie die entfernte Seite an und warten Sie auf einen Pong. Geben Sie ihnen die Chance, uns anzupingen. Wenn sie uns bereits kennen, werden sie keinen Ping zurückschicken.
    2. Fügen Sie das Tripel id, IP, discover port, tcp portin die Datenbank ein
    3. Fügen Sie dem angegebenen Knoten den entsprechenden Bucket hinzu. Wenn der Bucket Platz zur Verfügung hat, ist das Hinzufügen des Knotens sofort erfolgreich und der Knoten wird dem Schwanz hinzugefügt. Andernfalls wird der Knoten hinzugefügt, wenn der am längsten aktive Knoten im Bucket nicht auf ein Ping-Paket antwortet.
    4. Wenn der alte Knoten antwortet, behalten Sie ihn (bewegen Sie ihn zum Ende?) und fügen Sie den neuen Knoten nicht hinzu.
  2. Führen Sie eine Selbstsuche durch, um die Eimer zu füllen.

Sieh nach oben

Lookup führt eine Netzwerksuche nach Knoten in der Nähe des angegebenen Ziels durch. Es nähert sich dem Ziel, indem es bei jeder Iteration Knoten abfragt, die ihm näher sind. Das angegebene Ziel muss keine tatsächliche Knotenkennung sein.

  1. Bestimme die n Knoten in einer eigenen Tabelle, die der gegebenen ID am nächsten sind
  2. Wählen Sie 3 Kademlia concurrency factorKnoten aus dieser Liste aus
  3. Senden Sie findnodeeine Nachricht an diese entfernten Knoten und warten Sie auf die Nachricht von k Nachbarn.
  4. Ping also für jeden der neuen Knoten bonddie entfernte Seite und warte auf ein Pong. Dann zum richtigen Eimer hinzufügen.
  5. Fügen Sie diese neuen Knoten zu der Liste der n nächstgelegenen abzufragenden Knoten hinzu.
  6. Wenn keine Knoten mehr zu fragen sind, stoppe.

Verarbeiten einer Find-Node-Nachricht.

Wenn der Peer unbekannt ist, verarbeiten wir das Paket nicht. Wir finden die n Schrankknoten und senden sie als eine Anzahl von Nachbarnachrichten.

neighbors struct {
Nodes      []rpcNode
Expiration uint64
}
Gute Antwort. Zu " SHA3-256-Hash (auch bekannt als Keccak256) " - ich denke, es gibt tatsächlich einen subtilen Unterschied .
Vielen Dank für den ergänzenden Kommentar. Ich habe das durchgesehen und es hat mir geholfen, die Antwort zu finden.
Ich habe es gelesen, aber ich verstehe immer noch nicht, wie finden die Kunden ihren ersten Kollegen? Scannt es alle IPs, die versuchen, sich mit Port 30303 zu verbinden? Denn ich habe Bootstrap-Knoten aus params/bootnodes.go entfernt, geth neu kompiliert, neu gestartet und es zeigt etwas Verkehr auf dem 30303-Port in der tcpdump-Konsole.