Öffentlicher Test: Lösung für Datenschutz und Skalierbarkeit in Ethereum

Blockchain ist eine innovative Technologie, die verspricht, viele Bereiche des menschlichen Lebens zu verbessern. Es überträgt reale Prozesse und Produkte in den digitalen Raum, stellt die Geschwindigkeit und Zuverlässigkeit von Finanztransaktionen sicher, senkt deren Kosten und ermöglicht Ihnen die Erstellung moderner DAPP-Anwendungen mithilfe intelligenter Verträge in dezentralen Netzwerken.

Angesichts der vielen Vorteile und vielfältigen Verwendungsmöglichkeiten der Blockchain mag es seltsam erscheinen, dass diese vielversprechende Technologie noch nicht alle Sektoren durchdrungen hat. Das Problem ist, dass moderne dezentrale Blockchains nicht skalierbar sind. Ethereum verarbeitet ungefähr 20 Transaktionen pro Sekunde, was nicht ausreicht, um die Anforderungen des heutigen dynamischen Geschäfts zu erfüllen. Gleichzeitig trauen sich Unternehmen, die Blockchain-Technologie einsetzen, nicht, Ethereum wegen seines hohen Schutzes vor Hacking und Netzwerkfehlern aufzugeben.

Um Dezentralisierung, Sicherheit und Skalierbarkeit in der Blockchain zu gewährleisten und damit das Skalierbarkeitstrilemma zu lösen, hat das Opporty-Entwicklungsteam Plasma Cash erstellt - eine untergeordnete Kette, die aus einem intelligenten Vertrag und einem privaten Netzwerk auf der Basis von Node.js besteht und ihren Status regelmäßig an die Stammkette überträgt ( Ethereum).



Schlüsselprozesse bei Plasma Cash


1. Der Benutzer nennt die Funktion des Smart Contract "Einzahlung" und überweist den Betrag in der ETH, den er in den Plasma Cash Token einlegen möchte. Die Smart Contract-Funktion erstellt ein Token und generiert ein Ereignis darüber.

2. Plasma Cash-Knoten, die die Ereignisse des Smart-Vertrags abonniert haben, erhalten ein Ereignis über die Erstellung einer Einzahlung und fügen dem Pool eine Transaktion über die Erstellung eines Tokens hinzu.

3. In regelmäßigen Abständen nehmen spezielle Plasma Cash-Knoten alle Transaktionen aus dem Pool (bis zu 1 Million) und bilden einen Block daraus, berechnen den Merkle-Baum und dementsprechend den Hash. Dieser Block wird zur Überprüfung an andere Knoten gesendet. Die Knoten prüfen, ob der Merkle-Hash gültig ist, ob die Transaktionen gültig sind (z. B. wenn der Absender des Tokens sein Eigentümer ist). Nach Überprüfung des Blocks ruft der Knoten die Funktion "submitBlock" des Smart Contract auf, in der die Nummer und der Merkle-Hash des Blocks in der Trace-Kette gespeichert sind. Ein intelligenter Vertrag generiert ein Ereignis über das erfolgreiche Hinzufügen eines Blocks. Transaktionen werden aus dem Pool gelöscht.

4. Die Knoten, die das Ereignis über die Übermittlung des Blocks erhalten haben, beginnen, die Transaktionen anzuwenden, die dem Block hinzugefügt wurden.

5. Irgendwann möchte der Eigentümer (oder Nicht-Eigentümer) des Tokens ihn von Plasma Cash abheben. Zu diesem Zweck ruft er die Funktion "startExit" auf und übergibt Informationen zu den letzten beiden Transaktionen auf dem Token, die bestätigen, dass er der Eigentümer des Tokens ist. Der Smart-Vertrag überprüft mithilfe des Merkle-Hashs, ob Transaktionen in Blöcken vorliegen, und sendet ein Token an die Ausgabe, was in zwei Wochen geschehen wird.

6. Wenn der Token-Auszahlungsvorgang bei Verstößen aufgetreten ist (der Token wurde nach Beginn des Auszahlungsvorgangs ausgegeben oder war der Token vor dem Auszug bereits ein Fremder), kann der Inhaber des Tokens den Auszug innerhalb von zwei Wochen widerlegen.



Datenschutz wird auf zwei Arten erreicht.


