Јавни тест: решење за приватност и скалабилност на Етхереум-у

Блоцкцхаин је иновативна технологија која обећава да ће побољшати многе области људског живота. Он преноси стварне процесе и производе у дигитални простор, обезбеђује брзину и поузданост финансијских трансакција, смањује њихову цену, а такође вам омогућава да креирате модерне ДАПП апликације користећи паметне уговоре у децентрализованим мрежама.

С обзиром на многе предности и различите примене блокчејна, може изгледати изненађујуће да ова обећавајућа технологија још није ушла у сваку индустрију. Проблем је у томе што савремени децентрализовани блок ланац немају скалабилност. Етхереум обрађује око 20 трансакција у секунди, што није довољно да задовољи потребе данашњег динамичног пословања. Истовремено, компаније које користе блоцкцхаин технологију оклевају да напусте Етхереум због његовог високог степена заштите од хаковања и кварова на мрежи.

Да би се осигурала децентрализација, сигурност и скалабилност у блокчејну, решавајући тако трилему скалабилности, развојни тим Оппорти креирао је Пласма Цасх, ланац подружница који се састоји од паметног уговора и приватне мреже засноване на Ноде.јс, која периодично преноси своје стање у основни ланац (Етхереум).

Јавни тест: решење за приватност и скалабилност на Етхереум-у

Кључни процеси у Пласма Цасх

1. Корисник назива функцију паметног уговора `депозит`, преносећи у њу износ ЕТХ који жели да положи у токен Пласма Цасх. Функција паметног уговора креира токен и генерише догађај о њему.

2. Пласма Цасх чворови претплаћени на догађаје паметног уговора добијају догађај о креирању депозита и додају трансакцију о креирању токена у скуп.

3. Периодично, посебни Пласма Цасх чворови узимају све трансакције из пула (до 1 милион) и од њих формирају блок, израчунавају Меркле дрво и, сходно томе, хеш. Овај блок се шаље другим чворовима на верификацију. Чворови проверавају да ли је Меркле хеш исправан и да ли су трансакције важеће (на пример, да ли је пошиљалац токена његов власник). Након верификације блока, чвор позива функцију `субмитБлоцк` паметног уговора, која чува број блока и Меркле хеш у ивични ланац. Паметни уговор генерише догађај који указује на успешно додавање блока. Трансакције се уклањају из групе.

4. Чворови који приме догађај подношења блока почињу да примењују трансакције које су додате у блок.

5. У неком тренутку, власник (или не-власник) токена жели да га повуче из Пласма Цасх-а. Да би то урадио, он позива функцију `стартЕкит`, преносећи у њу информације о последње 2 трансакције на токену, које потврђују да је он власник токена. Паметни уговор, користећи Меркле хеш, проверава присуство трансакција у блоковима и шаље токен на повлачење, што ће се десити за две недеље.

6. Ако је до операције повлачења токена дошло са кршењима (токен је потрошен након што је поступак повлачења започео или је токен већ био нечији пре повлачења), власник токена може побити повлачење у року од две недеље.

Јавни тест: решење за приватност и скалабилност на Етхереум-у

Приватност се постиже на два начина

1. Основни ланац не зна ништа о трансакцијама које се генеришу и прослеђују унутар подређеног ланца. Информације о томе ко је депоновао и повукао ЕТХ из Пласма Цасх-а остају јавне.

2. Подређени ланац дозвољава анонимне трансакције користећи зк-СНАРК-ове.

Технолошки стог

  • НодеЈС
  • Редис
  • Eterijum
  • Соилд

Тестирање

Током развоја Пласма Цасх-а, тестирали смо брзину система и добили следеће резултате:

  • до 35 трансакција у секунди се додаје у скуп;
  • до 1 трансакција може бити ускладиштено у блоку.

Тестови су обављени на следећа 3 сервера:

