Test publiczny: rozwiązanie zapewniające prywatność i skalowalność w Ethereum

Blockchain to innowacyjna technologia, która obiecuje poprawę wielu dziedzin życia człowieka. Przenosi realne procesy i produkty w przestrzeń cyfrową, zapewnia szybkość i niezawodność transakcji finansowych, obniża ich koszt, a także pozwala na tworzenie nowoczesnych aplikacji DAPP z wykorzystaniem inteligentnych kontraktów w zdecentralizowanych sieciach.

Biorąc pod uwagę wiele korzyści i różnorodne zastosowania blockchain, może wydawać się zaskakujące, że ta obiecująca technologia nie trafiła jeszcze do każdej branży. Problem polega na tym, że współczesnym zdecentralizowanym łańcuchom bloków brakuje skalowalności. Ethereum przetwarza około 20 transakcji na sekundę, co nie jest wystarczające, aby sprostać potrzebom dzisiejszego dynamicznego biznesu. Jednocześnie firmy korzystające z technologii blockchain niechętnie porzucają Ethereum ze względu na wysoki stopień ochrony przed włamaniami i awariami sieci.

Aby zapewnić decentralizację, bezpieczeństwo i skalowalność w blockchainie, rozwiązując w ten sposób Trilemat Skalowalności, zespół programistów Opporty stworzył Plasma Cash, sieć zależną składającą się z inteligentnego kontraktu i sieci prywatnej opartej na Node.js, która okresowo przekazuje swój stan do łańcucha root (Ethereum).

Test publiczny: rozwiązanie zapewniające prywatność i skalowalność w Ethereum

Kluczowe procesy w Plasma Cash

1. Użytkownik nazywa funkcję inteligentnego kontraktu „depozytem”, przekazując do niej kwotę ETH, którą chce wpłacić na token Plasma Cash. Funkcja inteligentnego kontraktu tworzy token i generuje zdarzenie na jego temat.

2. Węzły Plasma Cash subskrybowane do zdarzeń inteligentnych kontraktów otrzymują zdarzenie o utworzeniu lokaty i dodają do puli transakcję dotyczącą utworzenia tokena.

3. Okresowo specjalne węzły Plasma Cash pobierają wszystkie transakcje z puli (do 1 miliona) i tworzą z nich blok, obliczają drzewo Merkle i odpowiednio skrót. Blok ten jest wysyłany do innych węzłów w celu weryfikacji. Węzły sprawdzają, czy hash Merkle jest ważny i czy transakcje są ważne (np. czy nadawca tokena jest jego właścicielem). Po zweryfikowaniu bloku węzeł wywołuje funkcję „submitBlock” inteligentnego kontraktu, która zapisuje numer bloku i skrót Merkle do łańcucha brzegowego. Inteligentny kontrakt generuje zdarzenie wskazujące pomyślne dodanie bloku. Transakcje są usuwane z puli.

4. Węzły, które otrzymają zdarzenie przesłania bloku, zaczynają stosować transakcje dodane do bloku.

5. W pewnym momencie właściciel (lub osoba niebędąca właścicielem) tokena chce go wypłacić z Plasma Cash. W tym celu wywołuje funkcję `startExit` przekazując do niej informację o 2 ostatnich transakcjach na tokenie, które potwierdzają, że jest właścicielem tokena. Inteligentny kontrakt wykorzystując hash Merkle sprawdza obecność transakcji w blokach i wysyła token do wypłaty, co nastąpi za dwa tygodnie.

6. Jeżeli operacja wypłaty tokena odbyła się z naruszeniami (token został wydany po rozpoczęciu procedury wypłaty lub token należał już do kogoś innego przed wypłatą), właściciel tokena może w ciągu dwóch tygodni odrzucić wypłatę.

Test publiczny: rozwiązanie zapewniające prywatność i skalowalność w Ethereum

Prywatność osiąga się na dwa sposoby

1. Łańcuch główny nic nie wie o transakcjach generowanych i przekazywanych w obrębie łańcucha podrzędnego. Informacje o tym, kto zdeponował i wypłacił ETH z Plasma Cash, pozostają publiczne.

2. Łańcuch podrzędny umożliwia anonimowe transakcje przy użyciu zk-SNARK.

Stos technologii

  • NodeJS
  • Redis
  • Etherium
  • Ziemia

