Test public : une solution pour la confidentialité et la scalabilité sur Ethereum

Blockchain est une technologie innovante qui promet d'améliorer de nombreux aspects de la vie humaine. Elle transfÚre des processus et des produits réels dans l'espace numérique, assurant la rapidité et la fiabilité des transactions financiÚres, réduisant leur coût, et permettant également de créer des applications DAPP modernes utilisant des contrats intelligents dans des réseaux décentralisés.

Étant donnĂ© les nombreux avantages et les diffĂ©rentes applications de la blockchain, il peut sembler Ă©trange que cette technologie prometteuse ne soit pas encore prĂ©sente dans tous les secteurs. Le problĂšme est que les blockchains dĂ©centralisĂ©es modernes manquent d'Ă©volutivitĂ©. Ethereum traite environ 20 transactions par seconde, ce qui est insuffisant pour rĂ©pondre aux besoins du business dynamique moderne. Dans le mĂȘme temps, les entreprises utilisant la technologie blockchain n'osent pas abandonner Ethereum en raison de sa haute rĂ©sistance au piratage et aux pannes de rĂ©seau.

Pour assurer la dĂ©centralisation, la sĂ©curitĂ© et la scalabilitĂ© dans la blockchain, rĂ©pondant ainsi Ă  la Trillemme de la ScalabilitĂ©, l'Ă©quipe de dĂ©veloppeurs Opporty a créé Plasma Cash — une chaĂźne latĂ©rale, composĂ©e d'un contrat intelligent et d'un rĂ©seau privĂ© basĂ© sur Node.js, transmettant pĂ©riodiquement son Ă©tat Ă  la chaĂźne principale (Ethereum).

Test public : une solution pour la confidentialité et la scalabilité sur Ethereum

Les processus clés dans Plasma Cash

1. L'utilisateur appelle la fonction du contrat intelligent `deposit`, lui transmettant le montant en ETH qu'il souhaite déposer dans le token Plasma Cash. La fonction du contrat intelligent crée le token et génÚre un événement à ce sujet.

2. Les nƓuds Plasma Cash, abonnĂ©s aux Ă©vĂ©nements du contrat intelligent, reçoivent l'Ă©vĂ©nement de crĂ©ation du dĂ©pĂŽt et ajoutent Ă  la pool la transaction de crĂ©ation du token.

3. PĂ©riodiquement, des nƓuds spĂ©ciaux Plasma Cash prennent toutes les transactions du pool (jusqu'Ă  1 million) et en forment un bloc, calculent l'arbre de Merkle et, par consĂ©quent, le hash. Ce bloc est envoyĂ© Ă  d'autres nƓuds pour vĂ©rification. Les nƓuds vĂ©rifient si le hash Merkle est valide, si les transactions sont valides (par exemple, si l'expĂ©diteur du jeton est bien le propriĂ©taire). AprĂšs la vĂ©rification du bloc, le nƓud appelle la fonction `submitBlock` du smart contract, qui enregistre dans la chaĂźne principale le numĂ©ro et le hash Merkle du bloc. Le smart contract gĂ©nĂšre un Ă©vĂ©nement sur l'ajout rĂ©ussi d'un bloc. Les transactions sont supprimĂ©es du pool.

4. Les nƓuds ayant reçu l'Ă©vĂ©nement de soumission de bloc commencent Ă  appliquer les transactions qui ont Ă©tĂ© ajoutĂ©es au bloc.

5. À un moment donnĂ©, le propriĂ©taire (ou non-propriĂ©taire) du jeton souhaite le retirer de Plasma Cash. Pour cela, il appelle la fonction `startExit`, en fournissant les informations sur les deux derniĂšres transactions concernant le jeton, qui prouvent qu'il est le propriĂ©taire du jeton. Le smart contract, utilisant le hash Merkle, vĂ©rifie la prĂ©sence des transactions dans les blocs et envoie le jeton pour retrait, qui aura lieu dans deux semaines.

6. Si l'opération de retrait du jeton a été effectuée de maniÚre irréguliÚre (le jeton a été dépensé aprÚs le début de la procédure de retrait ou le jeton avait déjà été pris par un tiers avant le retrait), le propriétaire du jeton peut contester le retrait dans un délai de deux semaines.

