Un merci spécial à Dankrad Feist et Aditya Asgaonkar pour la révision
Le sharding est l'avenir de l'évolutivité d'Ethereum, et il sera essentiel pour aider l'écosystème à prendre en charge plusieurs milliers de transactions par seconde et permettre à de grandes parties du monde d'utiliser régulièrement la plate-forme à un coût abordable. Cependant, c'est aussi l'un des concepts les plus mal compris de l'écosystème Ethereum et plus largement des écosystèmes blockchain. Il fait référence à un ensemble très spécifique d'idées avec des propriétés très spécifiques, mais il est souvent confondu avec des techniques qui ont des propriétés de sécurité très différentes et souvent beaucoup plus faibles. Le but de cet article sera d'expliquer exactement quelles propriétés spécifiques fournit le sharding, en quoi il diffère des autres technologies qui ne sont pas sharding et quels sacrifices un système shardé doit faire pour atteindre ces propriétés.
La meilleure façon de décrire le sharding commence par l'énoncé du problème qui a façonné et inspiré la solution : le trilemme de la scalabilité .
Le trilemme d'évolutivité dit qu'il y a trois propriétés qu'une blockchain essaie d'avoir, et que, si vous vous en tenez à des techniques "simples", vous ne pouvez obtenir que deux de ces trois . Les trois propriétés sont :
Évolutivité : la chaîne peut traiter plus de transactions qu'un seul nœud standard (pensez : un ordinateur portable grand public) ne peut en vérifier.
Décentralisation : la chaîne peut fonctionner sans aucune dépendance de confiance sur un petit groupe de grands acteurs centralisés. Cela est généralement interprété comme signifiant qu'il ne devrait y avoir aucune confiance (ou même hypothèse de majorité honnête) d'un ensemble de nœuds que vous ne pouvez pas joindre avec un ordinateur portable grand public.
Sécurité : la chaîne peut résister à un grand pourcentage de nœuds participants essayant de l'attaquer (idéalement 50 % ; tout ce qui dépasse 25 % est correct, 5 % ne l'est certainement pas ).
Nous pouvons maintenant examiner les trois classes de "solutions faciles" qui n'obtiennent que deux des trois :
Blockchains traditionnelles - y compris Bitcoin, pré-PoS/sharding Ethereum, Litecoin et autres chaînes similaires. Ceux-ci reposent sur chaque participant exécutant un nœud complet qui vérifie chaque transaction, et donc ils ont la décentralisation et la sécurité, mais pas l'évolutivité.
Chaînes à haut TPS - y compris la famille DPoS mais aussi bien d'autres. Celles-ci reposent sur un petit nombre de nœuds (souvent 10 à 100) maintenant un consensus entre eux, les utilisateurs devant faire confiance à la majorité de ces nœuds. Ceci est évolutif et sécurisé (en utilisant les définitions ci-dessus), mais il n'est pas décentralisé.
Écosystèmes multi-chaînes - cela fait référence au concept général de "mise à l'échelle" en faisant vivre différentes applications sur différentes chaînes et en utilisant des protocoles de communication inter-chaînes pour communiquer entre elles. Ceci est décentralisé et évolutif, mais il n'est pas sécurisé, car un attaquant n'a besoin d'obtenir qu'une majorité de nœuds de consensus dans l'une des nombreuses chaînes (souvent <1% de l'ensemble de l'écosystème) pour briser cette chaîne et éventuellement provoquer des effets d'entraînement qui provoquent de grands dommages aux applications dans d'autres chaînes.
Le sharding est une technique qui vous permet d'obtenir les trois. Une blockchain fragmentée est :
Le reste de l'article décrira comment les blockchains fragmentées parviennent à le faire.
La version la plus simple du partitionnement à comprendre est le partitionnement par échantillonnage aléatoire. Le sharding par échantillonnage aléatoire a des propriétés de confiance plus faibles que les formes de sharding vers lesquelles nous nous concentrons dans l'écosystème Ethereum, mais il utilise une technologie plus simple.
L'idée centrale est la suivante. Supposons que vous ayez une chaîne de preuve de participation avec un grand nombre (par exemple, 10 000) de validateurs et que vous ayez un grand nombre (par exemple, 100) blocs qui doivent être vérifiés. Aucun ordinateur n'est assez puissant pour valider tous ces blocs avant l'arrivée du prochain ensemble de blocs.
Par conséquent, ce que nous faisons, c'est que nous divisons au hasard le travail de vérification . Nous mélangeons au hasard la liste des validateurs, et nous affectons les 100 premiers validateurs de la liste mélangée pour vérifier le premier bloc, les 100 seconds validateurs de la liste mélangée pour vérifier le deuxième bloc, etc. Un groupe de validateurs sélectionnés au hasard qui est affecté à vérifier un bloc (ou effectuer une autre tâche) s'appelle un comité .
Lorsqu'un validateur vérifie un bloc, il publie une signature attestant qu'il l'a fait. Tout le monde, au lieu de vérifier 100 blocs entiers, ne vérifie désormais que 10 000 signatures - une quantité de travail beaucoup plus petite, en particulier avec l'agrégation de signatures BLS . Au lieu que chaque bloc soit diffusé via le même réseau P2P, chaque bloc est diffusé sur un sous-réseau différent, et les nœuds n'ont qu'à rejoindre les sous-réseaux correspondant aux blocs dont ils sont responsables (ou qui les intéressent pour d'autres raisons).
Considérez ce qui se passe si la puissance de calcul de chaque nœud augmente de 2x. Étant donné que chaque nœud peut désormais valider en toute sécurité 2 fois plus de signatures, vous pouvez réduire la taille minimale du dépôt de jalonnement pour prendre en charge 2 fois plus de validateurs, et vous pouvez donc créer 200 comités au lieu de 100.
Par conséquent, vous pouvez vérifier 200 blocs par emplacement au lieu de 100. De plus, chaque bloc individuel peut être 2 fois plus grand. Par conséquent, vous avez 2x plus de blocs de 2x la taille, ou 4x plus de capacité de chaîne au total.
Nous pouvons introduire un peu de jargon mathématique pour parler de ce qui se passe. En utilisant la notation Big O , nous utilisons " O(C) " pour désigner la capacité de calcul d'un seul nœud. Une blockchain traditionnelle peut traiter des blocs de taille O(C) . Une chaîne fragmentée telle que décrite ci-dessus peut traiter des blocs O(C) en parallèle (rappelez-vous que le coût pour chaque nœud de vérifier chaque bloc indirectement est O(1) car chaque nœud n'a besoin de vérifier qu'un nombre fixe de signatures), et chaque bloc a une capacité O(C) , et donc la capacité totale de la chaîne fragmentée est O(C2) . C'est pourquoi nous appelons ce type de partitionnement quadratique , et cet effet est l'une des principales raisons pour lesquelles nous pensons qu'à long terme, le partitionnement est le meilleur moyen de faire évoluer une blockchain.
Il existe deux différences essentielles :
Ces deux différences garantissent que le sharding crée un environnement pour les applications qui préserve les propriétés de sécurité clés d'un environnement à chaîne unique, ce que les écosystèmes multichaînes ne font pas fondamentalement.
Un refrain commun dans les cercles Bitcoin, et avec lequel je suis entièrement d'accord, est que les blockchains comme Bitcoin (ou Ethereum) ne reposent PAS complètement sur une hypothèse de majorité honnête . S'il y a une attaque à 51% sur une telle blockchain, l'attaquant peut faire des choses désagréables, comme annuler ou censurer des transactions, mais il ne peut pas insérer de transactions invalides. Et même s'ils annulent ou censurent les transactions, les utilisateurs exécutant des nœuds réguliers pourraient facilement détecter ce comportement, donc si la communauté souhaite se coordonner pour résoudre l'attaque avec un fork qui enlève le pouvoir de l'attaquant, ils pourraient le faire rapidement.
L'absence de cette sécurité supplémentaire est l'une des principales faiblesses des chaînes à haut TPS plus centralisées . De telles chaînes n'ont pas et ne peuvent pas avoir une culture d'utilisateurs réguliers exécutant des nœuds, et donc les principaux nœuds et acteurs de l'écosystème peuvent beaucoup plus facilement se réunir et imposer un changement de protocole que la communauté n'aime pas du tout. Pire encore, les nœuds des utilisateurs l'accepteraient par défaut. Après un certain temps, les utilisateurs s'en apercevraient, mais à ce moment-là, le changement de protocole forcé serait un fait accompli : la charge de coordination incomberait aux utilisateurs pour rejeter le changement, et ils devraient prendre la douloureuse décision d'annuler une journée ou plus de activité que tout le monde croyait déjà finalisée.
Idéalement, nous voulons avoir une forme de partitionnement qui évite les hypothèses de validité de 51 % et préserve le puissant rempart de sécurité que les chaînes de blocs traditionnelles obtiennent d'une vérification complète. Et c'est exactement ce sur quoi porte une grande partie de nos recherches au cours des dernières années.
Nous pouvons décomposer le problème de validation évolutive résistant aux attaques à 51 % en deux cas :
Validation du calcul : vérifier que certains calculs ont été effectués correctement, en supposant que vous êtes en possession de toutes les entrées du calcul.
Valider la disponibilité des données : vérifier que les entrées du calcul elles-mêmes sont stockées sous une forme où vous pouvez les télécharger si vous en avez vraiment besoin ; cette vérification doit être effectuée sans télécharger l'intégralité des entrées elles-mêmes (car les données pourraient être trop volumineuses pour être téléchargées pour chaque bloc).
La validation d'un bloc dans une blockchain implique à la fois un calcul et une vérification de la disponibilité des données : vous devez être convaincu que les transactions dans le bloc sont valides et que le nouveau hachage racine d'état réclamé dans le bloc est le résultat correct de l'exécution de ces transactions, mais vous devez également doivent être convaincus que suffisamment de données du bloc ont été effectivement publiées pour que les utilisateurs qui téléchargent ces données puissent calculer l'état et continuer à traiter la blockchain. Cette deuxième partie est un concept très subtil mais important appelé le problème de disponibilité des données ; plus à ce sujet plus tard.
La validation évolutive du calcul est relativement facile ; il existe deux familles de techniques : les preuves de fraude et les ZK-SNARK .
Les deux technologies peuvent être décrites simplement comme suit :
C
avec l'entrée X
, vous obtenez la sortie Y
". Vous faites confiance à ces messages par défaut, mais vous laissez la possibilité à quelqu'un d'autre avec un dépôt jalonné de faire un défi (un message signé disant "Je ne suis pas d'accord, la sortie est Z"). Ce n'est qu'en cas de défi que tous les nœuds exécutent le calcul. Celui des deux partis qui s'est trompé perd son dépôt, et tous les calculs qui dépendent du résultat de ce calcul sont recalculés.C
sur l'entrée X
donne la sortie Y
". La preuve est cryptographiquement "saine": si C(x)
n'est pas égal à Y
, il est informatiquement impossible de faire une preuve valide. La preuve est également rapide à vérifier, même si l'exécution de C
lui-même prend énormément de temps. Voir cet article pour plus de détails mathématiques sur les ZK-SNARK.
Le calcul basé sur des preuves de fraude est évolutif car "dans le cas normal", vous remplacez l'exécution d'un calcul complexe par la vérification d'une seule signature. Il y a le cas exceptionnel, où vous devez vérifier le calcul en chaîne car il y a un défi, mais le cas exceptionnel est très rare car le déclencher coûte très cher (soit le demandeur d'origine, soit le challenger perd un gros dépôt). Les ZK-SNARK sont conceptuellement plus simples - ils remplacent simplement un calcul par une vérification de preuve beaucoup moins chère - mais les mathématiques derrière leur fonctionnement sont considérablement plus complexes.
Il existe une classe de système semi-évolutif qui ne vérifie que le calcul de manière évolutive, tout en exigeant que chaque nœud vérifie toutes les données. Cela peut être rendu assez efficace en utilisant un ensemble d'astuces de compression pour remplacer la plupart des données par des calculs. C'est le domaine des rollups .
Un anti-fraude ne peut pas être utilisé pour vérifier la disponibilité des données. Les preuves de fraude pour le calcul reposent sur le fait que les entrées du calcul sont publiées en chaîne au moment où la réclamation d'origine est soumise, et donc si quelqu'un conteste, l'exécution du défi se déroule exactement dans le même "environnement" que l'exécution d'origine. événement. Dans le cas de la vérification de la disponibilité des données, vous ne pouvez pas le faire, car le problème est précisément le fait qu'il y a trop de données à vérifier pour les publier en chaîne. Par conséquent, un schéma anti-fraude pour la disponibilité des données se heurte à un problème clé : quelqu'un pourrait prétendre que "les données X sont disponibles" sans les publier, attendre d'être mis au défi, puis publier les données X et faire apparaître le challenger au reste du réseau est incorrect.
Ceci est développé dans le dilemme du pêcheur :
L'idée centrale est que les deux "mondes", l'un où V1 est un éditeur maléfique et V2 est un challenger honnête et l'autre où V1 est un éditeur honnête et V2 est un challenger maléfique, sont indiscernables pour quiconque n'essayait pas de télécharger cette donnée particulière à ce moment-là. Et bien sûr, dans une blockchain décentralisée évolutive, chaque nœud individuel ne peut espérer télécharger qu'une petite partie des données, donc seule une petite partie des nœuds verrait quoi que ce soit sur ce qui s'est passé, sauf le simple fait qu'il y avait un désaccord.
Le fait qu'il soit impossible de distinguer qui avait raison et qui avait tort rend impossible la mise en place d'un système anti-fraude fonctionnel pour la disponibilité des données.
Malheureusement, la simple validité n'est pas suffisante pour garantir le bon fonctionnement d'une blockchain. En effet, si la blockchain est valide mais que toutes les données ne sont pas disponibles , les utilisateurs n'ont aucun moyen de mettre à jour les données dont ils ont besoin pour générer des preuves que tout futur bloc est valide. Un attaquant qui génère un bloc valide mais indisponible mais qui disparaît ensuite peut effectivement bloquer la chaîne. Quelqu'un pourrait également retenir les données de compte d'un utilisateur spécifique jusqu'à ce que l'utilisateur paie une rançon, de sorte que le problème n'est pas purement un problème de vivacité.
Il existe de solides arguments théoriques de l'information selon lesquels ce problème est fondamental, et il n'y a pas d'astuce intelligente (par exemple, impliquant des accumulateurs cryptographiques ) qui puisse le contourner. Voir cet article pour plus de détails.
La clé est une technologie appelée échantillonnage de la disponibilité des données . L'échantillonnage de la disponibilité des données fonctionne comme suit :
Les codes d'effacement transforment un problème de « vérification de la disponibilité à 100 % » (chaque élément de données est disponible) en un problème de « vérification de la disponibilité à 50 % » (au moins la moitié des éléments sont disponibles). L'échantillonnage aléatoire résout le problème de disponibilité de 50 %. Si moins de 50 % des données sont disponibles, alors au moins une des vérifications échouera presque certainement, et si au moins 50 % des données sont disponibles, alors que certains nœuds peuvent ne pas reconnaître un bloc comme disponible, il faut un seul nœud honnête pour exécuter la procédure de reconstruction du code d'effacement afin de récupérer les 50 % restants du bloc. Ainsi, au lieu de devoir télécharger 1 Mo pour vérifier la disponibilité d'un bloc de 1 Mo, il suffit de télécharger quelques kilo-octets. Cela permet d'exécuter une vérification de la disponibilité des données sur chaque bloc. Voir cet article pour savoir comment cette vérification peut être efficacement mise en œuvre avec des sous-réseaux peer-to-peer.
Un ZK-SNARK peut être utilisé pour vérifier que le codage d'effacement sur une donnée a été effectué correctement , puis les branches Merkle peuvent être utilisées pour vérifier des morceaux individuels. Alternativement, vous pouvez utiliser des engagements polynomiaux (par exemple, les engagements de Kate (alias KZG) ), qui effacent essentiellement le codage et prouvent les éléments individuels et la vérification de l'exactitude dans un seul composant simple - et c'est ce qu'utilise le sharding Ethereum.
Supposons que vous disposiez de 100 blocs et que vous souhaitiez vérifier efficacement l'exactitude de chacun d'eux sans faire appel à des comités. Nous devons faire ce qui suit :
Chaque client effectue un échantillonnage de la disponibilité des données sur chaque bloc, en vérifiant que les données de chaque bloc sont disponibles, tout en téléchargeant seulement quelques kilo-octets par bloc, même si le bloc dans son ensemble a une taille d'un mégaoctet ou plus. Un client n'accepte un bloc que lorsque toutes les données de ses défis de disponibilité ont été correctement traitées.
Maintenant que nous avons vérifié la disponibilité des données, il devient plus facile de vérifier l'exactitude. Il existe deux techniques :
Dans l'un ou l'autre des cas ci-dessus, chaque client n'a besoin d'effectuer qu'une petite quantité de travail de vérification par bloc, quelle que soit la taille du bloc. Dans le cas des preuves de fraude, les blocs devront parfois être entièrement vérifiés en chaîne, mais cela devrait être extrêmement rare car déclencher ne serait-ce qu'un seul défi coûte très cher.
Et c'est tout ce qu'il y a à faire ! Dans le cas du sharding Ethereum, le plan à court terme est de faire en sorte que les blocs shardés ne contiennent que des données ; c'est-à-dire que les fragments sont purement un "moteur de disponibilité des données", et c'est le travail des cumuls de couche 2 d'utiliser cet espace de données sécurisé, ainsi que des preuves de fraude ou des ZK-SNARK, pour mettre en œuvre des capacités de traitement des transactions sécurisées à haut débit. Cependant, il est tout à fait possible de créer un tel système intégré pour ajouter une exécution "native" à haut débit.
L'objectif principal du sharding est de se rapprocher le plus possible de la réplication des propriétés de sécurité les plus importantes des chaînes de blocs traditionnelles (non partitionnées), mais sans que chaque nœud ait besoin de vérifier personnellement chaque transaction.
Sharding est assez proche. Dans une blockchain traditionnelle :
Dans une blockchain partagée avec des fonctionnalités de sécurité avancées :
Les blocs invalides ne peuvent pas passer car :
Les blocs indisponibles ne peuvent pas passer car :
Les chaînes traditionnelles à haut TPS sans sharding n'ont aucun moyen de fournir ces garanties. Les écosystèmes multichaînes n'ont aucun moyen d'éviter le problème d'un attaquant sélectionnant une chaîne pour l'attaque et la prenant facilement en charge (les chaînes pourraient partager la sécurité, mais si cela était mal fait, cela se transformerait de facto en une chaîne traditionnelle à haut TPS avec tous ses inconvénients, et si c'était bien fait, ce serait juste une mise en œuvre plus compliquée des techniques de partitionnement ci-dessus).
Les chaînes latérales dépendent fortement de la mise en œuvre, mais elles sont généralement vulnérables soit aux faiblesses des chaînes traditionnelles à haut TPS (c'est-à-dire si elles partagent des mineurs/validateurs), soit aux faiblesses des écosystèmes multichaînes (c'est-à-dire si elles ne partagent pas de mineurs/validateurs ). Les chaînes fragmentées évitent ces problèmes.
Cependant, il y a quelques failles dans l'armure du système fragmenté . Notamment :
Ce sont des préoccupations valables, bien qu'à notre avis, elles soient largement compensées par la réduction de la centralisation au niveau de l'utilisateur permise en permettant à davantage d'applications de s'exécuter en chaîne plutôt que via des services centralisés de couche 2. Cela dit, ces préoccupations, en particulier les deux dernières, sont en pratique la véritable contrainte à l'augmentation du débit d'une chaîne fragmentée au-delà d'un certain point. Il y a une limite au caractère quadratique du partitionnement quadratique.
Incidemment, les risques croissants pour la sécurité des blockchains fragmentées si leur débit devient trop élevé sont également la principale raison pour laquelle l'effort d'extension au sharding super-quadratique a été largement abandonné ; il semble que garder le partage quadratique juste quadratique est vraiment le juste milieu.
Une alternative au sharding qui est souvent proposée est d'avoir une chaîne structurée comme une chaîne centralisée à haut TPS, sauf qu'elle utilise l'échantillonnage de la disponibilité des données et le sharding en plus pour permettre la vérification de la validité et de la disponibilité.
Cela améliore les chaînes centralisées à haut TPS telles qu'elles existent aujourd'hui, mais reste considérablement plus faible qu'un système fragmenté. Ceci pour plusieurs raisons :
Les systèmes correctement partitionnés sont meilleurs comme couche de base. Étant donné une couche de base partitionnée, vous pouvez toujours créer un système de production centralisé (par exemple, parce que vous souhaitez un domaine à haut débit avec une composabilité synchrone pour defi ) superposé en le créant sous forme de cumul. Mais si vous avez une couche de base avec une dépendance à la production de blocs centralisée, vous ne pouvez pas créer une couche 2 plus décentralisée par-dessus.
Également publié ici .