1. Die Stammkette weiß nichts über Transaktionen, die innerhalb der untergeordneten Kette gebildet und weitergeleitet werden. Es bleiben Informationen darüber, wer die ETH zu / von Plasma Cash gestartet und zurückgezogen hat.

2. Mit der untergeordneten Kette können Sie anonyme Transaktionen mit zk-SNARKs organisieren.

Technologischer Stapel


  • NodeJS
  • Redis
  • Ethereum
  • Soild

Testen


Bei der Entwicklung von Plasma Cash haben wir die Geschwindigkeit des Systems getestet und die folgenden Ergebnisse erzielt:

  • Dem Pool werden bis zu 35.000 Transaktionen pro Sekunde hinzugefügt.
  • Im Block können bis zu 1.000.000 Transaktionen gespeichert werden.

Tests wurden auf den folgenden 3 Servern durchgeführt:

1. Intel Core i7-6700 Quad-Core Skylake inkl. NVMe SSD - 512 GB, 64 GB DDR4-RAM
Es wurden 3 validierte Plasma Cash-Knoten ausgelöst.

2. AMD Ryzen 7 1700X Octa-Core „Summit Ridge“ (Zen), SATA-SSD - 500 GB, 64 GB DDR4-RAM
Der Ropsten testnet ETH-Knoten wurde angehoben.
Es wurden 3 validierte Plasma Cash-Knoten ausgelöst.

3. Intel Core i9-9900K Octa-Core inkl. NVMe SSD - 1 TB, 64 GB DDR4-RAM
1 Plasma-Cash-Knoten senden wurde ausgelöst.
Es wurden 3 validierte Plasma Cash-Knoten ausgelöst.
Ein Test wurde gestartet, um Transaktionen zum Plasma Cash-Netzwerk hinzuzufügen.

Insgesamt: 10 Plasma Cash-Knoten in einem privaten Netzwerk.

Test 1


Es gibt ein Limit von 1 Million Transaktionen pro Block. Daher fallen 1 Million Transaktionen in 2 Blöcke (da das System es schafft, an den Transaktionen teilzunehmen und diese zu senden, während sie gesendet werden).


Ausgangszustand: letzter Block # 7; 1 Million Transaktionen und Token werden in der Datenbank gespeichert.

00:00 - Starten Sie das Transaktionsgenerierungsskript
01:37 - 1 Million Transaktionen wurden erstellt und das Senden an den Knoten begann
01:46 - Der Submit-Knoten hat 240.000 Transaktionen aus dem Pool übernommen und bildet Block 8. Wir sehen auch, dass 320.000 Transaktionen in 10 Sekunden zum Pool hinzugefügt werden
01:58 - Block 8 wird signiert und zur Validierung gesendet
02:03 - Block 8 wird validiert und die Funktion "submitBlock" des Smart-Vertrags mit dem Merkle-Hash und der Blocknummer wird aufgerufen
02:10 - Das Demo-Skript hat funktioniert und 1 Million Transaktionen in 32 Sekunden gesendet
02:33 - Die Knoten erhielten Informationen darüber, dass Block 8 zur Stammkette hinzugefügt wurde, und begannen, 240.000 Transaktionen durchzuführen
02:40 - 240.000 Transaktionen wurden aus dem Pool gelöscht, die sich bereits in Block 8 befinden
02:56 - Submit Node nahm die verbleibenden 760.000 Transaktionen aus dem Pool und begann mit der Berechnung des Merkle-Hash- und Sign-Blocks Nr. 9
03:20 - Alle Knoten enthalten 1 Mio. 240.000 Transaktionen und Token
03:35 - Block 9 wird signiert und zur Validierung an andere Knoten gesendet
03:41 - Ein Netzwerkfehler ist aufgetreten
04:40 - Nach einer Zeitüberschreitung wurde das Warten auf die Validierung von Block 9 gestoppt
04:54 - Der Submit-Knoten nahm die verbleibenden 760.000 Transaktionen aus dem Pool und begann mit der Berechnung des Merkle-Hash- und Sign-Blocks Nr. 9
05:32 - Block 9 wird signiert und zur Validierung an andere Knoten gesendet
05:53 - Block 9 wird validiert und an die Stammkette gesendet
06:17 - Knoten erhielten Informationen darüber, dass Block 9 zur Stammkette hinzugefügt wurde, und begannen, 760.000 Transaktionen durchzuführen
06:47 - Der Pool wurde von Transaktionen in Block 9 gelöscht
09:06 - Alle Knoten enthalten 2 Millionen Transaktionen und Token

Test 2


Es gibt ein Limit von 350.000 pro Block. Als Ergebnis haben wir 3 Blöcke.


Ausgangszustand: letzter Block # 9; 2 Millionen Transaktionen und Token in der Datenbank gespeichert

00:00 - Das Skript zur Transaktionsgenerierung wird bereits ausgeführt
00:44 - 1 Million Transaktionen wurden erstellt und das Senden an den Knoten begann
00:56 - Der Submit-Knoten hat 320.000 Transaktionen aus dem Pool übernommen und bildet Block Nr. 10. Wir sehen auch, dass 320.000 Transaktionen in 10 Sekunden zum Pool hinzugefügt werden
01:12 - Block Nr. 10 wird signiert und zur Validierung an andere Knoten gesendet
01:18 - Das Demo-Skript hat funktioniert und 1 Million Transaktionen in 34 Sekunden gesendet
01:20 - Block 10 wird validiert und an die Stammkette gesendet
01:51 - Alle Knoten haben Informationen von der Stammkette erhalten, dass Block 10 hinzugefügt wurde, und sie beginnen, 320.000 Transaktionen anzuwenden
02:01 - Der Pool wurde für 320.000 Transaktionen gelöscht, die zu Block 10 hinzugefügt wurden
02:15 - Der Submit-Knoten hat 350.000 Transaktionen aus dem Pool übernommen und bildet Block Nr. 11
02:34 - Block Nr. 11 wird signiert und zur Validierung an andere Knoten gesendet
02:51 - Block Nr. 11 wird validiert und an die Stammkette gesendet
02:55 - Der letzte Knoten hat Transaktionen aus Block 10 ausgeführt
10:59 - Eine sehr lange Zeit in der Stammkette wurde eine Transaktion mit einer Block # 9-Übermittlung ausgeführt, aber sie wurde abgeschlossen und alle Knoten erhielten Informationen darüber und begannen mit der Ausführung von 350.000 Transaktionen
11:05 - Der Pool wurde für 320.000 Transaktionen gelöscht, die zu Block 11 hinzugefügt wurden
12:10 - Alle Knoten enthalten 1 Million 670.000 Transaktionen und Token
12:17 - Der Submit-Knoten hat 330.000 Transaktionen aus dem Pool übernommen und bildet Block Nr. 12
12:32 - Block Nr. 12 wird signiert und zur Validierung an andere Knoten gesendet
12:39 - Block Nr. 12 wird validiert und an die Stammkette gesendet
13:44 - Alle Knoten haben Informationen von der Stammkette erhalten, dass Block Nr. 12 hinzugefügt wurde, und beginnen, 330.000 Transaktionen anzuwenden
14:50 - Alle Knoten enthalten 2 Millionen Transaktionen und Token

Test 3


Auf dem ersten und zweiten Server wurde ein Validierungsknoten durch einen Übermittlungsknoten ersetzt.


Ausgangszustand: letzter Block # 84; 0 Transaktionen und Token werden in der Datenbank gespeichert

00:00 - Es werden 3 Skripte gestartet, die 1 Million Transaktionen generieren und senden
01:38 - 1 Million Transaktionen wurden erstellt und das Senden an Submit Node # 3 begann
01:50 - Submit Node # 3 hat 330.000 Transaktionen aus dem Pool genommen und bildet Block # 85 (f21). Wir sehen auch, dass 350.000 Transaktionen in 10 Sekunden zum Pool hinzugefügt werden
01:53 - 1 Million Transaktionen wurden erstellt und das Senden an Submit Node # 1 begann
01:50 - Submit Node # 3 hat 330.000 Transaktionen aus dem Pool genommen und bildet Block # 85 (f21). Wir sehen auch, dass 350.000 Transaktionen in 10 Sekunden zum Pool hinzugefügt werden
02:01 - Submit Node # 1 hat 250.000 Transaktionen aus dem Pool genommen und bildet Block # 85 (65e)
02:06 - Block Nr. 85 (f21) wird signiert und zur Validierung an andere Knoten gesendet
02:08 - Das Demo-Skript für Server 3 funktioniert nicht mehr und hat in 30 Sekunden 1 Mio. Transaktionen gesendet
02:14 - Block # 85 (f21) wird validiert und an die Wurzelkette gesendet
02:19 - Block Nr. 85 (65e) wird signiert und zur Validierung an andere Knoten gesendet
02:22 - 1 Million Transaktionen wurden erstellt und das Senden an Submit Node # 2 begann
02:27 - Block # 85 (65e) wird validiert und an die Wurzelkette gesendet
02:29 - Submit Node # 2 hat aus dem Pool 111855 Transaktionen entnommen und Block # 85 (256) gebildet.
02:36 - Block Nr. 85 (256) wird signiert und zur Validierung an andere Knoten gesendet
02:36 - Das Demo-Skript von Server Nr. 1 hat seine Arbeit beendet und 1 Mio. Transaktionen in 42,5 Sekunden gesendet
02:38 - Block # 85 (256) wird validiert und an die Stammkette gesendet
03:08 - Das Serverskript Nr. 2, das in 47 Sekunden 1 Million Transaktionen gesendet hat, hat seine Arbeit beendet
03:38 - Alle Knoten haben Informationen von der Stammkette erhalten, dass die Blöcke # 85 (f21), # 86 (65e), # 87 (256) hinzugefügt wurden und beginnen, 330k, 250k, 111855 Transaktionen anzuwenden
03:49 - Der Pool wurde bei 330.000, 250.000, 111855 Transaktionen gelöscht, die zu den Blöcken Nr. 85 (f21), Nr. 86 (65e), Nr. 87 (256) hinzugefügt wurden.
03:59 - Übermittlungsknoten Nr. 1 aus dem Pool 888145 Transaktionen und Formularblock Nr. 88 (214), Übermittlungsknoten Nr. 2 aus dem Pool 750.000 Transaktionen und Formularblock Nr. 88 (50a), Übermittlungsknoten Nr. 3 aus dem Pool 670.000 Transaktionen und bildet Block # 88 (d3b)
04:44 - Block # 88 (d3b) wird signiert und zur Validierung an andere Knoten gesendet
04:58 - Block # 88 (214) wird signiert und zur Validierung an andere Knoten gesendet
05:11 - Block # 88 (50a) wird signiert und zur Validierung an andere Knoten gesendet
05:11 - Block # 85 (d3b) wird validiert und an die Wurzelkette gesendet
05:36 - Block Nr. 85 (214) wird validiert und an die Stammkette gesendet
05:43 - Alle Knoten haben Informationen von der Stammkette erhalten, dass die Blöcke # 88 (d3b), # 89 (214) hinzugefügt wurden und beginnen, 670.000, 750.000 Transaktionen anzuwenden
06:50 - Aufgrund einer Unterbrechung wurde Block Nr. 85 (50a) nicht validiert
06:55 - Submit Node # 2 hat 888145 Transaktionen aus dem Pool genommen und bildet Block # 90 (50a)
08:14 - Block Nr. 90 (50a) wird signiert und zur Validierung an andere Knoten gesendet
09:04 - Block Nr. 90 (50a) wird validiert und an die Stammkette gesendet
11:23 - Alle Knoten erhielten Informationen von der Stammkette, dass Block Nr. 90 (50a) hinzugefügt wurde, und 888145 Transaktionen wurden angewendet. Gleichzeitig hat Server Nr. 3 lange Zeit Transaktionen aus den Blöcken Nr. 88 (d3b), Nr. 89 (214) angewendet.
12:11 - Alle Pools sind leer
13:41 - Alle Serverknoten Nr. 3 enthalten 3 Millionen Transaktionen und Token
14:35 - Alle Serverknoten Nr. 1 enthalten 3 Millionen Transaktionen und Token
19:24 - Alle Serverknoten Nr. 2 enthalten 3 Millionen Transaktionen und Token

Hindernisse


Während der Entwicklung von Plasma Cash sind wir auf folgende Probleme gestoßen, die wir nach und nach gelöst haben und lösen:

1. Der Konflikt der Interaktion verschiedener Funktionen des Systems. Beispielsweise blockierte die Funktion zum Hinzufügen von Transaktionen zum Pool die Übermittlung und Validierung von Blöcken und umgekehrt, was zu einem Geschwindigkeitsabfall führte.

2. Es war nicht sofort klar, wie eine große Anzahl von Transaktionen gesendet und gleichzeitig die Kosten für die Datenübertragung minimiert werden sollten.