Test public : une solution pour la confidentialité et la scalabilité sur Ethereum

La confidentialité est atteinte de deux maniÚres.

1. La chaßne principale ne sait rien des transactions qui se forment et se transmettent à l'intérieur de la chaßne secondaire. L'information concernant qui a déposé et retiré de l'ETH dans/sout Plasma Cash reste publique.

2. La chaĂźne secondaire permet d'organiser des transactions anonymes en utilisant des zk-SNARKs.

Environnement technologique

  • NodeJS
  • Redis
  • Etherium
  • Soild

Test

En développant Plasma Cash, nous avons testé la vitesse du systÚme et obtenu les résultats suivants :

  • jusqu'Ă  35 000 transactions par seconde ajoutĂ©es au pool ;
  • jusqu'Ă  1 000 000 transactions peuvent ĂȘtre stockĂ©es dans un bloc.

Les tests ont été réalisés sur 3 serveurs suivants :

1. Intel Core i7-6700 Quad-Core Skylake avec NVMe SSD — 512 Go, 64 Go DDR4 RAM
3 nƓuds valides Plasma Cash ont Ă©tĂ© mis en place.

2. AMD Ryzen 7 1700X Octa-Core « Summit Ridge » (Zen), SATA SSD — 500 Go, 64 Go DDR4 RAM
Un nƓud ETH de testnet Ropsten a Ă©tĂ© mis en place.
3 nƓuds valides Plasma Cash ont Ă©tĂ© mis en place.

3. Intel Core i9-9900K Octa-Core avec NVMe SSD — 1 To, 64 Go DDR4 RAM
Un nƓud de soumission Plasma Cash a Ă©tĂ© mis en place.
3 nƓuds valides Plasma Cash ont Ă©tĂ© mis en place.
Un test a été lancé pour ajouter des transactions dans le réseau Plasma Cash.

Total : 10 nƓuds Plasma Cash dans un rĂ©seau privĂ©.

Test 1

