Blockchain â eine innovative Technologie, die verspricht, viele Bereiche des menschlichen Lebens zu verbessern. Sie ĂŒbertrĂ€gt reale Prozesse und Produkte in den digitalen Raum, gewĂ€hrleistet Schnelligkeit und ZuverlĂ€ssigkeit finanzieller Transaktionen, senkt deren Kosten und ermöglicht die Erstellung moderner DAPP-Anwendungen unter Verwendung von Smart Contracts in dezentralen Netzwerken.
Angesichts der zahlreichen Vorteile und der vielfĂ€ltigen Anwendungsbereiche könnte es merkwĂŒrdig erscheinen, dass diese vielversprechende Technologie noch nicht in alle Branchen vorgedrungen ist. Das Problem ist, dass modernen dezentralen Blockchains die Skalierbarkeit fehlt. Ethereum verarbeitet etwa 20 Transaktionen pro Sekunde, was nicht ausreicht, um die BedĂŒrfnisse des dynamischen modernen GeschĂ€fts zu befriedigen. Gleichzeitig zögern Unternehmen, die Blockchain-Technologie nutzen, Ethereum aufzugeben, aufgrund seines hohen Schutzes vor Hacks und Netzwerkstörungen.
Um Dezentralisierung, Sicherheit und Skalierbarkeit in der Blockchain zu gewĂ€hrleisten und damit die Skalierbarkeits-Trilemma zu lösen, hat das Entwicklerteam Plasma Cash geschaffen â eine Sidechain, die aus einem Smart Contract und einem privaten Netzwerk basierend auf Node.js besteht, das regelmĂ€Ăig ihren Zustand in die Haupt-Chain (Ethereum) ĂŒbertrĂ€gt.

Wichtige Prozesse in Plasma Cash
1. Der Benutzer ruft die Funktion des Smart Contracts `deposit` auf und ĂŒbertrĂ€gt den Betrag in ETH, den er in das Plasma Cash Token einzahlen möchte. Die Funktion des Smart Contracts erstellt das Token und generiert ein Ereignis darĂŒber.
2. Plasma Cash Nodes, die auf die Ereignisse des Smart Contracts subscribiert sind, erhalten das Ereignis zur Erstellung der Einzahlung und fĂŒgen die Transaktion zur Erstellung des Tokens zum Pool hinzu.
3. Gelegentlich nehmen spezielle Plasma Cash-Knoten alle Transaktionen aus dem Pool (bis zu 1 Million) und erstellen daraus einen Block, berechnen den Merkle-Baum und somit den Hash. Dieser Block wird an andere Knoten zur Verifizierung gesendet. Die Knoten prĂŒfen, ob der Merkle-Hash gĂŒltig ist und ob die Transaktionen gĂŒltig sind (zum Beispiel, ob der Absender des Tokens dessen EigentĂŒmer ist). Nach der Verifizierung des Blocks ruft der Knoten die Funktion `submitBlock` des Smart Contracts auf, die die Nummer und den Merkle-Hash des Blocks in die Hauptkette speichert. Der Smart Contract erzeugt ein Ereignis ĂŒber die erfolgreiche HinzufĂŒgung des Blocks. Transaktionen werden aus dem Pool entfernt.
4. Die Knoten, die das Ereignis ĂŒber das Absenden 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 aus Plasma Cash abheben. Dazu ruft er die Funktion `startExit` auf und ĂŒbergibt Informationen ĂŒber die letzten 2 Transaktionen des Tokens, die bestĂ€tigen, dass er tatsĂ€chlich der EigentĂŒmer des Tokens ist. Der Smart Contract ĂŒberprĂŒft mithilfe des Merkle-Hashes, ob sich die Transaktionen in den Blöcken befinden, und leitet den Token zur Abhebung weiter, die in zwei Wochen stattfinden wird.
6. Wenn der Token-Withdraw-Vorgang gegen die Regeln verstöĂt (der Token wurde nach Beginn des Auszahlungsvorgangs ausgegeben oder der Token gehörte vor der Auszahlung bereits jemand anderem), kann der EigentĂŒmer des Tokens den Withdraw innerhalb von zwei Wochen anfechten.

PrivatsphÀre wird auf zwei Arten erreicht.
1. Die Root-Chain weiĂ nichts ĂŒber die Transaktionen, die innerhalb der Child-Chain erzeugt und gesendet werden. Ăffentlich bleibt die Information, wer ETH in/aus Plasma Cash eingezahlt und ausgezahlt hat.
2. Die Child-Chain ermöglicht es, anonyme Transaktionen mithilfe von zk-SNARKs zu organisieren.
Technologischer Stack
- NodeJS
- Redis
- Etherium
- Soild
Testen
Bei der Entwicklung von Plasma Cash haben wir die Systemgeschwindigkeit getestet und folgende Ergebnisse erzielt:
- bis zu 35.000 Transaktionen pro Sekunde werden dem Pool hinzugefĂŒgt;
- bis zu 1.000.000 Transaktionen können in einem Block gespeichert werden.
Die Tests wurden auf den folgenden 3 Servern durchgefĂŒhrt:
1. Intel Core i7-6700 Quad-Core Skylake incl. NVMe SSD â 512 GB, 64 GB DDR4 RAM
Es wurden 3 validierende Plasma Cash Knoten gestartet.
2. AMD Ryzen 7 1700X Octa-Core âSummit Ridgeâ (Zen), SATA SSD â 500 GB, 64 GB DDR4 RAM
Es wurde ein Ropsten-Testnet ETH-Knoten gestartet.
Es wurden 3 validierende Plasma Cash Knoten gestartet.
3. Intel Core i9-9900K Octa-Core incl. NVMe SSD â 1 TB, 64 GB DDR4 RAM
Es wurde ein Submit Plasma Cash Knoten gestartet.
Es wurden 3 validierende Plasma Cash Knoten gestartet.
Der Test zum HinzufĂŒgen von Transaktionen zum Plasma Cash Netzwerk wurde gestartet.
Insgesamt: 10 Plasma Cash Knoten im privaten Netzwerk.
Test 1
Es gibt ein Limit von 1 Million Transaktionen pro Block. Daher gelangen 1 Million Transaktionen in 2 Blöcke (da das System in der Lage ist, einen Teil der Transaktionen zu erfassen und zu ĂŒbermitteln, wĂ€hrend sie gesendet werden).

UrsprĂŒnglicher Zustand: letzter Block #7; in der Datenbank sind 1 Million Transaktionen und Token gespeichert.
00:00 â Starten des Transaktionsgenerierungsskripts
01:37 â 1 Million Transaktionen erstellt und die Ăbermittlung an den Knoten begonnen
01:46 â Der Knoten hat 240k Transaktionen aus dem Pool entnommen und block #8 wird erstellt. AuĂerdem sehen wir, dass 320k Transaktionen in 10 Sekunden zum Pool hinzugefĂŒgt werden.
01:58 â Block #8 signiert und zur Validierung gesendet
02:03 â Block #8 validiert und die Funktion `submitBlock` des Smart Contracts mit dem Merkle-Hash und der Blocknummer aufgerufen
02:10 â Das Dem Skript hat seine Arbeit beendet, das 1 Million Transaktionen in 32 Sekunden gesendet hat
02:33 â Die Knoten haben begonnen, Informationen zu erhalten, dass Block #8 zur Wurzelkette hinzugefĂŒgt wurde, und haben begonnen, 240k Transaktionen auszufĂŒhren
02:40 â Aus dem Pool wurden 240k Transaktionen entfernt, die bereits in Block #8 sind
02:56 â Der Knoten hat die verbleibenden 760k Transaktionen aus dem Pool ĂŒbernommen und begonnen, den Merkle-Hash zu berechnen und Block #9 zu signieren
03:20 â Alle Knoten enthalten 1 Million 240k Transaktionen und Token
03:35 â Block #9 wurde signiert und wird zur Validierung an andere Nodes gesendet
03:41 â Ein Netzwerkfehler ist aufgetreten
04:40 â Die Wartung fĂŒr die Validierung von Block #9 wurde aufgrund eines Timeouts beendet
04:54 â Der Submit-Node hat 760.000 Transaktionen aus dem Pool entnommen und beginnt mit der Berechnung des Merkle-Hashes und der Signierung von Block #9
05:32 â Block #9 wurde signiert und wird zur Validierung an andere Nodes gesendet
05:53 â Block #9 wurde validiert und in die Root-Chain gesendet
06:17 â Die Nodes haben begonnen, Informationen darĂŒber zu erhalten, dass Block #9 zur Root-Chain hinzugefĂŒgt wurde und haben 760.000 Transaktionen ausgefĂŒhrt
06:47 â Der Pool wurde von den Transaktionen, die in Block #9 enthalten sind, bereinigt
09:06 â Alle Nodes enthalten 2 Millionen Transaktionen und Tokens
Test 2
Es gibt ein Limit von 350.000 pro Block. Infolgedessen haben wir 3 Blöcke.

UrsprĂŒnglicher Zustand: letzter Block #9; in der Datenbank sind 2 Millionen Transaktionen und Tokens gespeichert
00:00 â Das Transaktionsgenerierungsskript lĂ€uft bereits
00:44 â 1 Million Transaktionen wurden erstellt und der Versand an den Node hat begonnen
00:56 â Der Submit-Node hat 320.000 Transaktionen aus dem Pool entnommen und erstellt Block #10. Zudem sehen wir, dass 320.000 Transaktionen in 10 Sekunden zum Pool hinzugefĂŒgt werden
01:12 â Block #10 wurde signiert und wird zur Validierung an andere Nodes gesendet
01:18 â das Dem Skript, das 1 Million Transaktionen in 34 Sekunden gesendet hat, ist fertig.
01:20 â Block #10 wurde validiert und in die Hauptkette gesendet.
01:51 â alle Knoten haben aus der Hauptkette die Information erhalten, dass Block #10 hinzugefĂŒgt wurde, und beginnen, 320k Transaktionen anzuwenden.
02:01 â der Pool wurde von 320k Transaktionen bereinigt, die in Block #10 hinzugefĂŒgt wurden.
02:15 â der Submit-Knoten hat 350k Transaktionen aus dem Pool entnommen und erstellt Block #11.
02:34 â Block #11 ist signiert und wird zur Validierung an andere Knoten gesendet.
02:51 â Block #11 wurde validiert und in die Hauptkette gesendet.
02:55 â der letzte Knoten hat die Transaktionen aus Block #10 ausgefĂŒhrt.
10:59 â die Transaktion mit dem Submit von Block #9 hat sehr lange in der Hauptkette gedauert, aber sie wurde ausgefĂŒhrt und alle Knoten haben darĂŒber informiert und begannen, 350k Transaktionen auszufĂŒhren.
11:05 â der Pool wurde von 320k Transaktionen bereinigt, die in Block #11 hinzugefĂŒgt wurden.
12:10 â alle Knoten enthalten 1 Million 670k Transaktionen und Token.
12:17 â der Submit-Knoten hat 330k Transaktionen aus dem Pool entnommen und erstellt Block #12.
12:32 â Block #12 ist signiert und wird zur Validierung an andere Knoten gesendet.
12:39 â Block #12 wurde validiert und in die Hauptkette gesendet.
13:44 â Alle Nodes haben aus der Root-Kette Informationen erhalten, dass Block #12 hinzugefĂŒgt wurde und nun 330k Transaktionen anwenden.
14:50 â Alle Nodes enthalten 2 Millionen Transaktionen und Tokens.
Test 3
In den ersten beiden Servern wurde ein Validierungs-Node durch einen Submit-Node ersetzt.