3. Es war nicht klar, wie und wo Daten gespeichert werden sollten, um hohe Ergebnisse zu erzielen.

4. Es war nicht klar, wie ein Netzwerk zwischen Knoten organisiert werden soll, da die Blockgröße mit 1 Million Transaktionen etwa 100 MB beträgt.

5. Wenn Sie im Single-Thread-Modus arbeiten, wird die Verbindung zwischen Knoten unterbrochen, wenn lange Berechnungen durchgeführt werden (z. B. Erstellen eines Merkle-Baums und Berechnen seines Hashs).

Wie sind wir damit umgegangen?


Die erste Version des Plasma Cash-Knotens war eine Art Mähdrescher, der alles gleichzeitig ausführen konnte: Transaktionen akzeptieren, Blöcke senden und validieren, eine API für den Zugriff auf Daten bereitstellen. Da NodeJS ursprünglich Single-Threaded war, blockierte die Berechnungsfunktion für den schweren Merkle-Baum die Funktion zum Hinzufügen von Transaktionen. Wir haben zwei Möglichkeiten zur Lösung dieses Problems gesehen:

1. Führen Sie mehrere NodeJS-Prozesse aus, von denen jeder bestimmte Funktionen ausführt.

2. Verwenden Sie worker_threads und fügen Sie die Ausführung des Codes in Threads ein.

Infolgedessen haben wir beide Optionen gleichzeitig verwendet: Einen Knoten logisch in drei Teile unterteilt, die separat, aber gleichzeitig synchron arbeiten können

1. Senden Sie einen Knoten, der Transaktionen akzeptiert, an den Pool und erstellen Sie Blöcke.

2. Überprüfen des Knotens, der die Gültigkeit der Knoten überprüft.

3. Knoten-API - Bietet eine API für den Zugriff auf Daten.

Gleichzeitig können Sie mit cli über einen Unix-Socket eine Verbindung zu jedem Knoten herstellen.

Schwere Operationen wie die Berechnung des Merkle-Baumes haben wir in einem separaten Stream durchgeführt.

So haben wir den normalen Betrieb aller Plasma Cash-Funktionen gleichzeitig und fehlerfrei erreicht.

Sobald das System funktionierte, haben wir mit dem Testen der Geschwindigkeit begonnen und leider unbefriedigende Ergebnisse erzielt: 5.000 Transaktionen pro Sekunde und bis zu 50.000 Transaktionen in einem Block. Ich musste herausfinden, was falsch implementiert wurde.

Zunächst haben wir begonnen, den Kommunikationsmechanismus mit Plasma Cash zu testen, um die Spitzenleistung des Systems herauszufinden. Zuvor haben wir geschrieben, dass der Plasma Cash-Knoten eine Unix-Socket-Schnittstelle bietet. Es war ursprünglich in Textform. JSON-Objekte wurden mit "JSON.parse ()" und "JSON.stringify ()" gesendet.

```json { "action": "sendTransaction", "payload":{ "prevHash": "0x8a88cc4217745fd0b4eb161f6923235da10593be66b841d47da86b9cd95d93e0", "prevBlock": 41, "tokenId": "57570139642005649136210751546585740989890521125187435281313126554130572876445", "newOwner": "0x200eabe5b26e547446ae5821622892291632d4f4", "type": "pay", "data": "", "signature": "0xd1107d0c6df15e01e168e631a386363c72206cb75b233f8f3cf883134854967e1cd9b3306cc5c0ce58f0a7397ae9b2487501b56695fe3a3c90ec0f61c7ea4a721c" } } ``` 

Wir haben die Übertragungsgeschwindigkeit solcher Objekte gemessen und ~ 130.000 pro Sekunde erhalten. Sie versuchten, die Standardfunktionen durch json zu ersetzen, aber die Leistung verbesserte sich nicht. Es muss einen V8-Motor geben, der für diese Operationen gut optimiert ist.

Die Arbeit mit Transaktionen, Token und Blöcken wurde durch Klassen ausgeführt. Beim Erstellen solcher Klassen hat sich die Leistung um das Zweifache verringert, was darauf hinweist, dass OOP für uns nicht geeignet ist. Ich musste alles rein funktional umschreiben.

Schreiben Sie in die Datenbank


