Test public: solution pour la confidentialité et l'évolutivité dans Ethereum

La blockchain est une technologie innovante qui promet d'améliorer de nombreux domaines de la vie humaine. Il transfère les processus et produits réels vers l'espace numérique, garantit la rapidité et la fiabilité des transactions financières, réduit leur coût et vous permet également de créer des applications DAPP modernes à l'aide de contrats intelligents dans des réseaux décentralisés.

Compte tenu des nombreux avantages et des diverses utilisations de la blockchain, il peut sembler étrange que cette technologie prometteuse n'ait pas encore pénétré tous les secteurs. Le problème est que les chaînes de blocs décentralisées modernes manquent d'évolutivité. Ethereum traite environ 20 transactions par seconde, ce qui n'est pas suffisant pour répondre aux besoins des entreprises dynamiques d'aujourd'hui. Dans le même temps, les entreprises utilisant la technologie blockchain n'osent pas abandonner Ethereum en raison de son haut degré de protection contre le piratage et les pannes de réseau.

Pour assurer la décentralisation, la sécurité et l'évolutivité sur la blockchain, résolvant ainsi le trilemme d'évolutivité, l' équipe de développement d'Opporty a créé Plasma Cash - une chaîne enfant composée d'un contrat intelligent et d'un réseau privé basé sur Node.js, transférant périodiquement son état à la chaîne racine ( Ethereum).



Processus clés chez Plasma Cash


1. L' utilisateur appelle la fonction du contrat intelligent «dépôt», en y transférant le montant en ETH, qu'il souhaite mettre dans le jeton Plasma Cash. La fonction de contrat intelligent crée un jeton 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 un événement sur la création d'un dépôt et ajoutent une transaction sur la création d'un jeton au pool.

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 Merkle et, en conséquence, le hachage. Ce bloc est envoyé à d'autres nœuds pour vérification. Les nœuds vérifient si le hachage Merkle est valide, si les transactions sont valides (par exemple, si l'expéditeur du jeton est son propriétaire). Après avoir vérifié le bloc, le nœud appelle la fonction «submitBlock» du contrat intelligent, qui stocke le numéro et le hachage Merkle du bloc dans la chaîne de trace. Un contrat intelligent génère un événement concernant l'ajout réussi d'un bloc. Les transactions sont supprimées du pool.

4. Les nœuds qui ont reçu l'événement concernant la soumission du 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 ce faire, il appelle la fonction `startExit`, lui transmettant des informations sur les 2 dernières transactions sur le jeton, ce qui confirme qu'il est le propriétaire du jeton. Le contrat intelligent, à l'aide du hachage Merkle, vérifie les transactions en blocs et envoie un jeton à la sortie, ce qui se produira dans deux semaines.

6. Si l'opération de retrait du jeton s'est produite avec des violations (le jeton a été dépensé après le début de la procédure de retrait ou le jeton était déjà un étranger avant le retrait), le propriétaire du jeton peut réfuter le retrait dans un délai de deux semaines.



La confidentialité est obtenue de deux manières.


1. La chaîne racine ne sait rien des transactions qui sont formées et transmises à l'intérieur de la chaîne enfant. Des informations restent disponibles sur qui a commencé et retiré l'ETH vers / de Plasma Cash.

2. La chaîne enfant vous permet d'organiser des transactions anonymes à l'aide de zk-SNARK.

Pile technologique


  • NodeJS
  • Redis
  • Ethereum
  • Soild

Test


Lors du développement de Plasma Cash, nous avons testé la vitesse du système et obtenu les résultats suivants:

  • jusqu'à 35 000 transactions par seconde sont ajoutées au pool;
  • jusqu'à 1 000 000 de transactions peuvent être stockées dans le bloc.

Des tests ont été effectués sur les 3 serveurs suivants:

1. Intel Core i7-6700 Skylake quadricœur incl. SSD NVMe - 512 Go, 64 Go de RAM DDR4
3 nœuds Plasma Cash validés ont été relevés.

2. AMD Ryzen 7 1700X Octa-Core «Summit Ridge» (Zen), SSD SATA - 500 Go, 64 Go de RAM DDR4
Le nœud Ropsten testnet ETH a été soulevé.
3 nœuds Plasma Cash validés ont été relevés.

3. Intel Core i9-9900K Octa-Core incl. SSD NVMe - 1 To, 64 Go de RAM DDR4
1 Le nœud Submit Plasma Cash a été soulevé.
3 nœuds Plasma Cash validés ont été relevés.
Un test a été lancé pour ajouter des transactions au réseau Plasma Cash.

Total: 10 nœuds Plasma Cash dans un réseau privé.

Test 1


Il y a une limite de 1 million de transactions par bloc. Par conséquent, 1 million de transactions se répartissent en 2 blocs (puisque le système parvient à prendre une partie des transactions et à les soumettre pendant leur envoi).


État initial: dernier bloc # 7; 1 million de transactions et jetons sont stockés dans la base de données.

00:00 - démarrer le script de génération de transaction
01:37 - 1 million de transactions ont été créées et l'envoi au nœud a commencé
01:46 - le nœud d'envoi a pris 240k transactions du pool et forme le bloc # 8. Nous constatons également que 320 000 transactions sont ajoutées au pool en 10 secondes
01:58 - le bloc # 8 est signé et envoyé pour validation
02:03 - le bloc # 8 est validé et la fonction `submitBlock` du contrat intelligent avec le hachage Merkle et le numéro de bloc est appelée
02:10 - Le script de démonstration a fini de fonctionner, ce qui a envoyé 1 million de transactions en 32 secondes
02:33 - les nœuds ont commencé à recevoir des informations selon lesquelles le bloc # 8 a été ajouté à la chaîne racine, et ont commencé à effectuer 240k transactions
02:40 - 240k transactions ont été supprimées du pool, qui sont déjà dans le bloc # 8
02:56 - le nœud de soumission a pris les 760 000 transactions restantes du pool et a commencé à calculer le hachage Merkle et à signer le bloc # 9
03:20 - tous les nœuds contiennent 1 mln 240 000 transactions et jetons
03:35 - le bloc # 9 est signé et envoyé pour validation aux autres nœuds
03:41 - une erreur réseau s'est produite
04:40 - par timeout, l'attente de la validation du bloc # 9 s'est arrêtée
04:54 - le nœud de soumission a pris les 760 000 transactions restantes du pool et a commencé à calculer le hachage Merkle et à signer le bloc # 9
05:32 - le bloc # 9 est signé et envoyé pour validation aux autres nœuds
05:53 - le bloc # 9 est validé et envoyé à la chaîne racine
06:17 - les nœuds ont commencé à recevoir des informations selon lesquelles le bloc # 9 a été ajouté à la chaîne racine et ont commencé à effectuer 760 k transactions
06:47 - le pool est effacé des transactions qui sont dans le bloc # 9
09:06 - tous les nœuds contiennent 2 millions de transactions et de jetons

Test 2


Il y a une limite de 350k par bloc. En conséquence, nous avons 3 blocs.


État initial: dernier bloc # 9; 2 millions de transactions et jetons stockés dans la base de données

00:00 - le script de génération de transaction est déjà en cours d'exécution
00:44 - 1 million de transactions ont été créées et l'envoi au nœud a commencé
00:56 - le nœud d'envoi a pris 320k transactions du pool et forme le bloc # 10. Nous constatons également que 320 000 transactions sont ajoutées au pool en 10 secondes
01:12 - le bloc # 10 est signé et envoyé à d'autres nœuds pour validation
01:18 - Le script de démonstration a fini de fonctionner, ce qui a envoyé 1 million de transactions en 34 secondes
01:20 - le bloc # 10 est validé et envoyé à la chaîne racine
01:51 - tous les nœuds ont reçu des informations de la chaîne racine que le bloc # 10 a été ajouté, et ils commencent à appliquer 320k transactions
02:01 - le pool a été effacé pour 320k transactions ajoutées au bloc # 10
02:15 - soumettre le noeud a pris 350k transactions du pool et forme le bloc # 11
02:34 - le bloc # 11 est signé et envoyé à d'autres nœuds pour validation
02:51 - le bloc # 11 est validé et envoyé à la chaîne racine
02:55 - le dernier nœud a exécuté les transactions du bloc # 10
10h59 - très longtemps dans la chaîne racine, une transaction a été exécutée avec une soumission de bloc # 9, mais elle a été terminée et tous les nœuds ont reçu des informations à ce sujet et ont commencé à exécuter 350 000 transactions
11h05 - le pool a été effacé pour 320k transactions qui ont été ajoutées au bloc # 11
12:10 - tous les nœuds contiennent 1 million de transactions et jetons 670k
12:17 - soumettre le noeud a pris 330k transactions du pool et forme le bloc # 12
12:32 - le bloc # 12 est signé et envoyé à d'autres nœuds pour validation
12:39 - le bloc # 12 est validé et envoyé à la chaîne racine
13:44 - tous les nœuds ont reçu des informations de la chaîne racine que le bloc # 12 a été ajouté et commencent à appliquer 330 000 transactions
14:50 - tous les nœuds contiennent 2 millions de transactions et de jetons

Test 3


Sur les premier et deuxième serveurs, un nœud de validation a été remplacé par un nœud de soumission.


État initial: dernier bloc # 84; 0 transactions et jetons sont stockés dans la base de données

00:00 - 3 scripts sont lancés qui génèrent et envoient 1 million de transactions
01:38 - 1 million de transactions ont été créées et l'envoi pour soumettre le noeud # 3 a commencé
01:50 - soumettre le noeud # 3 a pris 330k transactions du pool et forme le bloc # 85 (f21). Nous constatons également que 350 000 transactions sont ajoutées au pool en 10 secondes
01:53 - 1 million de transactions ont été créées et l'envoi pour soumettre le noeud # 1 a commencé
01:50 - soumettre le noeud # 3 a pris 330k transactions du pool et forme le bloc # 85 (f21). Nous constatons également que 350 000 transactions sont ajoutées au pool en 10 secondes
02:01 - soumettre le noeud # 1 a pris 250k transactions du pool et forme le bloc # 85 (65e)
02:06 - le bloc # 85 (f21) est signé et envoyé à d'autres nœuds pour validation
02:08 - le script de démonstration du serveur # 3 a fini de fonctionner, ce qui a envoyé 1 mln de transactions en 30 secondes
02:14 - le bloc # 85 (f21) est validé et envoyé à la chaîne racine
02:19 - le bloc # 85 (65e) est signé et envoyé à d'autres nœuds pour validation
02:22 - 1 million de transactions ont été créées et l'envoi pour soumettre le noeud # 2 a commencé
02:27 - le bloc # 85 (65e) est validé et envoyé à la chaîne racine
02:29 - soumettre le noeud # 2 a pris du pool 111855 transactions 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émonstration du serveur # 1 a fini de fonctionner, qui a envoyé 1 mln de transactions en 42,5 secondes
02:38 - le bloc # 85 (256) est validé et envoyé à la chaîne racine
03h08 - le script serveur # 2, qui a envoyé 1 million de transactions en 47 secondes, a fini de fonctionner
03:38 - tous les nœuds ont reçu des informations de la chaîne racine selon lesquelles les blocs # 85 (f21), # 86 (65e), # 87 (256) ont été ajoutés et commencent à appliquer des transactions 330k, 250k, 111855
03:49 - le pool a été effacé à 330k, 250k, 111855 transactions qui ont été ajoutées aux blocs # 85 (f21), # 86 (65e), # 87 (256)
03:59 - soumettre le nœud n ° 1 a pris du pool 888145 transactions et formulaires bloc # 88 (214), soumettre le nœud n ° 2 a pris du pool 750k transactions et formulaires bloc # 88 (50a), soumettre le nœud n ° 3 a pris du pool 670k transactions 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) est validé et envoyé à la chaîne racine
05:36 - le bloc # 85 (214) est validé et envoyé à la chaîne racine
05:43 - tous les nœuds ont reçu des informations de la chaîne racine qui bloquent # 88 (d3b), # 89 (214) ont été ajoutés et commencent à appliquer 670k, 750k transactions
06:50 - en raison d'une déconnexion, le bloc # 85 (50a) n'a pas été validé
06:55 - soumettre le noeud # 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) est validé et envoyé à la chaîne racine
11:23 - tous les nœuds ont reçu des informations de la chaîne racine que le bloc # 90 (50a) a été ajouté, et 888145 transactions ont commencé à être appliquées. Dans le même temps, le serveur n ° 3 applique depuis longtemps les transactions des blocs n ° 88 (d3b), n ° 89 (214)
12:11 - toutes les piscines sont vides
13:41 - tous les nœuds de serveur # 3 contiennent 3 millions de transactions et de jetons
14:35 - tous les nœuds de serveur # 1 contiennent 3 millions de transactions et de jetons
19:24 - tous les nœuds de serveur # 2 contiennent 3 millions de transactions et de jetons

Obstacles


Au cours du développement de Plasma Cash, nous avons rencontré les problèmes suivants, que nous avons progressivement résolus et résolvons:

1. Le conflit d'interaction des différentes fonctions du système. Par exemple, la fonction d'ajout de transactions au pool a bloqué la soumission et la validation des blocs, et vice versa, ce qui a entraîné une baisse de la vitesse.

2. On ne savait pas immédiatement comment envoyer un grand nombre de transactions et en même temps minimiser le coût du transfert de données.

3. Il n'était pas clair comment et où stocker les données afin d'obtenir des résultats élevés.

4. La façon d'organiser un réseau entre les nœuds n'était pas claire, car la taille de bloc avec 1 million de transactions prend environ 100 Mo.

5. Le travail en mode monothread coupe la connexion entre les nœuds lorsque de longs calculs se produisent (par exemple, la construction d'un arbre 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 combinaison 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 était initialement monothread, la lourde fonction de calcul de l'arbre Merkle a bloqué la fonction d'ajout de transaction. Nous avons vu deux options pour résoudre ce problème:

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

2. Utilisez worker_threads et placez l'exécution du code dans des threads.

En conséquence, nous avons utilisé les deux options en même temps: divisé logiquement un nœud en 3 parties, qui peuvent fonctionner séparément, mais en même temps de manière synchrone

1. Soumettez un nœud qui accepte les transactions dans le pool et crée des blocs.

2. Noeud de validation qui vérifie la validité des noeuds.

3. Node API - fournit une API pour accéder aux données.

Dans le même temps, vous pouvez vous connecter à chaque nœud via un socket Unix à l'aide de cli.

Des opérations lourdes, comme le calcul de l'arbre de Merkle, nous avons effectué dans un flux séparé.

Ainsi, nous avons atteint le fonctionnement normal de toutes les fonctions Plasma Cash simultanément et sans échecs.

Dès que le système a fonctionné, 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. J'ai dû découvrir ce qui n'était pas mis en œuvre correctement.

Pour commencer, nous avons commencé à tester le mécanisme de communication avec Plasma Cash pour découvrir la capacité de pointe du système. Plus tôt, nous avons écrit que le nœud Plasma Cash fournit une interface de socket Unix. C'était à l'origine textuel. Les objets json ont été envoyé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 ces objets et reçu ~ 130k par seconde. Ils ont essayé de remplacer les fonctions standard par json, mais les performances ne se sont pas améliorées. Il doit y avoir un moteur V8 bien optimisé pour ces opérations.

Le travail avec les transactions, les jetons, les blocs a été effectué à travers les classes. Lors de la création de telles classes, les performances ont chuté de 2 fois, ce qui indique: OOP ne nous convient pas. J'ai dû tout réécrire sur une approche purement fonctionnelle.

Ecrire dans la base de données


Initialement, Redis a été choisi pour le stockage de données comme l'une des solutions les plus productives qui répondent à nos exigences: stockage de valeur-clé, travail avec des tables de hachage, et bien d'autres. Nous avons lancé redis-benchmark et obtenu ~ 80 000 opérations par seconde en 1 mode de pipeline.

Pour des performances élevées, nous avons affiné plus finement Redis:

  • Établi une connexion socket Unix.
  • Désactivez l'enregistrement de l'état sur le disque (pour plus de fiabilité, vous pouvez configurer la réplique et déjà enregistrer sur le disque dans un Redis distinct).

Dans Redis, un pool est une table de hachage, car nous devons pouvoir recevoir toutes les transactions en une seule demande et supprimer les transactions une par une. Nous avons essayé d'utiliser une liste régulière, mais cela fonctionne plus lentement lors du déchargement de la liste entière.

En utilisant la bibliothèque NodeJS standard, les bibliothèques Redis ont réalisé des performances de 18 000 transactions par seconde. La vitesse a chuté 9 fois.

Puisque le benchmark nous a clairement montré les possibilités 5 fois plus, ils ont commencé à optimiser. Nous avons changé la bibliothèque en ioredis et obtenu une performance de 25k par seconde. Nous avons ajouté les transactions une par une en utilisant la commande `hset`. Ainsi, nous avons généré de nombreuses requêtes dans Redis. Il y avait une idée de fusionner les transactions en bundles et de les envoyer avec une seule commande hmset. Le résultat est de 32k par seconde.

Pour plusieurs raisons, qui seront décrites ci-dessous, nous travaillons avec des données en utilisant `Buffer` et, comme il s'est avéré, si vous les traduisez en texte (` buffer.toString ('hex') ') avant d'écrire, vous pouvez obtenir des performances supplémentaires. Ainsi, la vitesse a été augmentée à 35 km par seconde. Pour le moment, nous avons décidé de suspendre toute optimisation supplémentaire.

Nous avons dû passer au protocole binaire car:

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

2. Lors du transfert entre services, les données binaires pèsent moins que le texte. Par exemple, lors de l'envoi d'un bloc avec 1 million de transactions, les données du texte peuvent occuper plus de 300 mégaoctets.

3. La conversion continue des données affecte les performances.

Par conséquent, nous avons pris comme base notre propre protocole binaire pour le stockage et la transmission de données, développé sur la base de la merveilleuse bibliothèque de données binaires.

En conséquence, nous avons 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, } ``` 

- Jeton


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

- Bloquer


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

Par les commandes habituelles `BD.encode (block, Protocol) .slice ();` et `BD.decode (buffer, Protocol)`, nous convertissons les données en `Buffer` pour les enregistrer dans Redis ou envoyer un autre nœud et récupérer les données.

Nous avons également 2 protocoles binaires pour le transfert de données entre services:

- Protocole pour interagir avec le nœud plasma via une prise 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` - action à effectuer, par exemple, 1 - sendTransaction, 2 - getTransaction;
  • `payload` - données à transférer vers la fonction correspondante;
  • `messageId` - id du message afin que la réponse puisse être identifiée.

- 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` - code de message, par exemple 6 - PREPARE_NEW_BLOCK, 7 - BLOCK_VALID, 8 - BLOCK_COMMIT;
  • `versionProtocol` - version du protocole, car les nœuds avec des versions différentes peuvent être élevés sur le réseau et ils peuvent fonctionner de différentes manières;
  • `seq` - identifiant du message;
  • `countChunk` et` chunkNumber` sont nécessaires pour diviser les gros messages;
  • `length` et` payload` la longueur et les données elles-mêmes.

Puisque nous avons tapé les données au préalable, le système final est beaucoup plus rapide que la bibliothèque `rlp` d'Ethereum. Malheureusement, nous n'avons pas encore pu le refuser, car il est nécessaire de finaliser le contrat intelligent, que nous prévoyons de faire à l'avenir.

Si nous avons réussi à atteindre une vitesse de 35 000 transactions par seconde, nous devons également les traiter en temps optimal. Étant donné que le temps de formation approximatif du bloc prend 30 secondes, nous devons inclure 1 000 000 de transactions dans le bloc, ce qui signifie envoyer plus de 100 Mo de données.

Initialement, nous avons utilisé la bibliothèque `ethereumjs-devp2p` pour communiquer les nœuds, mais elle ne pouvait pas gérer autant de données. En conséquence, nous avons utilisé la bibliothèque `ws` et mis en place le transfert de données binaires sur websocket. Bien sûr, nous avons également rencontré des problèmes lors de l'envoi de gros paquets de données, mais nous les avons divisés en morceaux et maintenant il n'y a plus de tels problèmes.

De plus, la formation de l'arbre Merkle et le calcul du hachage de 1 000 000 de transactions nécessitent environ 10 secondes de calcul continu. Pendant ce temps, la connexion avec tous les nœuds parvient à se rompre. Il a été décidé de transférer ce calcul sur un thread séparé.

Conclusions:


En fait, nos résultats ne sont pas nouveaux, mais pour une raison quelconque, de nombreux experts les oublient pendant le développement.

  • L'utilisation de la programmation fonctionnelle au lieu de la programmation orientée objet augmente les performances.
  • Un monolithe est pire qu'une architecture de service pour un système de production sur NodeJS.
  • L'utilisation de `worker_threads` pour le calcul intensif améliore la réactivité du système, en particulier lorsque vous travaillez avec des opérations d'E / S.
  • le socket unix est plus stable et plus rapide que les requêtes http.
  • Si vous devez transférer rapidement des données volumineuses 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 transmises si elles n'atteignent pas, puis fusionnées en un seul message.

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

L'article a été co-écrit par Alexander Nashivan , développeur principal de Clever Solution Inc.

Source: https://habr.com/ru/post/fr471096/


All Articles