La limite est de 1 million de transactions par bloc. Ainsi, 1 million de transactions se retrouvent dans 2 blocs (puisque le systÚme parvient à traiter une partie des transactions et à les soumettre pendant qu'elles sont envoyées).

Lire la vidéo

État initial : dernier bloc #7 ; 1 million de transactions et de jetons sont enregistrĂ©s dans la base.

00:00 — lancement du script de gĂ©nĂ©ration de transactions
01:37 — 1 million de transactions créées et l'envoi vers le nƓud a commencĂ©
01:46 — le nƓud de soumission a pris 240k transactions du pool et commence Ă  former le bloc #8. Nous voyons Ă©galement que 320k transactions sont ajoutĂ©es au pool en 10 secondes
01:58 — le bloc #8 a Ă©tĂ© signĂ© et envoyĂ© pour validation
02:03 — le bloc #8 a Ă©tĂ© validĂ© et la fonction `submitBlock` du contrat intelligent avec le hachage Merkle et le numĂ©ro du bloc a Ă©tĂ© appelĂ©e
02:10 — le script de dĂ©monstration a terminĂ© son travail, ayant envoyĂ© 1 million de transactions en 32 secondes
02:33 — les nƓuds ont commencĂ© Ă  recevoir des informations indiquant que le bloc #8 a Ă©tĂ© ajoutĂ© Ă  la chaĂźne principale et ont commencĂ© Ă  traiter 240k transactions
02:40 — 240k transactions ont Ă©tĂ© retirĂ©es du pool, qui sont dĂ©jĂ  dans le bloc #8
02:56 — le nƓud de soumission a pris les 760k transactions restantes du pool et a commencĂ© Ă  calculer le hachage Merkle et Ă  signer le bloc #9
03:20 — tous les nƓuds contiennent 1 million 240k transactions et jetons
03:35 — le bloc #9 a Ă©tĂ© signĂ© et est envoyĂ© pour validation Ă  d'autres nƓuds
03:41 — erreur rĂ©seau survenue
04:40 — l'attente de validation du bloc #9 a Ă©tĂ© interrompue par un timeout
04:54 — le nƓud de soumission a pris les 760k transactions restantes du pool et a commencĂ© Ă  calculer le hachage Merkle et Ă  signer le bloc #9
05:32 — le bloc #9 a Ă©tĂ© signĂ© et envoyĂ© pour validation Ă  d'autres nƓuds
05:53 — le bloc #9 a Ă©tĂ© validĂ© et envoyĂ© dans la chaĂźne principale
06:17 — les nƓuds ont commencĂ© Ă  recevoir des informations indiquant que le bloc #9 a Ă©tĂ© ajoutĂ© Ă  la chaĂźne principale et ont commencĂ© Ă  traiter 760k transactions
06:47 — le pool a Ă©tĂ© nettoyĂ© des transactions contenues dans le bloc #9
09:06 — tous les nƓuds contiennent 2 millions de transactions et jetons

Test 2

La limite est de 350k par bloc. Nous avons donc 3 blocs.

Lire la vidéo

État initial : dernier bloc #9 ; 2 millions de transactions et jetons sont enregistrĂ©s dans la base

00:00 — le script de gĂ©nĂ©ration de transactions est dĂ©jĂ  en cours d'exĂ©cution
00:44 — 1 million de transactions créées et l'envoi vers le nƓud a commencĂ©
00:56 — le nƓud de soumission a pris 320k transactions du pool et commence Ă  former le bloc #10. Nous voyons Ă©galement que 320k transactions sont ajoutĂ©es au pool en 10 secondes
01:12 — le bloc #10 a Ă©tĂ© signĂ© et envoyĂ© Ă  d'autres nƓuds pour validation
01:18 — le script de dĂ©monstration a terminĂ© son travail, ayant envoyĂ© 1 million de transactions en 34 secondes
01:20 — le bloc #10 a Ă©tĂ© validĂ© et envoyĂ© dans la chaĂźne racine
01:51 — tous les nƓuds ont reçu de la chaĂźne racine l'information que le bloc #10 a Ă©tĂ© ajoutĂ©, et commencent Ă  appliquer 320k transactions
02:01 — le pool s'est vidĂ© de 320k transactions qui ont Ă©tĂ© ajoutĂ©es au bloc #10
02:15 — le nƓud de soumission a pris 350k transactions du pool et forme le bloc #11
02:34 — le bloc #11 est signĂ© et envoyĂ© aux autres nƓuds pour validation
02:51 — le bloc #11 a Ă©tĂ© validĂ© et envoyĂ© dans la chaĂźne racine
02:55 — le dernier nƓud a exĂ©cutĂ© les transactions du bloc #10
10:59 — une transaction avec la soumission du bloc #9 a pris beaucoup de temps dans la chaĂźne racine, mais elle a Ă©tĂ© exĂ©cutĂ©e et tous les nƓuds ont reçu l'information et ont commencĂ© Ă  exĂ©cuter 350k transactions
11:05 — le pool s'est vidĂ© de 320k transactions qui ont Ă©tĂ© ajoutĂ©es au bloc #11
12:10 — tous les nƓuds contiennent 1 million 670k transactions et tokens
12:17 — le nƓud de soumission a pris 330k transactions du pool et forme le bloc #12
12:32 — le bloc #12 est signĂ© et envoyĂ© aux autres nƓuds pour validation
12:39 — le bloc #12 a Ă©tĂ© validĂ© et envoyĂ© dans la chaĂźne racine
13:44 — tous les nƓuds ont reçu de la chaĂźne racine l'information que le bloc #12 a Ă©tĂ© ajoutĂ© et commencent Ă  appliquer 330k transactions
14:50 — tous les nƓuds contiennent 2 millions de transactions et tokens

Test 3

Dans le premier et le deuxiĂšme serveurs, un nƓud validant a Ă©tĂ© remplacĂ© par un nƓud de soumission.

Lire la vidéo

État initial : dernier bloc #84 ; 0 transactions et tokens sont conservĂ©s dans la base

00:00 — 3 scripts ont Ă©tĂ© lancĂ©s, gĂ©nĂ©rant et envoyant chacun 1 million de transactions
01:38 — 1 million de transactions a Ă©tĂ© créé et l'envoi vers le nƓud de soumission #3 a commencĂ©
01:50 — le nƓud de soumission #3 a pris 330k transactions du pool et forme le bloc #85 (f21). Nous voyons aussi que 350k transactions sont ajoutĂ©es au pool en 10 secondes
01:53 — 1 million de transactions a Ă©tĂ© créé et l'envoi vers le nƓud de soumission #1 a commencĂ©
01:50 — le nƓud de soumission #3 a pris 330k transactions du pool et forme le bloc #85 (f21). Nous voyons aussi que 350k transactions sont ajoutĂ©es au pool en 10 secondes
02:01 — le nƓud de soumission #1 a pris 250k transactions du pool et forme le bloc #85 (65e)
02:06 — le bloc #85 (f21) est signĂ© et envoyĂ© aux autres nƓuds pour validation
02:08 — le script de dĂ©monstration du serveur #3 a terminĂ© son travail, ayant envoyĂ© 1 million de transactions en 30 secondes
02:14 — le bloc #85 (f21) a Ă©tĂ© validĂ© et envoyĂ© dans la chaĂźne racine
02:19 — le bloc #85 (65e) est signĂ© et envoyĂ© aux autres nƓuds pour validation
02:22 — 1 million de transactions a Ă©tĂ© créé et l'envoi vers le nƓud de soumission #2 a commencĂ©
02:27 — le bloc #85 (65e) a Ă©tĂ© validĂ© et envoyĂ© dans la chaĂźne racine
02:29 — le nƓud de soumission #2 a pris 111855 transactions du pool et forme le bloc #85 (256).
02:36 — le bloc #85 (256) est signĂ© et envoyĂ© Ă  d'autres nƓuds pour validation.
02:36 — le script de dĂ©mon du serveur #1 a terminĂ© son travail, ayant envoyĂ© 1 million de transactions en 42,5 secondes.
02:38 — le bloc #85 (256) a Ă©tĂ© validĂ© et envoyĂ© Ă  la chaĂźne racine.
03:08 — le script de dĂ©mon du serveur #2 a terminĂ© son travail, ayant envoyĂ© 1 million de transactions en 47 secondes.
03:38 — tous les nƓuds ont reçu de la chaĂźne racine l'information que les blocs #85 (f21), #86(65e), #87(256) ont Ă©tĂ© ajoutĂ©s et commencent Ă  appliquer 330k, 250k, 111855 transactions.
03:49 — le pool a Ă©tĂ© vidĂ© de 330k, 250k, 111855 transactions, qui ont Ă©tĂ© ajoutĂ©es aux blocs #85 (f21), #86(65e), #87(256).
03:59 — le nƓud de soumission #1 a pris 888145 transactions du pool et forme le bloc #88 (214), le nƓud de soumission #2 a pris 750k transactions du pool et forme le bloc #88 (50a), le nƓud de soumission #3 a pris 670k transactions du pool et forme le bloc #88 (d3b).
04:44 — le bloc #88 (d3b) est signĂ© et envoyĂ© Ă  d'autres nƓuds pour validation.
04:58 — le bloc #88 (214) est signĂ© et envoyĂ© Ă  d'autres nƓuds pour validation.
05:11 — le bloc #88 (50a) est signĂ© et envoyĂ© Ă  d'autres nƓuds pour validation.
05:11 — le bloc #85 (d3b) a Ă©tĂ© validĂ© et envoyĂ© Ă  la chaĂźne racine.
05:36 — le bloc #85 (214) a Ă©tĂ© validĂ© et envoyĂ© Ă  la chaĂźne racine.
05:43 — tous les nƓuds ont reçu de la chaĂźne racine l'information que les blocs #88 (d3b), #89(214) ont Ă©tĂ© ajoutĂ©s et commencent Ă  appliquer 670k, 750k transactions.
06:50 — en raison de la perte de connexion, le bloc #85 (50a) n'a pas Ă©tĂ© validĂ©.
06:55 — le nƓud de soumission #2 a pris 888145 transactions du pool et forme le bloc #90 (50a).
08:14 — le bloc #90 (50a) est signĂ© et envoyĂ© Ă  d'autres nƓuds pour validation.
09:04 — le bloc #90 (50a) a Ă©tĂ© validĂ© et envoyĂ© Ă  la chaĂźne racine.
11:23 — tous les nƓuds du serveur #3 ont reçu de la chaĂźne racine l'information que le bloc #90 (50a) a Ă©tĂ© ajoutĂ©, et commencent Ă  appliquer 888145 transactions. Pendant ce temps, le serveur #3 a dĂ©jĂ  appliquĂ© les transactions des blocs #88 (d3b), #89(214).
12:11 — tous les pools sont vides.
13:41 — tous les nƓuds du serveur #3 contiennent 3 millions de transactions et de jetons.
14:35 — tous les nƓuds du serveur #1 contiennent 3 millions de transactions et de jetons.
19:24 — tous les nƓuds du serveur #2 contiennent 3 millions de transactions et de jetons.

Obstacles

Lors du développement de Plasma Cash, nous avons rencontré les problÚmes suivants, que nous avons résolus et continuons de résoudre :

1. Conflit d'interaction entre différentes fonctions du systÚme. Par exemple, la fonctionnalité d'ajout de transactions dans le pool bloquait le fonctionnement du soumission et de la validation des blocs, et vice versa, ce qui entraßnait une baisse de la vitesse.

2. Il n'était pas clair comment envoyer un grand nombre de transactions tout en minimisant les coûts de transmission des données.

3. On ne savait pas comment et oĂč stocker les donnĂ©es pour obtenir de bons rĂ©sultats.

4. Il n'Ă©tait pas clair comment organiser le rĂ©seau entre les nƓuds, car la taille d'un bloc avec 1 million de transactions occupe environ 100 Mo.

5. Le fonctionnement en mode monothread interrompt la connexion entre les nƓuds lorsque des calculs longs se produisent (par exemple, la construction de l'arbre de Merkle et le calcul de son hachage).

Comment avons-nous géré tout cela ?

La premiĂšre version du nƓud Plasma Cash Ă©tait une sorte de combine qui pouvait tout faire en mĂȘme temps : accepter des transactions, soumettre et valider des blocs, fournir une API pour accĂ©der aux donnĂ©es. Étant donnĂ© que NodeJS est initialement monothread, la lourde fonction de calcul de l'arbre de Merkle bloquait la fonction d'ajout de transactions. Nous avons vu deux options pour rĂ©soudre ce problĂšme :

1. Lancer plusieurs processus NodeJS, chacun exécutant certaines fonctions.

2. Utiliser worker_threads et déplacer l'exécution d'une partie du code dans des threads.

En fin de compte, nous avons utilisĂ© les deux options en mĂȘme temps : nous avons logiquement sĂ©parĂ© un nƓud en 3 parties, qui peuvent fonctionner sĂ©parĂ©ment, mais en mĂȘme temps de maniĂšre synchronisĂ©e.

1. NƓud de soumission, qui accepte les transactions dans le pool et s'occupe de la crĂ©ation de blocs.

2. NƓud de validation, qui vĂ©rifie la validitĂ© des nƓuds.

3. NƓud API — fournit une API pour accĂ©der aux donnĂ©es.

Chaque nƓud peut ĂȘtre connectĂ© via un socket unix par le biais de cli.

Les opérations lourdes, telles que le calcul de l'arbre de Merkle, ont été transférées dans un thread séparé.

Ainsi, nous avons rĂ©ussi Ă  faire fonctionner toutes les fonctions de Plasma Cash en mĂȘme temps et sans pannes.

Une fois que le systĂšme a fonctionnĂ© fonctionnellement, nous avons commencĂ© Ă  tester la vitesse et, malheureusement, nous avons obtenu des rĂ©sultats insatisfaisants : 5 000 transactions par seconde et jusqu'Ă  50 000 transactions dans un bloc. Il a fallu comprendre ce qui avait Ă©tĂ© mal mis en Ɠuvre.

Pour commencer, nous avons commencĂ© Ă  tester le mĂ©canisme de communication avec Plasma Cash pour connaĂźtre la capacitĂ© maximale du systĂšme. Auparavant, nous avons Ă©crit que le nƓud Plasma Cash fournit une interface de socket Unix. Initialement, il Ă©tait textuel. Les objets JSON Ă©taient transfĂ©rĂ©s en utilisant `JSON.parse()` et `JSON.stringify()`.

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

Nous avons mesurĂ© la vitesse de transfert de tels objets et avons obtenu environ 130 000 par seconde. Nous avons essayĂ© de remplacer les fonctions standard travaillant avec JSON, mais la performance ne s'est pas amĂ©liorĂ©e. Le moteur V8 doit ĂȘtre bien optimisĂ© pour ces opĂ©rations.

La gestion des transactions, des jetons et des blocs se faisait via des classes. Lors de la création de telles classes, la performance a chuté de moitié, ce qui indique que la POO ne nous convient pas. Nous avons dû tout réécrire en utilisant une approche fonctionnelle.

Écriture dans la base de donnĂ©es

Au départ, Redis a été choisi pour le stockage des données comme l'une des solutions les plus performantes répondant à nos exigences : stockage clé-valeur, travail avec des tables de hachage, ensembles. Nous avons lancé redis-benchmark et obtenu environ 80 000 opérations par seconde en mode de 1 pipelining.

Pour une performance élevée, nous avons configuré Redis de maniÚre plus fine :

  • Nous avons Ă©tabli une connexion de socket Unix.
  • Nous avons dĂ©sactivĂ© la sauvegarde d'Ă©tat sur disque (pour la fiabilitĂ©, il est possible de configurer une rĂ©plique et de faire des sauvegardes sur disque dans un autre Redis).

Dans Redis, un pool est une table de hachage, car nous avons besoin de la possibilitĂ© d'obtenir toutes les transactions en une seule requĂȘte et de supprimer les transactions une par une. Nous avons essayĂ© d'utiliser une liste ordinaire, mais elle fonctionne plus lentement lors du dĂ©chargement de la liste entiĂšre.

Lors de l'utilisation de la bibliothÚque Redis standard de NodeJS, nous avons obtenu une performance de 18 000 transactions par seconde. La vitesse a chuté de 9 fois.

Étant donnĂ© que le benchmark nous a montrĂ© des capacitĂ©s clairement cinq fois supĂ©rieures, nous avons commencĂ© Ă  optimiser. Nous avons changĂ© la bibliothĂšque pour ioredis et avons obtenu des performances de 25k par seconde. Nous ajoutions des transactions individuellement, en utilisant la commande `hset`. Ainsi, nous gĂ©nĂ©rions de nombreuses requĂȘtes vers Redis. L'idĂ©e est nĂ©e de regrouper les transactions en lots et de les envoyer avec une seule commande `hmset`. Le rĂ©sultat — 32k par seconde.

Pour plusieurs raisons, que nous allons dĂ©crire ci-dessous, nous travaillons avec les donnĂ©es en utilisant `Buffer` et, comme il s'est avĂ©rĂ©, si cela est converti en texte (`buffer.toString(‘hex’)`) avant l'Ă©criture, nous pouvons obtenir un gain de performance supplĂ©mentaire. Ainsi, la vitesse a pu ĂȘtre augmentĂ©e Ă  35k par seconde. À ce stade, nous avons dĂ©cidĂ© de suspendre l'optimisation supplĂ©mentaire.

Nous avons dĂ» passer Ă  un protocole binaire car :

1. Le systÚme calcule souvent des hachages, des signatures, etc., et pour cela, il a besoin de données sous forme de `Buffer`.

2. Lors de la transmission entre services, les données binaires pÚsent moins que les données textuelles. Par exemple, lors de l'envoi d'un bloc de 1 million de transactions, les données en texte peuvent occuper plus de 300 mégaoctets.

3. La conversion constante des données affecte la performance.

C'est pourquoi nous avons basé notre propre protocole binaire de stockage et de transmission de données, développé sur la base de la bibliothÚque remarquable `binary-data`.

En conséquence, nous avons obtenu les structures de données suivantes :

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

— Bloc

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

Avec les commandes habituelles `BD.encode(block, Protocol).slice();` et ` BD.decode(buffer, Protocol)`, nous convertissons les donnĂ©es en `Buffer` pour les stocker dans Redis ou les transmettre Ă  un autre nƓud et rĂ©cupĂ©rer les donnĂ©es.

Nous avons également 2 protocoles binaires pour la transmission de données entre services :

— Protocole pour l'interaction avec le Plasma Node via un socket 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)
  }
  ```

oĂč :

  • `type` — une action Ă  exĂ©cuter, par exemple 1 — sendTransaction, 2 — getTransaction ;
  • `payload` — les donnĂ©es Ă  transmettre Ă  la fonction correspondante ;
  • `messageId` — l'identifiant du message, permettant d'identifier la rĂ©ponse.

— Le protocole d'interaction entre les nƓuds

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

oĂč :

  • `code` — le code du message, par exemple 6 — PREPARE_NEW_BLOCK, 7 — BLOCK_VALID, 8 — BLOCK_COMMIT ;
  • `versionProtocol` — la version du protocole, car il peut y avoir des nƓuds dans le rĂ©seau avec diffĂ©rentes versions qui peuvent fonctionner diffĂ©remment ;
  • `seq` — identifiant du message ;
  • `countChunk` et `chunkNumber` nĂ©cessaires pour diviser les gros messages ;
  • `length` et `payload` la longueur et les donnĂ©es elles-mĂȘmes.

Étant donnĂ© que nous avons prĂ©alablement typĂ© les donnĂ©es, le systĂšme final fonctionne beaucoup plus rapidement que la bibliothĂšque `rlp` d'Ethereum. Malheureusement, nous n'avons pas encore pu nous en passer, car il est nĂ©cessaire de retravailler le contrat intelligent, ce que nous prĂ©voyons de faire Ă  l'avenir.

Si nous avons réussi à atteindre la vitesse 35 000 des transactions par seconde, nous devons également les traiter dans un délai optimal. Comme le temps moyen de formation d'un bloc est d'environ 30 secondes, nous devons inclure dans le bloc 1 000 000 des transactions, ce qui signifie transférer plus de 100 Mo de données.

Au dĂ©part, nous avons utilisĂ© la bibliothĂšque `ethereumjs-devp2p` pour la communication entre les nƓuds, mais elle ne gĂ©rait pas une telle quantitĂ© de donnĂ©es. En consĂ©quence, nous avons utilisĂ© la bibliothĂšque `ws` et configurĂ© le transfert de donnĂ©es binaires via websocket. Bien sĂ»r, nous avons Ă©galement rencontrĂ© des problĂšmes lors du transfert de gros paquets de donnĂ©es, mais nous les avons divisĂ©s en chunks et ces problĂšmes n'existent plus maintenant.

La formation de l'arbre de Merkle et le calcul du hachage 1 000 000 des transactions prend environ 10 secondes de calcul continu. Pendant ce temps, la connexion avec tous les nƓuds peut ĂȘtre interrompue. Il a Ă©tĂ© dĂ©cidĂ© de dĂ©placer ce calcul dans un thread sĂ©parĂ©.

Conclusions :

En réalité, nos conclusions ne sont pas nouvelles, mais pour une raison quelconque, de nombreux spécialistes les oublient lors du développement.

  • L'utilisation de la programmation fonctionnelle plutĂŽt que de la programmation orientĂ©e objet augmente les performances.
  • Le monolithe est moins performant qu'une architecture de services pour un systĂšme performant sur NodeJS.
  • L'utilisation de `worker_threads` pour les calculs lourds amĂ©liore la rĂ©activitĂ© du systĂšme, en particulier lors des opĂ©rations i/o.
  • Les sockets UNIX sont plus stables et plus rapides que les requĂȘtes HTTP.
  • Si vous devez transmettre rapidement de grandes quantitĂ©s de donnĂ©es sur le rĂ©seau, il est prĂ©fĂ©rable d'utiliser des websockets et d'envoyer des donnĂ©es binaires divisĂ©es en morceaux, qui peuvent ĂȘtre retransmises si elles ne rĂ©ussissent pas Ă  arriver, puis regroupĂ©es en un seul message.

Nous vous invitons Ă  visiter GitHub le projet : https://github.com/opporty-com/Plasma-Cash/tree/new-version

Cet article a été coécrit avec Alexandre Nashivan, développeur senior Clever Solution Inc.

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster