Публічны тэст: рашэнне для прыватнасці і маштабаванасці ў Эфірыуме

Блокчэйн - Інавацыйная тэхналогія, якая абяцае палепшыць многія сферы чалавечага жыцця. Яна пераносіць рэальныя працэсы і прадукты ў лічбавую прастору, забяспечвае хуткасць і надзейнасць фінансавых аперацый, зніжае іх кошт, а таксама дазваляе ствараць сучасныя DAPP дадаткі з выкарыстаннем інтэлектуальных кантрактаў у дэцэнтралізаваных сетках.

Улічваючы шматлікія перавагі і разнастайныя сферы ўжывання блокчейн, можа здацца дзіўным, што гэтая шматабяцальная тэхналогія яшчэ не пракралася ва ўсе галіны. Праблема ў тым, што сучасным дэцэнтралізаваным блокчэйнам не хапае маштабаванасці. Эфірыум апрацоўвае каля 20 транзакцый у секунду, што недастаткова для задавальнення запатрабаванняў сучаснага дынамічнага бізнесу. У той жа час кампаніі, якія выкарыстоўваюць блокчейн тэхналогію, не вырашаюцца адмовіцца ад Эфірыума з-за яго высокай ступені абароны ад узлому і збояў сеткі.

Каб забяспечыць дэцэнтралізацыю, бяспеку і маштабаванасць у блокчейне, вырашаючы, такім чынам, Трылему Маштабаванасці, каманда распрацоўнікаў Opporty стварыла Plasma Cash - даччыны ланцужок, якая складаецца з смарт-кантракту і прыватнай сеткі на аснове Node.js, перыядычна якая перадае свой стан у каранёвы ланцужок (Эфірыум).

Публічны тэст: рашэнне для прыватнасці і маштабаванасці ў Эфірыуме

Ключавыя працэсы ў Plasma Cash

1. Карыстальнік выклікае функцыю смарт кантракту `deposit`, перадаючы ў яе суму ў ETH, якую ён жадае змясціць у токен Plasma Cash. Функцыя смарт-кантракту стварае токен і генеруе падзею аб гэтым.

2. Plasma Cash ноды, падпісаныя на падзеі смарт кантракту, атрымліваюць падзею аб стварэнні дэпазіту і дадаюць у пул транзакцыю аб стварэнні токена.