1. Интел Цоре и7-6700 Куад-Цоре Скилаке укљ. НВМе ССД – 512 ГБ, 64 ГБ ДДР4 РАМ
Подигнута су 3 валидирајућа Пласма Цасх чвора.

2. АМД Ризен 7 1700Кс Оцта-Цоре “Суммит Ридге” (Зен), САТА ССД – 500 ГБ, 64 ГБ ДДР4 РАМ-а
Ропстен тестнет ЕТХ чвор је подигнут.
Подигнута су 3 валидирајућа Пласма Цасх чвора.

3. Интел Цоре и9-9900К Оцта-Цоре укљ. НВМе ССД – 1 ТБ, 64 ГБ ДДР4 РАМ-а
1 Пласма Цасх чвор за подношење је подигнут.
Подигнута су 3 валидирајућа Пласма Цасх чвора.
Покренут је тест за додавање трансакција у мрежу Пласма Цасх.

Укупно: 10 Пласма Цасх чворова у приватној мрежи.

Test 1

Постоји ограничење од 1 милион трансакција по блоку. Дакле, 1 милион трансакција пада у 2 блока (пошто систем успева да узме део трансакција и достави док се шаљу).


Почетно стање: последњи блок #7; 1 милион трансакција и токена се чува у бази података.

00:00 — почетак скрипте за генерисање трансакције
01:37 - 1 милион трансакција је креирано и почело је слање у чвор
01:46 — субмит чвор је узео 240 хиљада трансакција из скупа и формира блок #8. Такође видимо да се 320 трансакција додаје групи за 10 секунди
01:58 — блок #8 је потписан и послат на валидацију
02:03 — блок #8 је потврђен и функција `субмитБлоцк` паметног уговора се позива са Меркле хешом и бројем блока
02:10 — завршена је демо скрипта која је послала милион трансакција за 1 секунде
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 милиона трансакција и токена

Test 2

Постоји ограничење од 350к по блоку. Као резултат, имамо 3 блока.


Почетно стање: последњи блок #9; 2 милиона трансакција и токена се чувају у бази података

00:00 — скрипта за генерисање трансакција је већ покренута
00:44 - 1 милион трансакција је креирано и почело је слање у чвор
00:56 — субмит чвор је узео 320 хиљада трансакција из скупа и формира блок #10. Такође видимо да се 320 трансакција додаје групи за 10 секунди
01:12 — блок #10 је потписан и послат другим чворовима на валидацију
01:18 — завршена је демо скрипта која је послала милион трансакција за 1 секунде
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 — субмит чвор је узео 330 трансакција из скупа и формира блок #12
12:32 — блок #12 је потписан и послат другим чворовима на валидацију
12:39 — блок #12 је потврђен и послат у основни ланац
13:44 - сви чворови су примили информацију из коренског ланца да је блок #12 додат и почињу да примењују 330к трансакција
14:50 - сви чворови садрже 2 милиона трансакција и токена

Test 3

На првом и другом серверу, један чвор за валидацију је замењен чвором за подношење.


Почетно стање: последњи блок #84; 0 трансакција и токена сачуваних у бази података

00:00 — Покренуте су 3 скрипте које генеришу и шаљу по 1 милион трансакција
01:38 — 1 милион трансакција је креирано и почело је слање на субмит чвор #3
01:50 — субмит чвор #3 узео је 330к трансакција из скупа и блок образаца #85 (ф21). Такође видимо да се 350 трансакција додаје групи за 10 секунди
01:53 — 1 милион трансакција је креирано и почело је слање на субмит чвор #1
01:50 — субмит чвор #3 узео је 330к трансакција из скупа и блок образаца #85 (ф21). Такође видимо да се 350 трансакција додаје групи за 10 секунди
02:01 — субмит чвор #1 је узео 250 трансакција из скупа и блок образаца #85 (65е)
02:06 — блок #85 (ф21) је потписан и послат другим чворовима на валидацију
02:08 — демо скрипта сервера #3, која је послала милион трансакција за 1 секунди, завршила са радом
02:14 — блок #85 (ф21) је потврђен и послат у основни ланац
02:19 — блок #85 (65е) је потписан и послат другим чворовима на валидацију
02:22 — 1 милион трансакција је креирано и почело је слање на субмит чвор #2
02:27 — блок #85 (65е) потврђен и послат у основни ланац
02:29 — субмит чвор #2 је узео 111855 трансакција из скупа и блок образаца #85 (256).
02:36 — блок #85 (256) је потписан и послат другим чворовима на валидацију
02:36 — демо скрипта сервера #1, која је послала милион трансакција за 1 секунди, завршила са радом
02:38 — блок #85 (256) је потврђен и послат у основни ланац
03:08 — завршила је са радом скрипта сервера #2, која је послала милион трансакција за 1 секунди
03:38 - сви чворови су примили информацију из основног ланца да су додани блокови #85 (ф21), #86(65е), #87(256) и почели да примењују 330к, 250к, 111855 трансакција
03:49 - скуп је очишћен на 330к, 250к, 111855 трансакција које су додате у блокове #85 (ф21), #86(65е), #87(256)
03:59 — чвор за слање #1 узео је 888145 трансакција из скупа и блок образаца #88 (214), чвор за слање #2 узео је 750 трансакција из скупа и блок образаца #88 (50а), чвор за слање #3 је узео 670 трансакција од базен и форме блок #88 (д3б)
04:44 — блок #88 (д3б) је потписан и послат другим чворовима на валидацију
04:58 — блок #88 (214) је потписан и послат другим чворовима на валидацију
05:11 — блок #88 (50а) је потписан и послат другим чворовима на валидацију
05:11 — блок #85 (д3б) је потврђен и послат у основни ланац
05:36 — блок #85 (214) је потврђен и послат у основни ланац
05:43 - сви чворови су примили информације из основног ланца који блокови #88 (д3б), #89(214) су додати и почињу да примењују 670к, 750к трансакција
06:50 — због грешке у комуникацији, блок #85 (50а) није потврђен
06:55 — субмит чвор #2 узео је 888145 трансакција из скупа и блок образаца #90 (50а)
08:14 — блок #90 (50а) је потписан и послат другим чворовима на валидацију
09:04 — блок #90 (50а) је потврђен и послат у основни ланац
11:23 - сви чворови су примили информацију из коренског ланца да је додат блок #90 (50а) и почињу да примењују 888145 трансакција. У исто време, сервер #3 је већ применио трансакције из блокова #88 (д3б), #89(214)
12:11 - сви базени су празни
13:41 — сви чворови сервера #3 садрже 3 милиона трансакција и токена
14:35 — сви чворови сервера #1 садрже 3 милиона трансакција и токена
19:24 — сви чворови сервера #2 садрже 3 милиона трансакција и токена

Prepreke

Током развоја Пласма Цасх-а наишли смо на следеће проблеме, које смо постепено решавали и решавамо:

1. Сукоб у интеракцији различитих функција система. На пример, функција додавања трансакција у базен блокирала је рад подношења и валидације блокова, и обрнуто, што је довело до пада брзине.

2. Није одмах било јасно како послати огроман број трансакција уз минимизирање трошкова преноса података.

3. Није било јасно како и где чувати податке да би се постигли високи резултати.

4. Није било јасно како организовати мрежу између чворова, пошто величина блока са 1 милион трансакција заузима око 100 МБ.

5. Рад у једнонитном режиму прекида везу између чворова када дође до дугих прорачуна (на пример, прављење Меркле дрвета и израчунавање његовог хеша).

Како смо се носили са свим овим?

Прва верзија Пласма Цасх чвора била је нека врста комбинаца који је могао да ради све у исто време: прихвата трансакције, подноси и потврђује блокове и обезбеђује АПИ за приступ подацима. Пошто је НодеЈС изворно једнонит, тешка функција израчунавања Мерклеовог стабла блокирала је функцију додавања трансакције. Видели смо две опције за решавање овог проблема:

1. Покрените неколико НодеЈС процеса, од којих сваки обавља одређене функције.

2. Користите воркер_тхреадс и померите извршење дела кода у нити.

Као резултат тога, користили смо обе опције у исто време: логички смо поделили један чвор на 3 дела који могу да раде одвојено, али истовремено синхроно

1. Чвор за подношење, који прихвата трансакције у скуп и креира блокове.

2. Чвор за проверу ваљаности који проверава валидност чворова.

3. АПИ чвор - обезбеђује АПИ за приступ подацима.

У овом случају, можете се повезати са сваким чвором преко уникс утичнице користећи цли.

Преместили смо тешке операције, као што је израчунавање Мерклеовог дрвета, у засебну нит.

Тиме смо постигли нормалан рад свих Пласма Цасх функција истовремено и без кварова.

Када је систем постао функционалан, почели смо да тестирамо брзину и, нажалост, добили смо незадовољавајуће резултате: 5 трансакција у секунди и до 000 трансакција по блоку. Морао сам да схватим шта је погрешно имплементирано.

За почетак смо почели да тестирамо механизам комуникације са Пласма Цасх-ом да бисмо сазнали врхунску способност система. Раније смо писали да Пласма Цасх чвор пружа уникс соцкет интерфејс. У почетку је био заснован на тексту. јсон објекти су послати помоћу `ЈСОН.парсе()` и `ЈСОН.стрингифи()`.

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

Измерили смо брзину преноса таквих објеката и пронашли ~ 130к у секунди. Покушали смо да заменимо стандардне функције за рад са јсоном, али перформансе се нису побољшале. В8 мотор мора бити добро оптимизован за ове операције.

Радили смо са трансакцијама, токенима и блоковима кроз класе. Приликом креирања таквих класа, учинак је пао за 2 пута, што указује да ООП није погодан за нас. Морао сам све да препишем на чисто функционалан приступ.

Снимање у бази података

У почетку је Редис изабран за складиштење података као једно од најпродуктивнијих решења које задовољава наше захтеве: складиштење кључ-вредност, рад са хеш табелама, скупови. Покренули смо редис-бенцхмарк и добили ~80 операција у секунди у 1 режиму цевовода.

За високе перформансе, финије смо подесили Редис:

  • Успостављена је уник соцкет веза.
  • Онемогућили смо чување стања на диску (ради поузданости, можете подесити реплику и сачувати на диск у посебном Редис-у).

У Редис-у, скуп је хеш табела јер морамо бити у могућности да преузмемо све трансакције у једном упиту и избришемо трансакције једну по једну. Покушали смо да користимо обичну листу, али је спорије када се учитава цела листа.

Када се користи стандардни НодеЈС, Редис библиотеке су постигле учинак од 18 хиљада трансакција у секунди. Брзина је пала 9 пута.

Пошто нам је бенчмарк показао да су могућности очигледно 5 пута веће, почели смо да оптимизујемо. Променили смо библиотеку у иореди и добили перформансе од 25 хиљада у секунди. Додавали смо трансакције једну по једну користећи команду `хсет`. Тако да смо генерисали много упита у Редис-у. Појавила се идеја да се трансакције комбинују у пакете и шаљу једном командом `хмсет`. Резултат је 32к у секунди.

Из неколико разлога, које ћемо описати у наставку, ми радимо са подацима користећи `Буффер` и, како се испоставило, ако их конвертујете у текст (`буффер.тоСтринг('хек')`) пре писања, можете добити додатне перформансе. Тако је брзина повећана на 35к у секунди. Тренутно смо одлучили да обуставимо даљу оптимизацију.

Морали смо да пређемо на бинарни протокол јер:

1. Систем често израчунава хешове, потписе итд., а за то су му потребни подаци у `Буффер-у.

2. Када се шаљу између услуга, бинарни подаци су мањи од текста. На пример, када се шаље блок са 1 милион трансакција, подаци у тексту могу заузети више од 300 мегабајта.

3. Стална трансформација података утиче на перформансе.

Стога смо за основу узели сопствени бинарни протокол за чување и пренос података, развијен на основу дивне библиотеке `бинари-дата`.

Као резултат, добили смо следеће структуре података:

— Трансакција

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

— Токен

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

-Блокирати

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

Са уобичајеним командама `БД.енцоде(блоцк, Протоцол).слице();` и `БД.децоде(буффер, Протоцол)` конвертујемо податке у `Буффер` за чување у Редис-у или прослеђивање на други чвор и преузимање податке назад.

Такође имамо 2 бинарна протокола за пренос података између сервиса:

— Протокол за интеракцију са плазма чвором преко уник соцкета

  ```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 — сендТрансацтион, 2 — гетТрансацтион;
  • `корисни терет` — податке које треба проследити одговарајућој функцији;
  • `мессагеИд` — ИД поруке тако да се одговор може идентификовати.

— Протокол за интеракцију између чворова

  ```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 — ПРЕПАРЕ_НЕВ_БЛОЦК, 7 — БЛОЦК_ВАЛИД, 8 — БЛОЦК_ЦОММИТ;
  • `версионПротоцол` — верзија протокола, пошто се чворови са различитим верзијама могу подићи на мрежи и могу другачије да раде;
  • `сек` — идентификатор поруке;
  • `цоунтЦхунк` и `цхункНумбер` неопходно за цепање великих порука;
  • `дужина` и `корисни терет` дужине и самих података.

Пошто смо унапред откуцали податке, коначни систем је много бржи од Етхереумове `рлп` библиотеке. Нажалост, још нисмо у могућности да то одбијемо, јер је неопходно финализирати смарт уговор, што планирамо да урадимо у будућности.

Ако бисмо успели да достигнемо брзину 35 000 трансакција у секунди, такође морамо да их обрадимо у оптималном времену. Пошто приближно време формирања блока траје 30 секунди, потребно је да га укључимо у блок 1 000 000 трансакције, што значи слање више 100 МБ података.

У почетку смо користили библиотеку `етхереумјс-девп2п` за комуникацију између чворова, али она није могла да поднесе толико података. Као резултат тога, користили смо библиотеку `вс` и конфигурисали слање бинарних података преко вебсоцкета. Наравно, наилазили смо и на проблеме приликом слања великих пакета података, али смо их поделили на делове и сада су ти проблеми нестали.

Такође формирање Мерклеовог дрвета и израчунавање хеша 1 000 000 трансакције захтева око 10 секунди непрекидног израчунавања. За то време, веза са свим чворовима успева да се прекине. Одлучено је да се овај прорачун премести у засебну нит.

Закључци:

У ствари, наша открића нису нова, али из неког разлога многи стручњаци заборављају на њих приликом развоја.

  • Коришћење функционалног програмирања уместо објектно оријентисаног програмирања побољшава продуктивност.
  • Монолит је гори од услужне архитектуре за продуктиван НодеЈС систем.
  • Коришћење `воркер_тхреадс` за тешко рачунање побољшава одзив система, посебно када се ради о И/о операцијама.
  • уникс соцкет је стабилнији и бржи од хттп захтева.
  • Ако треба брзо да пренесете велике податке преко мреже, боље је да користите вебсокете и пошаљете бинарне податке, подељене у делове, који се могу проследити ако не стигну, а затим спојити у једну поруку.

Позивамо вас да посетите ГитХуб пројекат: https://github.com/opporty-com/Plasma-Cash/tree/new-version

Чланак је коаутор Александар Нашиван, виши програмер Цлевер Солутион Инц.

Извор: ввв.хабр.цом

Додај коментар