Testowanie

Tworząc Plasma Cash, przetestowaliśmy szybkość systemu i uzyskaliśmy następujące wyniki:

  • do puli dodawanych jest do 35 000 transakcji na sekundę;
  • W bloku można przechowywać do 1 000 000 transakcji.

Testy przeprowadzono na 3 serwerach:

1. Czterordzeniowy procesor Intel Core i7-6700 Skylake w tym. Dysk SSD NVMe – 512 GB, 64 GB pamięci RAM DDR4
Podniesiono 3 węzły sprawdzające Plasma Cash.

2. AMD Ryzen 7 1700X Octa-Core „Summit Ridge” (Zen), dysk SSD SATA – 500 GB, 64 GB pamięci RAM DDR4
Węzeł ETH sieci testowej Ropsten został podniesiony.
Podniesiono 3 węzły sprawdzające Plasma Cash.

3. Ośmiordzeniowy Intel Core i9-9900K w tym. Dysk SSD NVMe – 1 TB, 64 GB pamięci RAM DDR4
1 węzeł przesyłania Plasma Cash został podniesiony.
Podniesiono 3 węzły sprawdzające Plasma Cash.
Ruszył test dodawania transakcji do sieci Plasma Cash.

Razem: 10 węzłów Plasma Cash w sieci prywatnej.

Test 1

Obowiązuje limit 1 miliona transakcji na blok. Dlatego 1 milion transakcji dzieli się na 2 bloki (ponieważ system potrafi wziąć udział w transakcjach i przesłać je w trakcie ich wysyłania).


Stan początkowy: ostatni blok #7; W bazie danych przechowywanych jest 1 milion transakcji i tokenów.

00:00 — rozpoczęcie skryptu generowania transakcji
01:37 - Utworzono 1 milion transakcji i rozpoczęło się wysyłanie do węzła
01:46 — węzeł przesyłania pobrał 240 tys. transakcji z puli i utworzył blok nr 8. Widzimy również, że w ciągu 320 sekund do puli zostaje dodanych 10 tys. transakcji
01:58 — blok nr 8 zostaje podpisany i przesłany do sprawdzenia
02:03 — sprawdzany jest blok nr 8 i wywoływana jest funkcja „submitBlock” inteligentnego kontraktu z hashem Merkle i numerem bloku
02:10 — skrypt demo zakończył działanie, który wysłał 1 milion transakcji w 32 sekundy
02:33 - węzły zaczęły otrzymywać informację, że blok nr 8 został dodany do łańcucha głównego i zaczęły wykonywać 240 tys. transakcji
02:40 - Z puli usunięto 240 tys. transakcji, które znajdują się już w bloku nr 8
02:56 — węzeł przesyłania pobrał pozostałe 760 tys. transakcji z puli i rozpoczął obliczanie skrótu Merkle oraz bloku podpisywania nr 9
03:20 - wszystkie węzły zawierają 1 milion 240 tys. transakcji i tokenów
03:35 — blok nr 9 zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
03:41 - wystąpił błąd sieci
04:40 — upłynął limit czasu oczekiwania na weryfikację bloku nr 9
04:54 — węzeł przesyłania pobrał pozostałe 760 tys. transakcji z puli i rozpoczął obliczanie skrótu Merkle oraz bloku podpisywania nr 9
05:32 — blok nr 9 zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
05:53 — blok nr 9 zostaje zatwierdzony i wysłany do łańcucha głównego
06:17 - węzły zaczęły otrzymywać informację, że blok nr 9 został dodany do łańcucha głównego i zaczęły wykonywać 760 tys. transakcji
06:47 — pula została wyczyszczona z transakcji znajdujących się w bloku nr 9
09:06 - wszystkie węzły zawierają 2 miliony transakcji i tokenów

Test 2

Limit wynosi 350 tys. na blok. W rezultacie mamy 3 bloki.


Stan początkowy: ostatni blok #9; W bazie danych przechowywanych jest 2 miliony transakcji i tokenów