3. Перыядычна спецыяльныя ноды Plasma Cash бяруць усе транзакцыі з пула (да 1 мільёна) і фарміруюць з іх блок, вылічваюць дрэва Меркле і, адпаведна, хэш. Гэты блок адпраўляецца іншым нодам на верыфікацыю. Ноды правяраюць ці валідны хеш Меркле, ці валідныя транзакцыі (напрыклад, ці з'яўляецца адпраўнік токена яго ўласнікам). Пасля верыфікацыі блока нода выклікае функцыю `submitBlock` смарт-кантракту, якая захоўвае ў конревую ланцужок нумар і хэш Меркле блока. Смарт-кантракт генеруе падзею аб паспяховым даданні блока. Транзакцыі выдаляюцца з пула.

4. Ноды, якія атрымалі падзею аб шабміце блока, пачынаюць ужываць транзакцыі, якія былі дададзены ў блок.

5. У нейкі момант уласнік (ці не ўласнік) токена хоча вывесці яго з Plasma Cash. Для гэтага ён выклікае функцыю `startExit`, перадаючы ў яе інфармацыю аб апошніх 2 транзакцыях па токене, якія пацвярджаюць, што менавіта ён з'яўляецца ўладальнікам токена. Смарт-кантракт, выкарыстоўваючы хэш Меркле, правярае знаходжанне транзакцый у блоках і адпраўляе токен на выснову, якая адбудзецца праз два тыдні.

6. Калі аперацыя высновы токена адбылася з парушэннямі (токен быў выдаткаваны пасля пачатку працэдуры высновы ці токен да высновы ўжо быў чужым), уласнік токена можа абвергнуць выснову на працягу двух тыдняў.

Публічны тэст: рашэнне для прыватнасці і маштабаванасці ў Эфірыуме

Прыватнасць дасягаецца двума спосабамі

1. Каранёвы ланцужок нічога не ведае аб транзакцыях, якія фармуюцца і перасылаюцца ўсярэдзіне даччынага ланцужка. Публічнай застаецца інфармацыя аб тым, хто завёў і вывеў ETH у/з Plasma Cash.

2. Даччыны ланцужок дазваляе арганізаваць ананімныя транзакцыі, выкарыстоўваючы zk-SNARKs.

Тэхналагічны стэк

  • NodeJS
  • Redis
  • Этэрыум
  • Глеба

Тэставанне

Распрацоўваючы Plasma Cash, мы пратэставалі хуткасць працы сістэмы і атрымалі наступныя вынікі:

  • да 35 000 транзакцый у секунду дадаюцца ў пул;
  • да 1 тразакцый можа захоўваецца ў блоку.

Тэсты праводзіліся на 3 наступных серверах:

1. Intel Core i7-6700 Quad-Core Skylake Incl. NVMe SSD – 512 GB, 64 GB DDR4 RAM
Былі паднятыя 3 якія валідуюць Plasma Cash ноды.

2. AMD Ryzen 7 1700X Octa-Core "Summit Ridge" (Zen), SATA SSD – 500 GB, 64 GB DDR4 RAM
Была паднятая Ropsten testnet ETH нода.
Было паднята 3 якія валідуюць Plasma Cash ноды.

3. Intel Core i9-9900K Octa-Core incl. NVMe SSD – 1 TB, 64 GB DDR4 RAM
Была паднята 1 сабміт Plasma Cash нода.
Было паднята 3 якія валідуюць Plasma Cash ноды.
Запускаўся тэст на даданне транзакцый у Plasma Cash сетку.

Разам: 10 Plasma Cash нод у прыватнай сетцы.

Тэст 1

Каштуе ліміт на 1 мільён транзакцый у блоку. Таму 1 мільён транзакцый трапляюць у 2 блокі (бо сістэма паспявае ўзяць частку транзакцый і засабіць пакуль яны адпраўляюцца).


Зыходнае стан: апошні блок #7; у базе захавана 1 млн транзакцый і токенаў.

00:00 - запуск скрыпту генерацыі транзакцый
01:37 - створана 1 млн транзакцый і пачалася адпраўка ў ноду
01:46 - саміт нода ўзяла з пула 240к транзакцый і фармуе блок #8. Таксама бачым, што ў пул дадаецца 320к транзакцый за 10 сек.
01:58 — блок #8 падпісаны і адпраўлены на валідацыю
02:03 — блок #8 правалідаваны і выклікана функцыя `submitBlock` смарт-кантракта з хешам Меркле і нумарам блока
02:10 — скончыў працаваць дэма-скрыпт, які адправіў 1 млн транзакцый за 32 сек.
02:33 — ноды пачалі атрымліваць інфармацыю аб тым, што блок #8 дададзены ў каранёвы ланцужок, і пачалі выконваць 240к транзакцый.
02:40 - з пула было выдалена 240к транзакцый, якія ўжо ў блоку #8
02:56 — саміт нода ўзяла з пула пакінутыя 760к транзакцый і пачала вылічваць хэш Меркле і падпісваць блок #9
03:20 - усе ноды ўтрымліваюць 1млн 240к транзакцый і токенаў
03:35 — блок #9 падпісаны і адпраўляецца на валідацыю ў іншыя ноды
03:41 — адбылася памылка сеткі
04:40 - па таймаўце спынілася чаканне валідацыі блока #9
04:54 — саміт нода ўзяла з пула пакінутыя 760к транзакцый і пачала вылічваць хэш Меркле і падпісваць блок #9
05:32 — блок #9 падпісаны і адпраўляецца на валідацыю ў іншыя ноды
05:53 — блок #9 правалідаваны і адпраўлены ў каранёвы ланцужок
06:17 — ноды пачалі атрымліваць інфармацыю аб тым, што блок #9 дададзены ў каранёвы ланцужок і пачалі выконваць 760к транзакцый.
06:47 - пул ачысціўся ад транзакцый, якія ў блоку #9
09:06 - усе ноды ўтрымліваюць 2 млн транзакцый і токенаў

Тэст 2

Каштуе ліміт у 350к на блок. У выніку маем 3 блокі.


Зыходнае стан: апошні блок #9; у базе захавана 2 млн транзакцый і токенаў

00:00 - скрыпт генерацыі транзакцый ужо запушчаны
00:44 - створана 1 млн транзакцый і пачалася адпраўка ў ноду
00:56 - саміт нода ўзяла з пула 320к транзакцый і фармуе блок #10. Таксама бачым, што ў пул дадаецца 320к транзакцый за 10 сек.
01:12 — блок #10 падпісаны і адпраўляецца да іншых нодаў на валідацыю
01:18 — скончыў працаваць дэма-скрыпт, які адправіў 1 млн транзакцый за 34 сек.
01:20 — блок #10 правалідаваны і адпраўлены ў каранёвы ланцужок
01:51 — усе ноды атрымалі з каранёвага ланцужка інфармацыю аб тым, што блок #10 дададзены, і пачынаюць ужываць 320к транзакцый.
02:01 — пул ачысціўся на 320к транзакцый, якія былі дададзены ў блок #10
02:15 — саміт нода ўзяла з пула 350к транзакцый і фарміруе блок #11
02:34 — блок #11 падпісаны і адпраўляецца іншым нодам на валідацыю
02:51 — блок #11 правалідаваны і адпраўлены ў каранёвы ланцужок
02:55 - апошняя нода выканала транзакцыі з блока #10
10:59 - вельмі доўга ў каранёвым ланцужку выконвалася транзакцыя з сабмітом блока #9, але яна выканалася і ўсе ноды атрымалі пра гэта інфармацыю і пачалі выконваць 350к транзакцый.
11:05 — пул ачысціўся на 320к транзакцый, якія былі дададзены ў блок #11
12:10 - усе ноды ўтрымліваюць 1 млн 670к транзакцый і токенаў
12:17 - Сабміт нода ўзяла з pool 330к транзакцый і фармуе блок #12
12:32 — блок #12 падпісаны і адпраўляецца іншым нодам на валідацыю
12:39 — блок #12 правалідаваны і адпраўлены ў каранёвы ланцужок
13:44 — усе ноды атрымалі з каранёвага ланцужка інфармацыю аб тым, што блок #12 дададзены і пачынаюць ужываць 330к транзакцый.
14:50 - усе ноды ўтрымліваюць 2 млн транзакцый і токенаў

Тэст 3

У першым і ў другім серверах, адна якая валідуе нода была заменена на саміт ноду.


Зыходнае стан: апошні блок #84; у базе захавана 0 транзакцый і токенаў

00:00 — Запушчана 3 скрыпты, якія генеруюць і адпраўляюць па 1 млн транзакцый
01:38 — створана 1млн транзакцый і пачалася адпраўка ў саміт ноду #3
01:50 — саміт нода #3 узяла з пула 330к транзакцый і фармуе блок #85 (f21). Таксама бачым, што ў пул дадаецца 350к транзакцый за 10 сек.
01:53 — створана 1млн транзакцый і пачалася адпраўка ў саміт ноду #1
01:50 — саміт нода #3 узяла з пула 330к транзакцый і фармуе блок #85 (f21). Таксама бачым, што ў пул дадаецца 350к транзакцый за 10 сек.
02:01 — саміт нода #1 узяла з пула 250к транзакцый і фармуе блок #85 (65e)
02:06 — блок #85 (f21) падпісаны і адпраўляецца іншым нодам на валідацыю
02:08 - скончыў працаваць дэма-скрыпт сервера #3, які адправіў 1млн транзакцый за 30 секунд
02:14 — блок #85 (f21) правалідаваны і адпраўлены ў каранёвы ланцужок
02:19 — блок #85 (65e) падпісаны і адпраўляецца іншым нодам на валідацыю
02:22 — створана 1млн транзакцый і пачалася адпраўка ў саміт ноду #2
02:27 — блок #85 (65e) правалідаваны і адпраўлены ў каранёвы ланцужок
02:29 — саміт нода #2 узяла з пула 111855 транзакцый і фарміруе блок #85 (256).
02:36 — блок #85 (256) падпісаны і адпраўляецца іншым нодам на валідацыю
02:36 - скончыў працаваць дэма-скрыпт сервера #1, які адправіў 1млн транзакцый за 42.5 секунд
02:38 — блок #85 (256) правалідаваны і адпраўлены ў каранёвы ланцужок
03:08 - скончыў працаваць справа-скрыпт сервера #2, які адправіў 1млн транзакцый за 47 сек.
03:38 — усе ноды атрымалі з каранёвага ланцужка інфармацыю аб тым, што блокі #85 (f21), #86(65e), #87(256) дабаўлены і пачынаюць прымяняць 330к, 250к, 111855 транзакцый.
03:49 — пул ачысціўся на 330к, 250к, 111855 транзакцый, якія былі дададзены ў блокі #85 (f21), #86(65e), #87(256)
03:59 — сабміт нода #1 узяла з пула 888145 транзакцый і фармуе блок #88 (214), сабміт нода #2 узяла з пула 750к транзакцый і фармуе блок #88 (50a), сабміт нода #3 узяла з пула 670 фармуе блок #88 (d3b)
04:44 — блок #88 (d3b) падпісаны і адпраўляецца іншым нодам на валідацыю
04:58 — блок #88 (214) падпісаны і адпраўляецца іншым нодам на валідацыю
05:11 — блок #88 (50a) падпісаны і адпраўляецца іншым нодам на валідацыю
05:11 — блок #85 (d3b) правалідаваны і адпраўлены ў каранёвы ланцужок
05:36 — блок #85 (214) правалідаваны і адпраўлены ў каранёвы ланцужок
05:43 — усе ноды атрымалі з каранёвага ланцужка інфармацыю аб тым, што блокі #88 (d3b), #89(214) дабаўлены і пачынаюць прымяняць 670к, 750к транзакцый.
06:50 - з-за абрыву сувязі блок #85 (50a) не быў праваліраваны
06:55 — саміт нода #2 узяла з pool 888145 транзакцый і фармуе блок #90 (50a)
08:14 — блок #90 (50a) падпісаны і адпраўляецца іншым нодам на валідацыю
09:04 — блок #90 (50a) правалідаваны і адпраўлены ў каранёвы ланцужок
11:23 - усе ноды атрымалі з каранёвага ланцужка інфармацыю аб тым, што блок #90 (50a) дададзены, і пачынаюць прымяняць 888145 транзакцый. Пры гэтым ужо даўно сервер #3 ужыў транзакцыі з блокаў #88 (d3b), #89(214)
12:11 - усе пулы пустыя
13:41 — усе ноды сервера #3 утрымоўваюць 3млн транзакцый і токенаў
14:35 — усе ноды сервера #1 утрымоўваюць 3млн транзакцый і токенаў
19:24 — усе ноды сервера #2 утрымоўваюць 3млн транзакцый і токенаў

Перашкоды

У час распрацоўкі Plasma Cash мы сутыкнуліся з наступнымі праблемамі, якія паступова вырашалі і вырашаем:

1. Канфлікт узаемадзеяння розных функцый сістэмы. Напрыклад, функцыя дадання транзакій у пул блакавала працу сабміта і валідацыі блокаў, і наадварот, што вяло да прасадкі хуткасці.

2. Не адразу было зразумела, якім чынам адправіць вялікую колькасць транзакцый і пры гэтым мінімізаваць выдаткі на перадачу дадзеных.

3. Не было зразумела, якім чынам і дзе захоўваць дадзеныя, каб дасягнуць высокіх вынікаў.

4. Не было ясна, як арганізаваць сетку паміж нодамі, бо памер блока з 1 мільёнам тразакцый займае каля 100 Мб.

5. Праца ў аднаструменным рэжыме ірве злучэнне паміж нодамі, калі адбываюцца доўгія вылічэнні (напрыклад, пабудова дрэва Меркле і вылічэнне яго хеша).

Як мы з усім гэтым справіліся?

Першая версія Plasma Cash ноды ўяўляла сабой нейкі камбаін, які мог рабіць усё адначасова: прымаць транзакцыі, сабіці і валідаваць блокі, падаваў API для доступу да дадзеных. Бо NodeJS першапачаткова аднаструменная, цяжкая функцыя разліку дрэва Меркле блакавала функцыю дадання транзакцыі. Мы бачылі два варыянты рашэння дадзенай праблемы:

1. Запусціць некалькі NodeJS працэсаў, кожны з якіх выконвае пэўныя функцыі.

2. Выкарыстоўваць worker_threads і вынесці выкананне часткі кода ў патокі.

У выніку мы скарысталіся абедзвюма варыянтамі адначасова: лагічна падзялілі адну ноду на 3 часткі, якія могуць працаваць асобна, але ў той жа час сінхронна

1. Сабміт нода, якая прымае транзакцыі ў пул і займаецца стварэннем блокаў.

2. Валодная нода, якая правярае валіднасць нод.

3. API нода - дае API для доступу да дадзеных.

Пры гэтым да кожнай нады можна падлучыцца праз unix socket пасродкам cli.

Цяжкія аперацыі, такія як разлік дрэва Меркле, мы вынеслі ў асобны струмень.

Такім чынам, мы дабіліся нармальнай працы ўсіх функцый Plasma Cash адначасова і без збояў.

Як толькі сістэма функцыянальна зарабіла, мы пачалі тэставаць хуткасць і, нажаль, атрымалі нездавальняючыя вынікі: 5 000 транзакцый у секунду і да 50 000 транзакцый у блоку. Прыйшлося высвятляць, што рэалізавана няправільна.

Для пачатку мы пачалі тэсціраваць механізм зносін з Plasma Cash, каб даведацца пікавую магчымасць сістэмы. Раней мы пісалі, што Plasma Cash нода дае unix socket інтэрфейс. Першапачаткова ён быў тэкставым. json аб'екты перасылаліся, выкарыстоўваючы `JSON.parse()` і `JSON.stringify()`.

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

Мы замералі хуткасць перасылкі такіх аб'ектаў і атрымалі ~ 130к у секунду. Спрабавалі замяніць стандартныя функцыі працы з json, але прадукцыйнасць не падвысілася. Павінна быць рухавік V8 добра аптымізаваны на дадзеныя аперацыі.

Праца з транзакцыямі, токенамі, блокамі ў нас ажыццяўлялася праз класы. Пры стварэнні такіх класаў прадукцыйнасць асела ў 2 разы, што сведчыць: OOP нам не падыходзіць. Прыйшлося перапісваць усё на чыста функцыянальны падыход.

Запіс у базу

Першапачаткова для захоўвання дадзеных быў абраны Redis як адно з самых прадукцыйных рашэнняў, якія задавальняе нашым патрабаваннем: key-value сховішча, праца з hash-табліцамі, мноствы. Запусцілі redis-benchmark і атрымалі ~80да аперацый у секунду ў рэжыме 1 pipelining.

Для высокай прадукцыйнасці мы наладзілі Redis больш тонка:

  • Усталявалі unix socket злучэнне.
  • Адключылі захаванні стану на дыск (для надзейнасці можна наладзіць рэпліку і ўжо ў асобным Redis рабіць захаванне на дыск).

У Redis пул – гэта хэш-табліца, бо нам патрэбна магчымасць атрымліваць усе транзакцыі адным запытам і выдаляць транзакцыі паасобку. Спрабавалі выкарыстоўваць звычайны спіс, але ён працуе павольней пры выгрузцы ўсяго спісу.

Пры выкарыстанні стандартнай NodeJS бібліятэкі Redis атрымалі прадукцыйнасць у 18к транзакцый у секунду. Хуткасць упала ў 9 разоў.

Бо benchmark паказваў нам магчымасці відавочна ў 5 разоў больш, пачалі аптымізаваць. Памянялі бібліятэку на ioredis і атрымалі прадукцыйнасць ужо 25к у секунду. Транзакцыі мы дадавалі паасобку, выкарыстоўваючы каманду `hset`. Такім чынам, мы генеравалі шмат запытаў у Redis. Узнікла ідэя аб'ядноўваць транзакцыі ў пачкі і адпраўляць іх адной камандай `hmset`. Вынік - 32к у сек.

Па некалькіх прычынах, якія апішам ніжэй, з дадзенымі мы працуем выкарыстоўваючы `Buffer` і, як аказалася, калі яго перавесці ў тэкст (`buffer.toString('hex')`) перад запісам, можна атрымаць дадатковую прадукцыйнасць. Такім чынам, хуткасць удалося павысіць да 35к у секунду. На гэты момант вырашылі прыпыніць далейшую аптымізацыю.

Нам прыйшлося перайсці на бінарны пратакол паколькі:

1. Сістэма часта вылічвае хэшы, подпісы і да т.п., і для гэтага ёй патрэбныя дадзеныя ў `Buffer.

2. Пры перасыланні паміж сэрвісамі бінарныя дадзеныя важаць менш, чым тэкст. Напрыклад, пры адпраўцы блока з 1 млн транзакцыі, дадзеныя ў тэксце могуць займаць больш за 300 мегабайт.

3. Пастаяннае пераўтварэнне дадзеных уплывае на прадукцыйнасць.

Таму за аснову мы ўзялі ўласны бінарны пратакол захоўвання і перадачы даных, распрацаваны на базе выдатнай бібліятэкі `binary-data`.

У выніку ў нас атрымаліся наступныя структуры дадзеных:

- Transaction

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

Звычайнымі камандамі `BD.encode(block, Protocol).slice();` і `BD.decode(buffer, Protocol)` мы ператвараем дадзеныя ў `Buffer` для захавання ў Redis або перасыланні іншай нодзе і вымання дадзеных зваротна.

Таксама ў нас ёсць 2 бінарных пратаколы для перадачы даных паміж сэрвісамі:

— Пратакол для ўзаемадзеяння з Plasma Node з дапамогай 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)
  }
  ```

дзе:

  • `тып` - дзеянне, якое трэба выканаць, напрыклад, 1 - sendTransaction, 2 - getTransaction;
  • `payload` - дадзеныя, якія трэба перадаць у адпаведную функцыю;
  • `messageId` - ідзі паведамлення, каб можна было ідэнтыфікаваць адказ.

- Пратакол узаемадзеяння паміж нодамі

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

дзе:

  • `код` - код паведамленне, напрыклад 6 - PREPARE_NEW_BLOCK, 7 - BLOCK_VALID, 8 - BLOCK_COMMIT;
  • `versionProtocol` - версія пратаколу, бо ў сетцы могуць быць паднятыя ноды з рознымі версіямі і яны могуць працаваць па-рознаму;
  • `seq` - Ідэнтыфікатар паведамлення;
  • `countChunk` и `chunkNumber` неабходны для драбнення вялікіх паведамленняў;
  • `length` и `payload` даўжыня і самі дадзеныя.

Бо мы загадзя тыпізавалі дадзеныя, канчатковая сістэма працуе нашмат хутчэй, чым `rlp` бібліятэка ад Эфірыума. Нажаль, нам пакуль не атрымалася ад яе адмовіўся, бо неабходна дапрацаваць смарт-кантракт, што мы і плануем зрабіць у будучыні.

Калі ў нас атрымалася дасягнуць хуткасці 35 000 транзакцый у секунду, нам таксама трэба апрацоўваць іх за аптымальны час. Бо прыкладны час фармавання блока займае 30 секунд, нам неабходна ўлучыць у блок 1 000 000 транзакцый, што азначае перасылку больш 100 мб дадзеных.

Першапачаткова мы выкарыстоўвалі `ethereumjs-devp2p` бібліятэку для сувязі нод, але яна не спраўлялася з такой колькасцю дадзеных. У выніку мы скарысталіся бібліятэкай `ws` і наладзілі перасылку бінарных дадзеных па websocket. Вядома, мы таксама сутыкнуліся з праблемамі пры перасылцы вялікіх пакетаў дадзеных, але мы падзялілі іх на чанкі і зараз гэтых праблем няма.

Таксама фармаванне дрэва Меркле і вылічванне хеша 1 000 000 транзакцый патрабуе каля 10 секунд бесперапыннага вылічэнні. За гэты час паспявае абарвацца злучэнне з усімі нодамі. Было вырашана перанесці гэтае вылічэнне ў асобны струмень.

Высновы:

Насамрэч, нашы высновы не новыя, але чамусьці многія спецыялісты забываюцца пра іх пры распрацоўцы.

  • Выкарыстанне Functional Programming замест Object-Oriented Programming павялічвае прадукцыйнасць.
  • Маналіт горш, чым сэрвісная архітэктура для прадукцыйнай сістэмы на NodeJS.
  • Выкарыстанне `worker_threads` для цяжкіх вылічэнняў паляпшае спагадлівасць сістэмы, асабліва пры працы з аперацыямі i / o.
  • unix socket стабільней і хутчэй, чым http запыты.
  • Калі трэба хутка перадаваць вялікія дадзеныя па сетцы, лепш скарыстаюцца websocket-амі і слаць бінарныя дадзеныя, разбітыя на чанкі, якія можна пераправіць, калі яны не дойдуць, і потым аб'яднаць у адно паведамленне.

Запрашаем вас наведаць GitHub праекта: https://github.com/opporty-com/Plasma-Cash/tree/new-version

Артыкул быў напісаны ў суаўтарстве з Аляксандрам Нашыванам, старэйшым распрацоўшчыкам Clever Solution Inc.

Крыніца: habr.com

Дадаць каментар