Ursprünglich wurde Redis für die Datenspeicherung als eine der produktivsten Lösungen ausgewählt, die unsere Anforderungen erfüllen: Schlüsselwertspeicherung, Arbeit mit Hash-Tabellen und vieles mehr. Wir haben den Redis-Benchmark gestartet und ~ 80.000 Operationen pro Sekunde in einem Pipelining-Modus durchgeführt.

Für eine hohe Leistung haben wir Redis feiner abgestimmt:

  • Unix-Socket-Verbindung hergestellt.
  • Deaktivieren Sie das Speichern des Status auf der Festplatte (aus Gründen der Zuverlässigkeit können Sie das Replikat konfigurieren und bereits in einem separaten Redis auf der Festplatte speichern).

In Redis ist ein Pool eine Hash-Tabelle, da wir die Fähigkeit benötigen, alle Transaktionen in einer Anfrage zu empfangen und Transaktionen einzeln zu löschen. Wir haben versucht, eine reguläre Liste zu verwenden, diese funktioniert jedoch beim Entladen der gesamten Liste langsamer.

Mit der Standard-NodeJS-Bibliothek erzielten Redis-Bibliotheken eine Leistung von 18.000 Transaktionen pro Sekunde. Die Geschwindigkeit sank 9 Mal.

Da der Benchmark uns die Möglichkeiten deutlich fünfmal deutlicher zeigte, begannen sie zu optimieren. Wir haben die Bibliothek auf ioredis umgestellt und eine Leistung von 25.000 pro Sekunde erzielt. Wir haben Transaktionen einzeln mit dem Befehl `hset` hinzugefügt. Daher haben wir in Redis viele Anfragen generiert. Es gab die Idee, Transaktionen zu Bundles zusammenzuführen und mit einem hmset-Befehl zu senden. Das Ergebnis ist 32k pro Sekunde.

Aus mehreren Gründen, die im Folgenden beschrieben werden, arbeiten wir mit Daten unter Verwendung von "Puffer". Wie sich herausstellte, können Sie zusätzliche Leistung erzielen, wenn Sie diese vor dem Schreiben in Text ("buffer.toString (" hex ")") übersetzen. Somit wurde die Geschwindigkeit auf 35 km / s erhöht. Im Moment haben wir beschlossen, die weitere Optimierung auszusetzen.

Wir mussten auf das Binärprotokoll umsteigen, weil:

1. Das System berechnet häufig Hashes, Signaturen usw. und benötigt dafür Daten in `Buffer.

2. Bei der Übertragung zwischen Diensten wiegen Binärdaten weniger als Text. Wenn Sie beispielsweise einen Block mit 1 Million Transaktionen senden, können die Daten im Text mehr als 300 Megabyte belegen.

3. Kontinuierliche Datenkonvertierung beeinträchtigt die Leistung.

Aus diesem Grund haben wir unser eigenes Binärprotokoll zum Speichern und Übertragen von Daten zugrunde gelegt, das auf der Grundlage der wunderbaren Binärdatenbibliothek entwickelt wurde.

Als Ergebnis haben wir die folgenden Datenstrukturen:

- Transaktion


  ```json { prevHash: BD.types.buffer(20), prevBlock: BD.types.uint24le, tokenId: BD.types.string(null), type: BD.types.uint8, newOwner: BD.types.buffer(20), dataLength: BD.types.uint24le, data: BD.types.buffer(({current}) => current.dataLength), signature: BD.types.buffer(65), hash: BD.types.buffer(32), blockNumber: BD.types.uint24le, timestamp: BD.types.uint48le, } ``` 

- Token


  ```json { id: BD.types.string(null), owner: BD.types.buffer(20), block: BD.types.uint24le, amount: BD.types.string(null), } ``` 

- Blockieren


  ```json { number: BD.types.uint24le, merkleRootHash: BD.types.buffer(32), signature: BD.types.buffer(65), countTx: BD.types.uint24le, transactions: BD.types.array(Transaction.Protocol, ({current}) => current.countTx), timestamp: BD.types.uint48le, } ``` 

Mit den üblichen Befehlen "BD.encode (Block, Protokoll) .slice ()" und "BD.decode (Puffer, Protokoll)" konvertieren wir die Daten in "Puffer", um sie in Redis zu speichern oder auf einen anderen Knoten zu übertragen und die Daten zurückzurufen.

Wir haben auch 2 binäre Protokolle für die Übertragung von Daten zwischen Diensten:

- Protokoll für die Interaktion mit Plasma Node über Unix-Socket

  ```json { type: BD.types.uint8, messageId: BD.types.uint24le, error: BD.types.uint8, length: BD.types.uint24le, payload: BD.types.buffer(({node}) => node.length) } ``` 

wo:

  • `type` - auszuführende Aktion, zum Beispiel 1 - sendTransaction, 2 - getTransaction;
  • `payload` - Daten, die an die entsprechende Funktion übertragen werden sollen;
  • `messageId` - Nachrichten-ID, damit die Antwort identifiziert werden kann.

- Protokoll der Interaktion zwischen Knoten

  ```json { code: BD.types.uint8, versionProtocol: BD.types.uint24le, seq: BD.types.uint8, countChunk: BD.types.uint24le, chunkNumber: BD.types.uint24le, length: BD.types.uint24le, payload: BD.types.buffer(({node}) => node.length) } ``` 

wo:

  • `code` - Nachrichtencode, zum Beispiel 6 - PREPARE_NEW_BLOCK, 7 - BLOCK_VALID, 8 - BLOCK_COMMIT;
  • `versionProtocol` - Protokollversion, da Knoten mit unterschiedlichen Versionen im Netzwerk ausgelöst werden können und auf unterschiedliche Weise arbeiten können;
  • `seq` - Nachrichtenkennung;
  • `countChunk` und` chunkNumber` werden benötigt, um große Nachrichten aufzuteilen.
  • `Länge` und` Nutzlast` die Länge und die Daten selbst.

Da wir die Daten zuvor eingegeben haben, ist das endgültige System viel schneller als die "rlp" -Bibliothek von Ethereum. Leider konnten wir es noch nicht ablehnen, da es notwendig ist, den Smart-Vertrag abzuschließen, den wir in Zukunft planen.

Wenn es uns gelungen ist, eine Geschwindigkeit von 35.000 Transaktionen pro Sekunde zu erreichen, müssen wir diese auch zum optimalen Zeitpunkt verarbeiten. Da die ungefähre Formationszeit des Blocks 30 Sekunden beträgt, müssen 1.000.000 Transaktionen in den Block aufgenommen werden, was bedeutet, dass mehr als 100 MB Daten gesendet werden.

Anfangs haben wir die Bibliothek "ethereumjs-devp2p" verwendet, um Knoten zu kommunizieren, aber sie konnte nicht mit so vielen Daten umgehen. Infolgedessen haben wir die `ws`-Bibliothek verwendet und die binäre Datenübertragung auf dem Websocket eingerichtet. Natürlich sind auch beim Senden großer Datenpakete Probleme aufgetreten, aber wir haben sie in Blöcke unterteilt, und jetzt gibt es keine derartigen Probleme mehr.

Auch die Bildung des Merkle-Baums und die Berechnung des Hash von 1.000.000 Transaktionen erfordert etwa 10 Sekunden kontinuierliche Berechnung. Während dieser Zeit kann die Verbindung mit allen Knoten unterbrochen werden. Es wurde beschlossen, diese Berechnung in einen separaten Thread zu übertragen.

Schlussfolgerungen:


Tatsächlich sind unsere Ergebnisse nicht neu, aber aus irgendeinem Grund vergessen viele Experten sie während der Entwicklung.

  • Die Verwendung der funktionalen Programmierung anstelle der objektorientierten Programmierung erhöht die Leistung.
  • Ein Monolith ist schlechter als eine Servicearchitektur für ein Produktionssystem auf NodeJS.
  • Die Verwendung von `worker_threads` für Heavy Computing verbessert die Reaktionsfähigkeit des Systems, insbesondere bei der Arbeit mit E / A-Vorgängen.
  • Unix-Socket ist stabiler und schneller als http-Anforderungen.
  • Wenn Sie große Datenmengen schnell über das Netzwerk übertragen müssen, ist es besser, Websockets zu verwenden und Binärdaten zu senden, die in Blöcke unterteilt sind, die weitergeleitet werden können, wenn sie nicht erreicht werden, und dann zu einer einzigen Nachricht zusammengeführt werden.

Wir laden Sie ein, das GitHub- Projekt zu besuchen: https://github.com/opporty-com/Plasma-Cash/tree/new-version

Der Artikel wurde von Alexander Nashivan , Senior Developer von Clever Solution Inc., mitgeschrieben .

Source: https://habr.com/ru/post/de471096/


All Articles