La blockchain, en raison de son design décentralisé, a sacrifié l'efficacité, donc améliorer la vitesse d'exécution est l'un des problèmes urgents à résoudre. La "couche d'exécution" de la blockchain est la partie clé qui traite chaque transaction et l'ajoute à la chaîne. Pour accélérer la capacité de traitement, l'amélioration de la couche d'exécution est devenue l'une des stratégies clés, et l'exécution parallèle est une percée importante dans ce domaine.
Les blockchains traditionnelles traitent généralement les transactions une par une de manière séquentielle, ce qui limite considérablement la vitesse des transactions, en particulier dans des réseaux à forte densité de transactions où cela peut provoquer des congestions. Cependant, grâce à l'exécution parallèle, plusieurs transactions peuvent être traitées simultanément, augmentant considérablement l'efficacité d'exécution et allégeant la pression sur la chaîne.
Pour mieux comprendre ce qu'est le parallèle, nous commencerons par introduire l'exécution, en prenant Ethereum dans le mode PBS après la fusion comme exemple, pour expliquer ce qu'est l'exécution tout en montrant où elle se situe dans l'ensemble du cycle de vie des transactions.
Étapes spécifiques de l'exécution des transactions
La transaction entre dans le pool de mémoire et est filtrée et triée : il s'agit de la phase de prétraitement après que la transaction a été soumise, comprenant l'interaction entre Mempool, Searcher et Builder, pour compléter le filtrage et le tri des transactions.
Le Builder construit le bloc (mais ne l'exécute pas) : le Builder aligne les transactions rentables en un bloc pour compléter le regroupement et le tri des transactions.
Le Proposer vérifie et soumet le bloc : une fois la construction du bloc terminée, le Builder envoie la proposition du bloc au Proposer. Le Proposer vérifie la structure et le contenu des transactions du bloc, puis soumet officiellement le bloc au réseau pour commencer l'exécution.
Exécution des transactions : une fois le bloc soumis, les nœuds exécutent les transactions dans le bloc une par une. C'est une phase clé de mise à jour de l'état où chaque transaction déclenche un appel de contrat intelligent, un changement de solde de compte ou un changement d'état.
Témoignage des témoins : les validateurs témoignent des résultats d'exécution du bloc et de la racine d'état, en les considérant comme une confirmation finale. Cela garantit l'authenticité et la validité du bloc au niveau d'exécution et prévient les incohérences.
Synchronisation de l'état : chaque nœud synchronise les résultats d'exécution du bloc (tels que les soldes de compte, les mises à jour d'état de contrat, etc.) à son état local, après avoir exécuté chaque transaction, le nœud calcule et stocke une nouvelle racine d'état, qui servira d'état initial pour le prochain bloc.
Bien sûr, cela ne concerne que la synchronisation de l'état des transactions par bloc. Pour maintenir l'état le plus récent sur la chaîne, en général, les nœuds synchronisent les données bloc par bloc et continuent de valider les blocs et les états. Mais pour atteindre la finalité sous un mécanisme POS, les agrégateurs doivent agréger les signatures des témoins de chaque Slot en une seule signature complète et la transmettre au proposant du Slot suivant. De plus, les validateurs doivent, après un Epoch, confirmer l'état de tous les blocs de cet Epoch sur la base du nombre de votes, formant ainsi un point de contrôle d'état de consensus temporaire. Lorsque deux Epoch consécutifs obtiennent le soutien de la majorité des témoins, les blocs et les transactions atteignent la finalité.
Du point de vue du cycle de vie complet d'une transaction, l'exécution se produit après que le Proposer a vérifié la structure et le contenu des transactions du bloc envoyés par le Builder. Le processus d'exécution réel nécessite de traiter chaque transaction individuellement et de mettre à jour l'état correspondant du compte ou du contrat. Une fois toutes les transactions exécutées, le Proposer calcule une nouvelle racine d'état (racine Merkle), qui résume les résultats d'exécution de toutes les transactions du bloc actuel et l'état global final. En termes simples, le processus complet d'exécution d'un bloc comprend une série de calculs nécessaires pour passer l'Ethereum de l'état précédent à l'état suivant, de l'exécution de chaque transaction au calcul de la racine Merkle.
Exécution séquentielle
En contraste avec le parallèle, l'exécution séquentielle est la méthode d'exécution la plus courante dans les blockchains aujourd'hui. En général, les transactions sont exécutées progressivement dans l'ordre. Une fois qu'une transaction a été exécutée, Ethereum met à jour l'état du compte et les informations associées (comme le solde, les données de stockage de contrat) dans l'arbre d'état du compte, générant un nouveau hachage d'état de compte. Une fois que tous les arbres d'état de compte sont mis à jour, cela forme ce qu'on appelle la racine de Merkle d'état. Après avoir complété la racine de Merkle d'état, la racine de Merkle de transaction et la racine de Merkle de reçu, l'en-tête du bloc est ensuite calculé en hachant, générant le hachage du bloc.
Dans ce cas, l'ordre d'exécution des transactions est crucial. Étant donné que l'arbre de Merkle est un arbre binaire de valeurs de hachage, les racines Merkle formées dans un ordre différent seront différentes.
Exécution parallèle
Dans un environnement d'exécution parallèle, les nœuds essaieront de traiter les transactions dans le bloc de manière parallèle. Au lieu d'exécuter les transactions une par une dans l'ordre, les transactions sont attribuées à différents "chemins d'exécution" pour qu'elles puissent s'exécuter simultanément. Grâce à l'exécution parallèle, le système peut traiter les transactions dans le bloc de manière plus efficace, augmentant ainsi le débit.
Une fois que toutes les transactions sont exécutées, les nœuds résument les résultats d'exécution (c'est-à-dire les mises à jour d'état affectées par les transactions) pour former un nouvel état de bloc. Cet état sera ajouté à la blockchain, représentant l'état global le plus récent sur la chaîne.
Conflit d'état
Étant donné que le parallèle traite les transactions sur différents chemins en même temps, l'un des principaux défis du parallèle est le conflit d'état. Cela signifie qu'il peut y avoir plusieurs transactions qui lisent ou écrivent sur la même partie des données (état) de la blockchain en même temps. Si cette situation n'est pas gérée correctement, cela peut conduire à des résultats d'exécution indéterminés. En raison de l'ordre différent de mise à jour des états, le résultat final des calculs peut également être différent. Prenons un exemple :
Supposons qu'il y a deux transactions, transaction A et transaction B, qui tentent toutes deux de mettre à jour le solde du même compte :
Transaction A : augmenter le solde du compte de 10.
Transaction B : augmenter le solde du compte de 20.
Le solde initial du compte est de 100.
Si nous exécutons en série, le résultat de l'ordre d'exécution est déterminé :
1. D'abord exécutez la transaction A, puis la transaction B :
Le solde du compte augmente d'abord de 10, atteignant 110.
Augmentez de 20, pour un total de 130.
2. Exécutez d'abord la transaction B, puis la transaction A :
Le solde du compte augmente d'abord de 20, atteignant 120.
Puis augmente de 10, atteignant finalement 130.
Dans ces deux séquences, le solde final est de 130, car le système garantit la cohérence de l'ordre d'exécution des transactions.
Cependant, dans un environnement d'exécution parallèle, les transactions A et B peuvent lire simultanément le solde initial de 100 et effectuer leurs calculs respectifs :
La transaction A lit un solde de 100, calcule et met à jour le solde à 110.
La transaction B lit également un solde de 100, calcule et met à jour le solde à 120.
Dans ce cas, en raison de l'exécution simultanée des transactions, le solde final n'est mis à jour qu'à 120, et non à 130, car les opérations de la transaction A et de la transaction B "couvrent" les résultats l'un de l'autre, entraînant un conflit d'état.
Ce type de problème de conflit d'état est généralement appelé "couverture de données", c'est-à-dire que lorsque des transactions essaient de modifier simultanément les mêmes données, elles peuvent se couvrir mutuellement, entraînant un état final incorrect. Un autre type de conflit d'état pourrait poser le problème de la garantie de l'ordre d'exécution. Étant donné que plusieurs transactions achèvent leurs opérations à différents moments, cela peut entraîner différents ordres d'exécution. Des ordres différents peuvent entraîner des résultats de calcul différents, rendant ainsi les résultats indéterminés.
Pour éviter cette incertitude, les systèmes d'exécution parallèle de blockchain introduisent généralement des mécanismes de détection de conflits et de rollback, ou effectuent une analyse des dépendances des transactions à l'avance pour garantir qu'elles s'exécutent en parallèle sans affecter la cohérence de l'état final.
Parallélisation optimiste et déterministe
Il existe deux méthodes pour traiter les problèmes potentiels de conflit d'état : la parallélisation déterministe et la parallélisation optimiste. Ces deux modèles présentent des compromis en termes d'efficacité et de complexité de conception.
La parallélisation déterministe nécessite de déclarer à l'avance l'accès à l'état. Les validateurs ou séquenceurs vérifient l'accès à l'état déclaré lors du tri des transactions. Si plusieurs transactions essaient d'écrire dans le même état, ces transactions sont marquées comme conflictuelles, évitant l'exécution simultanée. Les chaînes spécifiques mettent en œuvre la déclaration d'accès à l'état de différentes manières, mais cela inclut généralement les méthodes suivantes :
Par le biais des contraintes de spécification des contrats : les développeurs spécifient directement la portée d'accès à l'état dans le contrat intelligent. Par exemple, le transfert de jetons ERC-20 nécessite d'accéder aux champs de solde de l'expéditeur et du destinataire.
Par la déclaration de données structurées dans les transactions : ajout de champs spécifiques dans les transactions pour indiquer l'accès à l'état.
Par l'analyse du compilateur : le compilateur des langages de haut niveau peut analyser statiquement le code des contrats et générer automatiquement un ensemble d'accès à l'état.
Par déclaration contraignante du cadre : certains cadres exigent que les développeurs spécifient explicitement les états à accéder lors de l'appel de fonctions
La parallélisation optimiste traite d'abord les transactions de manière optimiste, puis lorsque des conflits se produisent, les transactions affectées sont réexécutées dans l'ordre. Pour éviter autant que possible les conflits, le cœur de la conception de la parallélisation optimiste est de prédire rapidement l'état à l'aide de données historiques, d'analyses statiques, etc. Cela signifie que le système, sans validation complète, suppose que certaines opérations ou mises à jour d'état sont valides, essayant d'éviter d'attendre tous les processus de validation afin d'améliorer les performances et le débit.
Bien que la parallélisation optimiste puisse éviter autant que possible les conflits grâce à des prédictions rapides de l'état, il existe encore des défis inévitables, en particulier lorsqu'il s'agit d'exécutions de contrats ou de transactions inter-chaînes. Si les conflits se produisent fréquemment, la réexécution peut ralentir considérablement les performances du système et augmenter la consommation de ressources informatiques.
La parallélisation déterministe, quant à elle, effectue une vérification des dépendances d'état avant les transactions pour éviter les conflits potentiels dans la parallélisation optimiste. Cependant, cela exige des développeurs qu'ils déclarent avec précision les dépendances d'état avant que les transactions soient soumises, augmentant ainsi la complexité de mise en œuvre.
Dilemme de l'EVM parallèle
Aborder les conflits d'état ne se limite pas à une distinction entre déterministe et optimiste. Dans le processus spécifique de mise en œuvre de la parallélisation, il est également nécessaire de considérer l'architecture de la base de données de la chaîne. Les problèmes de conflit d'état dans l'EVM sous l'architecture d'arbre de Merkle sont particulièrement difficiles. Un arbre de Merkle est une structure de hachage hiérarchique, et chaque fois qu'une transaction modifie un certain état de données, la valeur de hachage racine de l'arbre de Merkle doit également être mise à jour. Ce processus de mise à jour est récursif, calculant par niveau à partir des feuilles jusqu'à la racine. Étant donné que le hachage est irréversible, il ne peut être calculé pour le niveau supérieur qu'une fois que les modifications des données du niveau inférieur sont terminées. Cette caractéristique rend difficile la mise à jour parallèle.
Si deux transactions s'exécutent en parallèle et accèdent au même état (comme le solde du compte), cela entraînera un conflit entre les nœuds de l'arbre Merkle. Résoudre ce conflit nécessite généralement un mécanisme de gestion des transactions supplémentaire pour garantir qu'une valeur de hachage racine cohérente soit obtenue sur plusieurs branches. Cela n'est pas facile à réaliser pour l'EVM, car cela nécessite des compromis entre parallélisation et cohérence de l'état.
Solutions parallèles non EVM
Solana
Contrairement à l'arbre d'état global d'Ethereum, Solana utilise un modèle de compte. Chaque compte est un espace de stockage indépendant, stocké dans le grand livre, évitant ainsi les problèmes de conflit de chemin.
Solana est une parallélisation déterministe. Dans Solana, chaque transaction doit déclarer clairement au moment de la soumission les comptes à accéder et les permissions nécessaires (lecture seule ou lecture-écriture). Ce design permet aux nœuds de blockchain d'analyser à l'avance les ressources que chaque transaction doit accéder avant leur exécution. Étant donné que les transactions ont déjà déclaré toutes les dépendances de compte avant de commencer à s'exécuter, les nœuds peuvent déterminer quelles transactions accèderont aux mêmes comptes et celles qui peuvent être exécutées en parallèle en toute sécurité, ce qui permet un planificateur intelligent pour éviter les conflits et réaliser les bases de la planification parallèle.
Puisque chaque transaction a déjà déclaré les comptes et les permissions nécessaires avant l'exécution, Solana peut vérifier s'il existe des relations de dépendance entre les comptes dans les transactions (modèle Sealevel). Si les transactions n'ont pas de comptes partagés en lecture-écriture, le système peut les attribuer à différents processeurs pour une exécution parallèle.
Aptos
La conception d'exécution parallèle d'Aptos diffère considérablement de celle d'Ethereum, ayant apporté des innovations clés en termes d'architecture et de mécanismes, principalement en ce qui concerne le modèle de compte et le stockage d'état.
Ethereum doit fréquemment mettre à jour l'arbre d'état global (MPT) lors de l'exécution des transactions. Les états de tous les comptes et contrats sont stockés dans un arbre d'état partagé, et chaque transaction doit accéder et mettre à jour une partie de cet arbre d'état. En revanche, Aptos divise les comptes en unités d'état indépendantes, chaque objet étant une paire clé-valeur indépendante, les objets pouvant exister indépendamment les uns des autres, ne se liant que lorsque des relations de référence explicites existent. Il n'y a pas de chemins d'arbre communs entre les objets, évitant ainsi la concurrence de verrouillage et permettant une exécution complètement parallèle.
La structure de données sous-jacente d'Aptos est l'arbre Merkle Jellyfish. L'état de chaque objet est finalement stocké dans le JMT, en tant que paires clé-valeur indépendantes. Contrairement au MPT d'Ethereum, l'arbre Merkle Jellyfish prend la forme d'une structure d'arbre binaire complète, simplifiant ainsi les chemins de stockage et de requête des nœuds, réduisant considérablement le temps de validation. De plus, la position de chaque compte dans l'arbre est fixe, et les nœuds dans l'arbre sont stockés de manière indépendante, permettant la mise à jour et la recherche parallèles de plusieurs comptes.
Aptos est une parallélisation optimiste, n'exigeant pas de fournir à l'avance toutes les dépendances des comptes déclarées. Pour cela, Aptos utilise Block-STM, qui utilise un ordre de transaction prédéfini pour estimer les dépendances, réduisant ainsi le nombre d'annulations.
EVM parallèle
Comparé au parallèle non EVM, l'EVM parallèle fait face à des défis techniques plus importants en ce qui concerne la gestion des dépendances d'état, la détection des conflits, la gestion du gaz et les mécanismes de rollback. Pour mieux comprendre cela, nous pouvons nous référer à certains projets EVM parallèles (comme Sui, Monad, Canto) qui tentent de résoudre ces problèmes.
Sui
Tout comme Aptos, Sui utilise également un modèle d'objet pour gérer l'état, en utilisant chaque objet (par exemple, compte, état de contrat intelligent) comme une ressource indépendante, ces objets étant distingués par leur identifiant unique. Lorsque les transactions impliquent différents objets, ces transactions peuvent être traitées en parallèle, car elles opèrent sur des états différents, évitant ainsi les conflits directs.
Bien que Sui utilise un modèle d'objet pour gérer l'état, pour être compatible avec l'EVM, l'architecture de Sui utilise une couche d'adaptation ou un mécanisme d'abstraction supplémentaire pour faire le lien entre le modèle d'objet et le modèle de compte de l'EVM.
Dans Sui, la planification des transactions utilise une stratégie de parallélisation optimiste, supposant qu'il n'y a pas de conflit entre les transactions. Si un conflit se produit, le système utilise un mécanisme de rollback pour restaurer l'état.
Sui utilise le modèle d'objet et la technologie d'isolation d'état pour éviter efficacement les problèmes de dépendance d'état. Chaque objet est une ressource indépendante, différentes transactions peuvent être exécutées en parallèle, améliorant ainsi le débit et l'efficacité. Cependant, le compromis de cette méthode est la complexité du modèle d'objet et le coût des mécanismes de rollback. Si un conflit se produit entre les transactions, certaines parties de l'état devront être annulées, ce qui augmentera la charge sur le système et pourrait affecter l'efficacité du traitement parallèle. Comparé aux systèmes parallèles non EVM (comme Solana), Sui nécessite plus de ressources informatiques et de stockage pour maintenir une efficacité parallèle élevée.
Monad
Comme Sui, Monad adopte également une parallélisation optimiste. Cependant, la parallélisation optimiste de Monad prédit certaines transactions ayant des dépendances avant l'exécution concrète, principalement à travers l'analyse statique du code de Monad. Cette prédiction nécessite un accès à l'état, et la façon dont l'état est stocké dans la base de données Ethereum rend cet accès très difficile. Pour rendre la lecture de l'état plus efficace lors de l'exécution parallèle, Monad a également reconstruit la base de données.
L'arbre d'état Monad est partitionné, chaque partition maintenant son propre sous-arbre d'état. Lors de la mise à jour, seules les partitions concernées doivent être modifiées, sans nécessiter la reconstruction de tout l'arbre d'état. Grâce à une table d'index d'état, il est possible de localiser rapidement l'état dans la partition, réduisant ainsi les interactions entre partitions.
Résumé
Le cœur du parallèle est d'augmenter l'efficacité d'exécution de la couche d'exécution par une méthode d'exécution multipath, et pour réaliser l'exécution multipath, la chaîne doit effectuer une série de mécanismes tels que la détection de conflits et les mécanismes de rollback pour garantir qu'elles s'exécutent en parallèle sans affecter la cohérence de l'état final, ainsi que d'apporter certaines améliorations à la base de données.
Bien sûr, l'amélioration de l'efficacité de la couche d'exécution ne se limite pas à une seule méthode de parallélisation. L'optimisation des étapes d'exécution peut également se faire en réduisant les opérations de lecture et d'écriture nécessaires pour une transaction dans la base de données. Cependant, l'amélioration de la vitesse de toute la chaîne implique un éventail plus large, y compris l'amélioration de l'efficacité de la couche de consensus.
Chaque technologie a ses propres conditions de limitation. La parallélisation n'est qu'un moyen d'améliorer l'efficacité. La décision d'utiliser cette technologie doit également tenir compte de sa convivialité pour les développeurs, de la possibilité de l'implémenter sans sacrifier la décentralisation, etc. L'accumulation de technologies n'est pas nécessairement bénéfique, du moins pour Ethereum, la parallélisation n'est pas si attrayante. Si l'on considère uniquement l'amélioration de l'efficacité, l'ajout de la parallélisation n'est pas la solution optimale pour Ethereum, que ce soit en termes de simplicité ou en regardant la feuille de route actuelle d'Ethereum centrée sur Rollup.