00:00 — skrypt generowania transakcji został już uruchomiony
00:44 - Utworzono 1 milion transakcji i rozpoczęło się wysyłanie do węzła
00:56 — węzeł przesyłania pobrał 320 tys. transakcji z puli i utworzył blok nr 10. Widzimy również, że w ciągu 320 sekund do puli zostaje dodanych 10 tys. transakcji
01:12 — blok nr 10 zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
01:18 — skrypt demo zakończył działanie, który wysłał 1 milion transakcji w 34 sekundy
01:20 — blok nr 10 zostaje zatwierdzony i wysłany do łańcucha głównego
01:51 - wszystkie węzły otrzymały informację z łańcucha głównego, że dodano blok nr 10 i zaczęto realizować 320 tys. transakcji
02:01 - pula została wyczyszczona na 320 tys. transakcji dodanych do bloku nr 10
02:15 — węzeł przesyłania pobrał 350 tys. transakcji z puli i utworzył blok nr 11
02:34 — blok nr 11 zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
02:51 — blok nr 11 zostaje zatwierdzony i wysłany do łańcucha głównego
02:55 — ostatni węzeł zakończył transakcje z bloku nr 10
10:59 — transakcja ze złożeniem bloku nr 9 trwała bardzo długo w łańcuchu głównym, ale została zakończona, wszystkie węzły otrzymały o niej informację i zaczęły wykonywać 350 tys. transakcji
11:05 - pula została wyczyszczona na 320 tys. transakcji dodanych do bloku nr 11
12:10 - wszystkie węzły zawierają 1 milion 670 tys. transakcji i tokenów
12:17 — węzeł przesyłania pobrał 330 tys. transakcji z puli i utworzył blok nr 12
12:32 — blok nr 12 zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
12:39 — blok nr 12 zostaje zatwierdzony i wysłany do łańcucha głównego
13:44 - wszystkie węzły otrzymały informację z łańcucha głównego, że blok nr 12 został dodany i zaczynają realizować 330 tys. transakcji
14:50 - wszystkie węzły zawierają 2 miliony transakcji i tokenów

Test 3

Na pierwszym i drugim serwerze jeden węzeł sprawdzający został zastąpiony węzłem przesyłającym.


Stan początkowy: ostatni blok #84; 0 transakcji i tokenów zapisanych w bazie danych

00:00 — Uruchomiono 3 skrypty, z których każdy generuje i wysyła po 1 milion transakcji
01:38 — Utworzono 1 milion transakcji i rozpoczęło się wysyłanie do węzła nr 3
01:50 — węzeł przesyłania nr 3 pobrał 330 tys. transakcji z puli i utworzył blok nr 85 (f21). Widzimy również, że w ciągu 350 sekund do puli zostaje dodanych 10 tys. transakcji
01:53 — Utworzono 1 milion transakcji i rozpoczęło się wysyłanie do węzła nr 1
01:50 — węzeł przesyłania nr 3 pobrał 330 tys. transakcji z puli i utworzył blok nr 85 (f21). Widzimy również, że w ciągu 350 sekund do puli zostaje dodanych 10 tys. transakcji
02:01 — węzeł przesyłania nr 1 pobrał 250 tys. transakcji z puli i utworzył blok nr 85 (65e)
02:06 — blok nr 85 (f21) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
02:08 — Skrypt demonstracyjny serwera nr 3, który wysłał 1 milion transakcji w 30 sekund, zakończył działanie
02:14 — blok nr 85 (f21) zostaje zatwierdzony i wysłany do łańcucha głównego
02:19 — blok nr 85 (65e) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
02:22 — Utworzono 1 milion transakcji i rozpoczęło się wysyłanie do węzła nr 2
02:27 — blok nr 85 (65e) został zatwierdzony i wysłany do łańcucha głównego
02:29 — węzeł przesyłania nr 2 pobrał 111855 transakcji z puli i utworzył blok nr 85 (256).
02:36 — blok nr 85 (256) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
02:36 — Skrypt demonstracyjny serwera nr 1, który wysłał 1 milion transakcji w 42.5 sekund, zakończył działanie
02:38 — blok nr 85 (256) zostaje zatwierdzony i wysłany do łańcucha głównego
03:08 — Skrypt serwera nr 2 zakończył działanie, który wysłał 1 milion transakcji w 47 sekund
03:38 - wszystkie węzły otrzymały informacje z łańcucha głównego, które bloki #85 (f21), #86(65e), #87(256) zostały dodane i zaczęły realizować transakcje 330 tys., 250 tys., 111855
03:49 - pula została wyczyszczona na poziomie 330 tys., 250 tys., 111855 transakcji, które zostały dodane do bloków #85 (f21), #86(65e), #87(256)
03:59 — węzeł przesyłania nr 1 pobrał 888145 88 transakcji z bloku puli i formularzy nr 214 (2), węzeł przesyłania nr 750 pobrał 88 tys. transakcji z bloku puli i formularzy nr 50 (3a), węzeł przesyłania nr 670 przyjął 88 tys. transakcji z basen i blok form #3 (dXNUMXb)
04:44 — blok nr 88 (d3b) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
04:58 — blok nr 88 (214) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
05:11 — blok nr 88 (50a) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
05:11 — blok nr 85 (d3b) jest sprawdzany i wysyłany do łańcucha głównego
05:36 — blok nr 85 (214) zostaje zatwierdzony i wysłany do łańcucha głównego
05:43 - wszystkie węzły otrzymały informacje z łańcucha głównego, które bloki #88 (d3b), #89(214) zostały dodane i zaczynają realizować 670 tys., 750 tys. transakcji
06:50 — z powodu awarii komunikacji blok nr 85 (50a) nie został zatwierdzony
06:55 — węzeł przesyłania nr 2 pobrał 888145 transakcji z puli i utworzył blok nr 90 (50a)
08:14 — blok nr 90 (50a) zostaje podpisany i wysłany do innych węzłów w celu sprawdzenia
09:04 — blok nr 90 (50a) zostaje zatwierdzony i wysłany do łańcucha głównego
11:23 - wszystkie węzły otrzymały informacje z łańcucha głównego, który został dodany blok #90 (50a) i zaczynają realizować 888145 transakcji. W tym samym czasie serwer #3 zastosował już transakcje z bloków #88 (d3b), #89(214)
12:11 - wszystkie baseny są puste
13:41 — wszystkie węzły serwera nr 3 zawierają 3 miliony transakcji i tokenów
14:35 — wszystkie węzły serwera nr 1 zawierają 3 miliony transakcji i tokenów
19:24 — wszystkie węzły serwera nr 2 zawierają 3 miliony transakcji i tokenów