UrsprĂŒnglicher Zustand: letzter Block #84; in der Datenbank sind 0 Transaktionen und Tokens gespeichert.
00:00 â 3 Skripte wurden gestartet, die jeweils 1 Million Transaktionen generieren und senden.
01:38 â 1 Million Transaktionen erstellt und der Versand an Submit-Node #3 begonnen.
01:50 â Submit-Node #3 hat aus dem Pool 330k Transaktionen entnommen und formt Block #85 (f21). AuĂerdem sehen wir, dass in den Pool in 10 Sekunden 350k Transaktionen hinzugefĂŒgt werden.
01:53 â 1 Million Transaktionen erstellt und der Versand an Submit-Node #1 begonnen.
01:50 â Submit-Node #3 hat aus dem Pool 330k Transaktionen entnommen und formt Block #85 (f21). AuĂerdem sehen wir, dass in den Pool in 10 Sekunden 350k Transaktionen hinzugefĂŒgt werden.
02:01 â Submit-Node #1 hat aus dem Pool 250k Transaktionen entnommen und formt Block #85 (65e).
02:06 â Block #85 (f21) wurde signiert und wird an andere Nodes zur Validierung gesendet.
02:08 â Das Demo-Skript des Servers #3 hat die Arbeit beendet, welches 1 Million Transaktionen in 30 Sekunden gesendet hat.
02:14 â Block #85 (f21) wurde validiert und in die Root-Kette gesendet.
02:19 â Block #85 (65e) wurde signiert und wird an andere Nodes zur Validierung gesendet.
02:22 â 1 Million Transaktionen wurden erstellt und die Ăbertragung an Node #2 begann.
02:27 â Block #85 (65e) wurde validiert und in die Hauptkette gesendet.
02:29 â Node #2 hat 111855 Transaktionen aus dem Pool ĂŒbernommen und formt Block #85 (256).
02:36 â Block #85 (256) wurde signiert und wird zur Validierung an andere Nodes gesendet.
02:36 â Das Demo-Skript des Servers #1 hat 1 Million Transaktionen in 42,5 Sekunden gesendet.
02:38 â Block #85 (256) wurde validiert und in die Hauptkette gesendet.
03:08 â Das Skript des Servers #2 hat 1 Million Transaktionen in 47 Sekunden gesendet.
03:38 â Alle Nodes haben aus der Hauptkette Informationen erhalten, dass die Blöcke #85 (f21), #86 (65e) und #87 (256) hinzugefĂŒgt wurden und beginnen, 330k, 250k, 111855 Transaktionen anzuwenden.
03:49 â Der Pool hat sich um 330k, 250k und 111855 Transaktionen geleert, die in die Blöcke #85 (f21), #86 (65e) und #87 (256) aufgenommen wurden.
03:59 â Node #1 hat 888145 Transaktionen aus dem Pool ĂŒbernommen und bildet Block #88 (214), Node #2 hat 750k Transaktionen aus dem Pool ĂŒbernommen und bildet Block #88 (50a), Node #3 hat 670k Transaktionen aus dem Pool ĂŒbernommen und bildet Block #88 (d3b).
04:44 â Block #88 (d3b) wurde signiert und wird zur Validierung an andere Nodes gesendet.
04:58 â Block #88 (214) wurde signiert und wird zur Validierung an andere Nodes gesendet.
05:11 â Block #88 (50a) wurde signiert und an andere Knoten zur Validierung gesendet
05:11 â Block #85 (d3b) wurde validiert und in die Hauptkette gesendet
05:36 â Block #85 (214) wurde validiert und in die Hauptkette gesendet
05:43 â Alle Knoten erhielten aus der Hauptkette die Information, dass die Blöcke #88 (d3b) und #89 (214) hinzugefĂŒgt wurden und beginnen, 670k und 750k Transaktionen anzuwenden
06:50 â Aufgrund eines Verbindungsabbruchs wurde Block #85 (50a) nicht validiert
06:55 â Der Submit-Knoten #2 nahm 888145 Transaktionen aus dem Pool und bildet Block #90 (50a)
08:14 â Block #90 (50a) wurde signiert und an andere Knoten zur Validierung gesendet
09:04 â Block #90 (50a) wurde validiert und in die Hauptkette gesendet
11:23 â Alle Knoten erhielten aus der Hauptkette die Information, dass Block #90 (50a) hinzugefĂŒgt wurde und beginnen, 888145 Transaktionen anzuwenden. Dabei hatte der Server #3 bereits Transaktionen aus den Blöcken #88 (d3b) und #89 (214) angewendet
12:11 â Alle Pools sind leer
13:41 â Alle Knoten des Servers #3 enthalten 3 Millionen Transaktionen und Tokens
14:35 â Alle Knoten des Servers #1 enthalten 3 Millionen Transaktionen und Tokens
19:24 â Alle Knoten des Servers #2 enthalten 3 Millionen Transaktionen und Tokens
Hindernisse
WĂ€hrend der Entwicklung von Plasma Cash sind wir auf folgende Probleme gestoĂen, die wir schrittweise gelöst haben und weiterhin lösen:
1. Konflikt zwischen verschiedenen Systemfunktionen. Zum Beispiel hat die Funktion zum HinzufĂŒgen von Transaktionen den Betrieb des Submits und der Validierung von Blöcken blockiert und umgekehrt, was zu einer Verlangsamung der Geschwindigkeit fĂŒhrte.
2. Es war nicht sofort klar, wie man eine groĂe Anzahl von Transaktionen senden und gleichzeitig die DatenĂŒbertragungskosten minimieren kann.
3. Es war unklar, wie und wo die Daten gespeichert werden sollten, um optimale Ergebnisse zu erzielen.
4. Es war nicht klar, wie man das Netzwerk zwischen den Knoten organisieren sollte, da die GröĂe eines Blocks mit 1 Million Transaktionen etwa 100 MB betrĂ€gt.
5. Der Betrieb im Ein-Thread-Modus unterbricht die Verbindung zwischen den Knoten, wenn lange Berechnungen stattfinden (zum Beispiel beim Erstellen des Merkle-Baums und Berechnen seines Hashs).
Wie haben wir all dies gemeistert?
Die erste Version des Plasma Cash Nodes war eine Art KombinationsgerĂ€t, das alles gleichzeitig erledigen konnte: Transaktionen annehmen, Blöcke submitten und validieren und eine API fĂŒr den Datenzugriff bereitstellen. Da NodeJS ursprĂŒnglich ein-Threadig ist, hat die rechenintensive Funktion zur Berechnung des Merkle-Baums die Funktion zum HinzufĂŒgen von Transaktionen blockiert. Wir haben zwei LösungsansĂ€tze fĂŒr dieses Problem gesehen:
1. Mehrere NodeJS-Prozesse starten, von denen jeder bestimmte Funktionen ausfĂŒhrt.
2. worker_threads verwenden und Teile des Codes in Threads auslagern.
Letztlich haben wir beide Optionen gleichzeitig genutzt: Eine Logiknode in 3 Teile unterteilt, die unabhÀngig, aber auch synchron arbeiten können.
1. Die Submit-Node, die Transaktionen in den Pool aufnimmt und Blöcke erstellt.
2. Die Validierungs-Node, die die GĂŒltigkeit der Nodes ĂŒberprĂŒft.
3. API-Node â bietet eine API fĂŒr den Zugriff auf Daten.
Dabei kann jede Node ĂŒber einen Unix-Socket mittels CLI verbunden werden.
Schwere Operationen, wie die Berechnung des Merkle-Baums, haben wir in einen separaten Thread ausgelagert.
So haben wir sichergestellt, dass alle Funktionen von Plasma Cash gleichzeitig und ohne Störungen arbeiten.
Sobald das System funktional war, begannen wir mit Tests der Geschwindigkeit und erhielten leider unzufriedenstellende Ergebnisse: 5.000 Transaktionen pro Sekunde und bis zu 50.000 Transaktionen pro Block. Wir mussten herausfinden, was falsch implementiert war.
Zu Beginn haben wir den Kommunikationsmechanismus mit Plasma Cash getestet, um die maximale LeistungsfĂ€higkeit des Systems zu ermitteln. Zuvor haben wir berichtet, dass der Plasma Cash-Knoten eine Unix-Socket-Schnittstelle bietet. UrsprĂŒnglich war diese textbasiert. JSON-Objekte wurden mit `JSON.parse()` und `JSON.stringify()` ĂŒbertragen.
```json
{
"action": "sendTransaction",
"payload":{
"prevHash": "0x8a88cc4217745fd0b4eb161f6923235da10593be66b841d47da86b9cd95d93e0",
"prevBlock": 41,
"tokenId": "57570139642005649136210751546585740989890521125187435281313126554130572876445",
"newOwner": "0x200eabe5b26e547446ae5821622892291632d4f4",
"type": "pay",
"data": "",
"signature": "0xd1107d0c6df15e01e168e631a386363c72206cb75b233f8f3cf883134854967e1cd9b3306cc5c0ce58f0a7397ae9b2487501b56695fe3a3c90ec0f61c7ea4a721c"
}
}
```
Wir haben die Ăbertragungsgeschwindigkeit dieser Objekte gemessen und erhalten ~ 130.000 pro Sekunde. Wir haben versucht, die Standardfunktionen zur Bearbeitung von JSON zu ersetzen, aber die Leistung hat sich nicht verbessert. Offenbar ist der V8-Engine gut fĂŒr diese Operationen optimiert.
Der Umgang mit Transaktionen, Token und Blöcken erfolgte ĂŒber Klassen. Bei der Erstellung solcher Klassen sank die Leistung um das Zweifache, was darauf hindeutet: OOP ist fĂŒr uns nicht geeignet. Wir mussten alles auf einen rein funktionalen Ansatz umschreiben.
Datenbankeintrag
UrsprĂŒnglich wurde Redis als eine der leistungsstĂ€rksten Lösungen fĂŒr die Datenspeicherung ausgewĂ€hlt, da es unseren Anforderungen entspricht: ein Key-Value-Speicher, der mit Hash-Tabellen und Mengen arbeitet. Wir haben redis-benchmark ausgefĂŒhrt und ~80k Operationen pro Sekunde im 1 Pipelining-Modus erhalten.
FĂŒr hohe Leistung haben wir Redis feiner abgestimmt:
- Wir haben eine Unix-Socket-Verbindung eingerichtet.
- Wir haben die Speicherung des Zustands auf die Festplatte deaktiviert (zur GewÀhrleistung der ZuverlÀssigkeit kann eine Replik eingerichtet werden, die dann in einem separaten Redis die Speicherung auf der Festplatte vornimmt).
In Redis ist ein Pool eine Hash-Tabelle, da wir die Möglichkeit benötigen, alle Transaktionen mit einer Abfrage abzurufen und Transaktionen einzeln zu löschen. Wir haben versucht, eine normale Liste zu verwenden, aber diese arbeitet langsamer beim Abrufen der gesamten Liste.
Bei der Verwendung der Standard-NodeJS-Bibliothek fĂŒr Redis haben wir eine Leistung von 18k Transaktionen pro Sekunde erreicht. Die Geschwindigkeit ist um das 9-fache gesunken.
Da der Benchmark uns Möglichkeiten zeigte, die deutlich fĂŒnffach gröĂer waren, haben wir mit der Optimierung begonnen. Wir wechselten die Bibliothek zu ioredis und erreichten bereits eine Leistung von 25.000 pro Sekunde. Die Transaktionen haben wir einzeln hinzugefĂŒgt, indem wir den Befehl `hset` verwendeten. Dadurch generierten wir viele Anfragen an Redis. Die Idee entstand, Transaktionen in BĂŒndeln zu kombinieren und sie mit einem einzigen Befehl `hmset` zu versenden. Das Ergebnis â 32.000 pro Sekunde.
Aus mehreren GrĂŒnden, die wir unten beschreiben werden, arbeiten wir mit den Daten ĂŒber `Buffer` und, wie sich herausstellte, kann eine Umwandlung in Text (`buffer.toString('hex')`) vor dem Schreiben zusĂ€tzliche Leistung bringen. So gelang es, die Geschwindigkeit auf 35.000 pro Sekunde zu steigern. Derzeit haben wir beschlossen, die weitere Optimierung zu pausieren.
Wir mussten auf ein binÀres Protokoll umsteigen, da:
1. Das System berechnet hĂ€ufig Hashes, digitale Signaturen usw., und dafĂŒr benötigt es die Daten im `Buffer`.
2. Beim Versand zwischen den Diensten wiegen binÀre Daten weniger als Text. Zum Beispiel können die Daten eines Blocks mit 1 Million Transaktionen im Text mehr als 300 Megabyte einnehmen.
3. Die stÀndige Umwandlung von Daten hat Auswirkungen auf die Leistung.
Deshalb haben wir unser eigenes binĂ€res Protokoll zur Speicherung und DatenĂŒbertragung auf Basis der groĂartigen Bibliothek `binary-data` entwickelt.
Daraus ergeben sich 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),
}
```
â Block
```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, Protocol).slice();` und ` BD.decode(buffer, Protocol)` wandeln wir die Daten in ein `Buffer` um, um sie in Redis zu speichern oder an einen anderen Knoten zu senden und die Daten zurĂŒckzugewinnen.
AuĂerdem haben wir zwei binĂ€re Protokolle zur DatenĂŒbertragung zwischen den 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` â die auszufĂŒhrende Aktion, zum Beispiel 1 â sendTransaction, 2 â getTransaction;
- `payload` â die Daten, die an die entsprechende Funktion ĂŒbergeben werden mĂŒssen;
- `messageId` â die ID der Nachricht, um die Antwort identifizieren zu können.
â Das Interaktionsprotokoll zwischen den 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` â der Nachrichten-Code, zum Beispiel 6 â PREPARE_NEW_BLOCK, 7 â BLOCK_VALID, 8 â BLOCK_COMMIT;
- `versionProtocol` â die Protokollversion, da im Netzwerk Knoten mit unterschiedlichen Versionen aktiv sein können, die unterschiedlich arbeiten können;
- `seq` â die Nachrichten-ID;
- `countChunk` und `chunkNumber` werden benötigt, um groĂe Nachrichten zu splitten;
- `length` und `payload` die LĂ€nge und die Daten selbst.
Da wir die Daten im Voraus typisiert haben, arbeitet das gesamte System viel schneller als die `rlp` Bibliothek von Ethereum. Leider ist es uns bisher nicht gelungen, darauf zu verzichten, da der Smart Contract ĂŒberarbeitet werden muss, was wir in Zukunft planen.
Wenn wir es geschafft haben, Geschwindigkeiten von 35 000 Transaktionen pro Sekunde zu erreichen, mĂŒssen wir diese auch in optimaler Zeit verarbeiten. Da die ungefĂ€hre Zeit zur Blockbildung 30 Sekunden betrĂ€gt, mĂŒssen wir in den Block 1 000 000 Transaktionen einfĂŒgen, was bedeutet, dass wir mehr als 100 MB Daten ĂŒbertragen mĂŒssen.
UrsprĂŒnglich haben wir die `ethereumjs-devp2p`-Bibliothek zur Kommunikation zwischen den Knoten verwendet, aber sie konnte mit der Menge an Daten nicht umgehen. Daher haben wir die `ws`-Bibliothek verwendet und die Ăbertragung binĂ€rer Daten ĂŒber Websocket eingerichtet. NatĂŒrlich hatten wir auch Probleme beim Versand groĂer Datenpakete, aber wir haben sie in Chunks aufgeteilt, und jetzt gibt es diese Probleme nicht mehr.
Die Erstellung des Merkle-Baums und die Berechnung des Hashs 1 000 000 von Transaktionen erfordert etwa 10 Sekunden kontinuierlicher Berechnungen. WĂ€hrend dieser Zeit kann die Verbindung zu allen Knoten abbrechen. Daher wurde beschlossen, diese Berechnung in einen separaten Thread zu verlagern.
Fazit:
TatsÀchlich sind unsere Erkenntnisse nicht neu, aber aus irgendeinem Grund vergessen viele Fachleute sie bei der Entwicklung.
- Die Verwendung von funktionaler Programmierung anstelle von objektorientierter Programmierung erhöht die Leistung.
- Monolithen ist schlechter als eine serviceorientierte Architektur fĂŒr leistungsstarke Systeme auf NodeJS.
- Die Nutzung von `worker_threads` fĂŒr rechenintensive Aufgaben verbessert die SystemreaktionsfĂ€higkeit, insbesondere bei I/O-Operationen.
- Unix-Sockets sind stabiler und schneller als HTTP-Anfragen.
- Wenn groĂe Datenmengen schnell ĂŒber das Netzwerk ĂŒbertragen werden mĂŒssen, sollten WebSockets genutzt und binĂ€re Daten in Chunks gesendet werden, die im Falle eines Ausfalls erneut ĂŒbertragen und dann zu einer Nachricht zusammengefĂŒgt werden können.
Wir laden Sie ein, GitHub Projekt:
Der Artikel wurde in Zusammenarbeit mit Alexander Nashivan, Senior-Entwickler bei .
Quelle: habr.com