Przeszkody

Podczas opracowywania Plasma Cash napotkaliśmy następujące problemy, które stopniowo rozwiązaliśmy i nadal rozwiązujemy:

1. Konflikt w interakcji różnych funkcji systemu. Przykładowo funkcja dodawania transakcji do puli blokowała pracę przesyłania i sprawdzania bloków i odwrotnie, co powodowało spadek szybkości.

2. Nie było od razu jasne, jak wysłać ogromną liczbę transakcji, minimalizując jednocześnie koszty przesyłania danych.

3. Nie było jasne, jak i gdzie przechowywać dane, aby osiągać wysokie wyniki.

4. Nie było jasne, jak zorganizować sieć między węzłami, ponieważ rozmiar bloku z 1 milionem transakcji zajmuje około 100 MB.

5. Praca w trybie jednowątkowym przerywa połączenie między węzłami, gdy występują długie obliczenia (na przykład budowanie drzewa Merkle i obliczanie jego skrótu).

Jak sobie z tym wszystkim poradziliśmy?

Pierwsza wersja węzła Plasma Cash była rodzajem kombajnu, który mógł robić wszystko w tym samym czasie: akceptować transakcje, przesyłać i zatwierdzać bloki oraz zapewniać API dostępu do danych. Ponieważ NodeJS jest natywnie jednowątkowy, funkcja obliczania ciężkiego drzewa Merkle blokowała funkcję dodawania transakcji. Widzieliśmy dwie możliwości rozwiązania tego problemu:

1. Uruchom kilka procesów NodeJS, z których każdy wykonuje określone funkcje.

2. Użyj worker_threads i przenieś wykonanie części kodu do wątków.

W rezultacie zastosowaliśmy obie opcje jednocześnie: logicznie podzieliliśmy jeden węzeł na 3 części, które mogą pracować osobno, ale jednocześnie synchronicznie

1. Węzeł przesyłania, który przyjmuje transakcje do puli i tworzy bloki.

2. Węzeł sprawdzający, który sprawdza ważność węzłów.

3. Węzeł API – udostępnia API umożliwiające dostęp do danych.

W takim przypadku możesz połączyć się z każdym węzłem poprzez gniazdo unixowe, używając cli.

Ciężkie operacje, takie jak obliczanie drzewa Merkle, przenieśliśmy do osobnego wątku.

W ten sposób osiągnęliśmy normalne działanie wszystkich funkcji Plasma Cash jednocześnie i bezawaryjnie.

Gdy system zaczął działać, zaczęliśmy testować prędkość i niestety otrzymaliśmy niezadowalające wyniki: 5 transakcji na sekundę i do 000 50 transakcji na blok. Musiałem dowiedzieć się, co zostało zaimplementowane nieprawidłowo.

Na początek zaczęliśmy testować mechanizm komunikacji z Plasma Cash, aby poznać maksymalne możliwości systemu. Pisaliśmy wcześniej, że węzeł Plasma Cash udostępnia interfejs gniazd uniksowych. Początkowo była to wersja tekstowa. obiekty json zostały wysłane przy użyciu `JSON.parse()` i `JSON.stringify()`.

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

Zmierzyliśmy prędkość transferu takich obiektów i stwierdziliśmy, że ~ 130 tys. na sekundę. Próbowaliśmy zastąpić standardowe funkcje do pracy z jsonem, ale wydajność nie uległa poprawie. Silnik V8 musi być dobrze zoptymalizowany do tych operacji.

Pracowaliśmy z transakcjami, tokenami i blokami poprzez zajęcia. Podczas tworzenia takich klas wydajność spadła 2-krotnie, co świadczy o tym, że OOP nie jest dla nas odpowiedni. Musiałem wszystko przepisać, aby uzyskać podejście czysto funkcjonalne.

Zapis w bazie danych

Początkowo wybrano Redis do przechowywania danych jako jedno z najbardziej produktywnych rozwiązań spełniających nasze wymagania: przechowywanie klucz-wartość, praca z tablicami skrótów, zbiorami. Uruchomiliśmy program Redis-Benchmark i uzyskaliśmy ~80 tys. operacji na sekundę w 1 trybie potokowym.

Aby uzyskać wysoką wydajność, dokładniej dostrojiliśmy Redis:

  • Ustanowiono połączenie z gniazdem unix.
  • Wyłączyliśmy zapisywanie stanu na dysku (dla niezawodności można skonfigurować replikę i zapisać na dysku w osobnym Redisie).

W Redis pula jest tabelą mieszającą, ponieważ musimy mieć możliwość pobrania wszystkich transakcji w jednym zapytaniu i usunięcia transakcji jedna po drugiej. Próbowaliśmy użyć zwykłej listy, ale ładowanie całej listy jest wolniejsze.

Przy użyciu standardowego NodeJS biblioteki Redis osiągnęły wydajność 18 tys. transakcji na sekundę. Prędkość spadła 9 razy.

Ponieważ benchmark pokazał nam, że możliwości są wyraźnie 5 razy większe, zaczęliśmy optymalizować. Zmieniliśmy bibliotekę na ioredis i uzyskaliśmy wydajność 25 tys. na sekundę. Dodaliśmy transakcje jedna po drugiej za pomocą polecenia `hset`. Generowaliśmy więc wiele zapytań w Redis. Powstał pomysł połączenia transakcji w partie i wysłania ich za pomocą jednego polecenia `hmset`. Wynik to 32 tys. na sekundę.

Z kilku powodów, które opiszemy poniżej, pracujemy z danymi za pomocą `Buffer` i jak się okazuje, jeśli przed zapisem skonwertujesz je na tekst (`buffer.toString('hex')`) możesz uzyskać dodatkowe wydajność. W ten sposób prędkość wzrosła do 35 tys. na sekundę. Na chwilę obecną zdecydowaliśmy się zawiesić dalszą optymalizację.

Musieliśmy przejść na protokół binarny, ponieważ:

1. System często wylicza skróty, podpisy itp. i do tego potrzebuje danych w pliku `Buffer.

2. Dane binarne przesyłane między usługami ważą mniej niż tekst. Przykładowo przy wysyłaniu bloku zawierającego 1 milion transakcji dane w tekście mogą zająć ponad 300 megabajtów.

3. Ciągłe przekształcanie danych wpływa na wydajność.

Dlatego za podstawę przyjęliśmy własny protokół binarny do przechowywania i przesyłania danych, opracowany w oparciu o wspaniałą bibliotekę „dane binarne”.

W rezultacie otrzymaliśmy następujące struktury danych:

-Transakcja

  ```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,
  }
  ```

— Znak

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

-Blok

  ```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,
  }
  ```

Za pomocą zwykłych poleceń `BD.encode(block, Protocol).slice();` i `BD.decode(buffer, Protocol)` konwertujemy dane na `Buffer` w celu zapisania w Redis lub przesłania do innego węzła i pobrania dane z powrotem.

Mamy również 2 protokoły binarne do przesyłania danych pomiędzy usługami:

— Protokół interakcji z węzłem plazmowym poprzez gniazdo unix

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

gdzie:

  • `Type` — akcja, która ma zostać wykonana, np. 1 — sendTransaction, 2 — getTransaction;
  • „ładunek”. — dane, które należy przekazać do odpowiedniej funkcji;
  • `Identyfikator wiadomości` — identyfikator wiadomości, umożliwiający identyfikację odpowiedzi.

— Protokół interakcji między węzłami

  ```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)
  }
  ```

gdzie:

  • `kod` — kod komunikatu, np. 6 — PREPARE_NEW_BLOCK, 7 — BLOCK_VALID, 8 — BLOCK_COMMIT;
  • „Protokół wersji”. — wersja protokołu, ponieważ węzły o różnych wersjach mogą być tworzone w sieci i mogą działać inaczej;
  • „sekwencja”. — identyfikator komunikatu;
  • „policz Kawałek”. и `Numer fragmentu` niezbędne do dzielenia dużych wiadomości;
  • „długość”. и „ładunek”. długość i same dane.

Ponieważ wstępnie wpisaliśmy dane, ostateczny system jest znacznie szybszy niż biblioteka „rlp” Ethereum. Niestety nie mogliśmy jeszcze odmówić, ponieważ konieczne jest sfinalizowanie inteligentnej umowy, co planujemy w przyszłości.

Jeśli uda nam się osiągnąć prędkość 35 000 transakcji na sekundę, musimy je także przetworzyć w optymalnym czasie. Ponieważ przybliżony czas tworzenia bloku trwa 30 sekund, musimy uwzględnić go w bloku 1 000 000 transakcji, co oznacza wysyłanie większej liczby transakcji 100 MB danych.

Początkowo do komunikacji pomiędzy węzłami używaliśmy biblioteki `ethereumjs-devp2p`, ale nie była ona w stanie obsłużyć tak dużej ilości danych. W efekcie skorzystaliśmy z biblioteki `ws` i skonfigurowaliśmy przesyłanie danych binarnych poprzez websocket. Oczywiście napotkaliśmy również problemy przy wysyłaniu dużych pakietów danych, ale podzieliliśmy je na porcje i teraz te problemy zniknęły.

Tworzę także drzewo Merkle i obliczam skrót 1 000 000 transakcji wymaga ok 10 sekundy ciągłych obliczeń. W tym czasie połączenie ze wszystkimi węzłami zostaje zerwane. Postanowiono przenieść tę kalkulację do osobnego wątku.

Wnioski:

Tak naprawdę nasze odkrycia nie są nowe, ale z jakiegoś powodu wielu ekspertów zapomina o nich podczas opracowywania.

  • Używanie programowania funkcjonalnego zamiast programowania obiektowego zwiększa produktywność.
  • Monolit jest gorszy niż architektura usług dla produktywnego systemu NodeJS.
  • Używanie `worker_threads` do intensywnych obliczeń poprawia responsywność systemu, szczególnie w przypadku operacji we/wy.
  • gniazdo unix jest bardziej stabilne i szybsze niż żądania http.
  • Jeśli trzeba szybko przesłać duże ilości danych przez sieć, lepiej skorzystać z websocketów i wysłać dane binarne, podzielone na porcje, które w przypadku ich nie dotarcia można przekazać dalej, a następnie połączyć w jedną wiadomość.

Zapraszamy do odwiedzenia GitHub projekt: https://github.com/opporty-com/Plasma-Cash/tree/new-version

Współautorem artykułu jest Aleksander Nashivan, starszy programista Inteligentne rozwiązanie Inc.

Źródło: www.habr.com

Dodaj komentarz