NOUVEAU : Helius acquiert Light Protocol
Comment faire aboutir des transactions sur Solana
Blog/Développement

Comment faire aboutir des transactions sur Solana

Ingénieur en expérience développeurAnam Ansari sur XAnam Ansari sur LinkedIn
18 min de lecture

Solana connaît récemment un volume sans précédent, ce qui entraîne un taux élevé de transactions échouées ou abandonnées.

Le nombre de transactions par seconde (TPS) de Solana s’élève à plus de 1 000 transactions hors vote. Quinn (implémentation Rust de la couche réseau — QUIC) présente des limites pour gérer efficacement le spam lors des périodes de forte demande. Les leaders de bloc peuvent alors devoir interrompre certaines connexions de manière sélective. Parmi toutes les transactions ayant échoué, environ 8 % ont été initiées par de véritables utilisateurs, tandis que les autres étaient des transactions arbitraires de bots.

Il est essentiel de comprendre comment les transactions sont soumises et traitées sur Solana pour gérer leurs échecs. Cet article examine les causes possibles d’échec et recommande des bonnes pratiques pour augmenter le débit des transactions. Il suppose une compréhension élémentaire du modèle de programmation de Solana, ainsi que de la création et de l’envoi de transactions.

Transactions

L’exécution d’un programme commence par une transaction soumise au cluster. Une transaction contient :

  • Un tableau de tous les comptes qu’elle prévoit de lire ou de modifier
  • Une ou plusieurs instructions (c’est-à-dire les plus petites unités d’exécution)
  • Un blockhash récent
  • Une ou plusieurs signatures

L’environnement d’exécution traite dans l’ordre et de manière atomique chacune des instructions contenues dans la transaction. Si une partie d’une instruction échoue, toute la transaction échoue.

Qu’est-ce qu’un blockhash ?

Un « blockhash » est le dernier hash de Proof of History (PoH) d’un slot. Comme Solana utilise la PoH en tant qu’horloge de confiance, le blockhash récent d’une transaction peut être considéré comme un horodatage. Le blockhash empêche les doublons et définit la durée de vie des transactions. Une transaction dont le blockhash est trop ancien est rejetée. L’âge maximal d’un blockhash est de 150 blocs, soit environ ~1 minute et 19 secondes.

Comment les transactions sont-elles soumises ?

Solana est géré par un groupe de validateurs qui valident les transactions ajoutées au registre. Un validateur leader est choisi au sein de ce groupe pour ajouter les entrées au registre. Une entrée du registre peut être un tick ou une entrée de transaction. Le registre conserve une liste d’entrées contenant les transactions signées par les clients. Le bloc de genèse constitue conceptuellement l’origine du registre. Toutefois, le registre d’un validateur réel peut ne conserver que les blocs les plus récents afin de réduire l’espace de stockage, car les blocs plus anciens ne sont par conception pas nécessaires à la validation des futurs blocs.

Le validateur leader ne peut produire qu’un seul bloc par slot, et le blockhash est l’identifiant unique de chaque bloc. Il s’agit d’un hash de toutes les entrées d’un bloc, y compris le hash du bloc précédent. Le calendrier des leaders est déterminé avant chaque époque, généralement environ deux jours à l’avance, afin de définir quel validateur sera le leader à chaque instant. Lorsqu’une transaction est initiée, elle est transmise au validateur leader actuel et au suivant.

Les transactions peuvent être soumises au leader via : 

  1. Serveur RPC : un fournisseur RPC peut soumettre des transactions avec la méthode JSON-RPC sendTransaction. Le nœud RPC qui les reçoit tente de les envoyer sous forme de paquet UDP au leader actuel et au suivant toutes les deux secondes, jusqu’à ce que la transaction soit finalisée ou que son blockhash expire (après 150 blocs, soit environ ~1 minute et 19 secondes). Jusque-là, aucune trace de la transaction n’existe en dehors des informations connues du client et des nœuds RPC qui la relaient. 
  2. Client TPU : le client TPU se contente de soumettre la transaction. Le logiciel client doit gérer la rediffusion et la transmission au leader.

Pour utiliser la méthode sendTransaction, vous devez transmettre l’objet de transaction encodé sous forme de chaîne. Les autres paramètres facultatifs sont les suivants :

  1. encoding : l’encodage utilisé pour les données de transaction est base58 ou base64. 
  2. skipPreflight : les vérifications préalables comprennent la vérification des signatures de la transaction et sa simulation sur le slot bancaire défini par le niveau d’engagement préalable. Si la vérification préalable échoue, une erreur est renvoyée. La valeur par défaut de cette fonctionnalité est false, ce qui signifie que les vérifications préalables ne sont pas ignorées.
  3. preflightCommitment : indique le niveau d’engagement utilisé pendant les vérifications préalables. Par défaut, ce niveau est défini sur finalized, mais vous pouvez le modifier en indiquant une chaîne. Il est recommandé d’utiliser le même niveau d’engagement pour la transaction et les vérifications préalables afin d’éviter tout comportement inattendu.
  4. maxRetries : le paramètre maxRetries définit le nombre maximal de tentatives d’envoi de la transaction au leader par le nœud RPC. Si ce paramètre n’est pas fourni, le nœud RPC réessaie jusqu’à ce que la transaction soit finalisée ou que le blockhash expire. 
  5. minContextSlot : le paramètre minContextSlot indique le slot minimal à utiliser pour effectuer les vérifications préalables de la transaction.

Comment les transactions sont-elles traitées ?

La Transaction Processing Unit (TPU) du validateur reçoit la transaction, vérifie sa signature, l’exécute et la partage avec les autres validateurs du réseau.

La TPU traite les transactions en cinq phases distinctes :

Étape de récupération

L’étape de récupération reçoit les transactions. Elle classe les transactions entrantes en fonction de trois ports :

  • tpu : gère les transactions ordinaires telles que les transferts de tokens, les émissions de NFT et les instructions de programme
  • tpu_vote : se consacre exclusivement aux transactions de vote
  • tpu_forwards : si le leader actuel ne peut pas traiter toutes les transactions, il transmet les paquets non traités au leader suivant

Les paquets sont regroupés par lots de 128, puis transmis à l’étape SigVerify.

Étape SigVerify

L’étape SigVerify vérifie les signatures des paquets et les élimine en cas d’échec de la vérification. Les votes et les paquets ordinaires suivent deux pipelines distincts. Du point de vue du logiciel, les paquets reçus contiennent certaines métadonnées, mais il n’est pas encore établi qu’il s’agit de transactions.

Si un GPU est installé, il sert à vérifier les signatures. Une logique utilisant les adresses IP permet également de gérer les paquets excédentaires et de les abandonner lorsque le trafic augmente.

Étape bancaire

Cette étape filtre et traite les transactions. Elle comprend actuellement six threads de travail indépendants : deux pour les votes et quatre hors vote. Les transactions ordinaires sont ajoutées aux threads hors vote. Chaque thread dispose d’un tampon local pouvant contenir jusqu’à 64 transactions sans conflit dans une file de priorité. Ces transactions sont ensuite traitées en parallèle grâce à Sealevel. Consultez cette vidéo pour en savoir plus sur l’étape bancaire.

Service Proof of History

Le module PoH Service enregistre le passage des ticks. Chaque tick représente une unité de temps, et un slot contient 64 ticks. Le hash est généré de manière répétée jusqu’à la réception d’un enregistrement provenant de l’étape bancaire :

next_hash = hash(prev_hash, hash(transaction_ids))

Ces enregistrements sont ensuite convertis en entrées, puis diffusés sur le réseau lors de l’étape de diffusion.

Étape de diffusion

Les entrées du service PoH sont converties en shreds, qui représentent la plus petite unité d’un bloc, puis envoyées au reste du réseau au moyen d’une technique de propagation des blocs appelée Turbine. À un niveau général, Turbine divise un bloc en fragments plus petits et les distribue au moyen d’une structure hiérarchique de nœuds. Les nœuds n’ont pas besoin d’être en contact avec tous les autres. Ils doivent seulement communiquer avec quelques nœuds sélectionnés. Consultez cet article pour en savoir plus sur Turbine et son fonctionnement. 

Pourquoi les transactions échouent-elles ?

Outre les échecs causés par des instructions incorrectes ou des erreurs de programme personnalisées, les transactions peuvent échouer pour les raisons suivantes :

Abandons sur le réseau

La couche réseau peut abandonner une transaction avant même qu’un leader ne la traite. La perte de paquets UDP est l’explication la plus simple. Une autre raison est liée au fetch stage de la TPU. Lorsque le réseau est fortement sollicité, les validateurs peuvent être submergés par le nombre de transactions à traiter. Ils peuvent transmettre les transactions excédentaires au port tpu_forward du validateur suivant. Toutefois, la quantité de données transmissible est limitée, et chaque transmission ne peut parcourir qu’un seul saut entre validateurs. Les transactions reçues sur le port tpu_forwards ne sont donc pas transmises à d’autres validateurs. Si la file d’attente des rediffusions dépasse 10 000 transactions, les nouvelles transactions soumises sont abandonnées.

Blockhash obsolète ou incorrect 

Chaque transaction comporte un « blockhash récent » qui sert d’horodatage à l’horloge de Proof of History (PoH). Ce blockhash aide les validateurs à ne pas traiter deux fois la même transaction et permet de suivre le moment et l’ordre de traitement des transactions. Pendant le traitement, le validateur rejette toute transaction dont le blockhash n’est pas valide.

Expiration du blockhash

Le blockhash d’une transaction expire lorsqu’il n’est plus considéré comme suffisamment « récent ». Pour traiter une transaction, les validateurs Solana recherchent dans un bloc le numéro de slot correspondant au blockhash. Si le validateur ne trouve aucun numéro de slot pour ce blockhash, ou si le numéro trouvé se situe plus de 151 slots avant le numéro du slot du bloc en cours de traitement, la transaction est rejetée. Par défaut, une transaction Solana expire si elle n’est pas intégrée à un bloc dans un délai donné (environ ~1 minute et 19 secondes).

Nœuds RPC en retard

Lorsque vous soumettez une transaction via un RPC, le pool RPC peut être en avance sur le reste du réseau. Cela peut poser problème lorsque les nœuds du pool doivent travailler ensemble. Par exemple, si le recentBlockhash d’une transaction est récupéré auprès de la partie avancée du pool, puis soumis à sa partie en retard, les nœuds ne reconnaissent pas ce blockhash avancé et rejettent la transaction. Vous pouvez détecter ce cas au moment de la soumission en activant les vérifications préalables sur sendTransaction.

Forks temporaires du réseau

Les forks temporaires du réseau peuvent également entraîner l’abandon de transactions. Si un validateur rejoue trop lentement ses blocs pendant l’étape bancaire, il peut créer un fork minoritaire. Lorsqu’un client construit une transaction, celle-ci peut référencer un recentBlockhash qui n’existe que sur le fork minoritaire. Après la soumission de la transaction, le cluster peut abandonner ce fork avant qu’elle ne soit traitée. Dans ce scénario, la transaction est abandonnée, car le blockhash est introuvable.

Comment faire aboutir mes transactions ?

Pour diagnostiquer les problèmes de confirmation, il est important de comprendre l’expiration des transactions. Suivez ces étapes afin d’augmenter les chances de réussite de vos transactions :

En bref

  • Récupérez le dernier blockhash avec le niveau d’engagement « confirmed » ou « finalized »
  • Définissez skipPreflight sur true
  • Optimisez le nombre d’unités de calcul demandées
  • Ajoutez et calculez dynamiquement les frais de priorité
  • Définissez maxRetries sur 0 et ajoutez une logique de nouvelle tentative personnalisée pour l’envoi des transactions.
  • Explorez les connexions stakées
  • Si la transaction n’est pas urgente, utilisez des nonces durables

Blockhash

Les transactions disposent d’un temps limité pour être traitées par le validateur. Si le blockhash associé à une transaction expire avant son traitement, celle-ci est annulée. Pour vous assurer que votre transaction aboutit, envoyez-la avec un blockhash récent. Si le blockhash expire avant que le validateur ne traite votre transaction, vous pouvez la soumettre à nouveau avec un nouveau blockhash. Deux méthodes sont possibles : 

1. Définir un nouveau niveau d’engagement :

La méthode d’API RPC recommandée pour récupérer le dernier blockhash est getLatestBlockhash. Par défaut, cette méthode utilise le niveau d’engagement finalized pour renvoyer le blockhash du dernier bloc finalisé. Ce niveau d’engagement indique qu’au moins 31 blocs confirmés ont été ajoutés au-dessus de ce bloc. Il élimine le risque d’utiliser un blockhash appartenant à un fork abandonné. Toutefois, un écart d’au moins 32 slots sépare généralement les derniers blocs confirmés et finalisés. Ce compromis réduit d’environ 13 secondes le temps restant avant l’expiration des transactions, voire davantage lorsque le cluster est instable. 

Vous pouvez remplacer le niveau d’engagement du blockhash en attribuant une autre valeur au paramètre d’engagement. Le niveau confirmed est recommandé pour les requêtes RPC, car il n’a généralement que quelques slots de retard sur le niveau processed et présente peu de risques d’appartenir à un fork abandonné. Bien que le niveau processed récupère le blockhash le plus récent, il n’est pas recommandé, car environ 5 % des blocs ne sont pas finalisés par le cluster en raison des forks inhérents au protocole Solana. Si votre transaction utilise un blockhash appartenant à un fork abandonné, aucun bloc de la blockchain finalisée ne le considérera comme récent.

2. Rechercher fréquemment de nouveaux blockhashes récents :

Ajoutez un script qui utilise fréquemment la méthode getLatestBlockhash, toutes les 60 secondes, pour récupérer et stocker le blockhash le plus récent. L’application dispose ainsi d’un blockhash à jour chaque fois qu’un utilisateur déclenche une transaction. Les portefeuilles doivent eux aussi rechercher fréquemment de nouveaux blockhashes et remplacer le blockhash récent d’une transaction juste avant sa signature, afin qu’il soit aussi récent que possible.

Ignorer les vérifications préalables

Avant la soumission d’une transaction, les vérifications préalables suivantes sont effectuées :

  • Les signatures de la transaction sont vérifiées.
  • La transaction est simulée sur le slot bancaire défini par le niveau d’engagement préalable. En cas d’échec, une erreur est renvoyée. 

Si le bloc choisi pour la simulation est antérieur à celui utilisé pour le blockhash de votre transaction, la simulation échoue avec la redoutable erreur « blockhash not found ». 

Si vous avez la certitude que la signature de votre transaction est valide et qu’il n’existe aucune autre erreur, vous pouvez ignorer la vérification préalable. Même si vous utilisez le paramètre skipPreflight, définissez toujours le paramètre preflightCommitment sur le même niveau d’engagement que celui utilisé pour récupérer le blockhash de votre transaction, aussi bien pour les requêtes sendTransaction que pour les requêtes simulateTransaction.

Unités de calcul

Lorsqu’une transaction est confirmée sur le réseau, elle consomme une partie des unités de calcul (CU) totales disponibles dans un bloc. Actuellement, la limite de calcul totale d’un bloc est de 48 millions de CU. Les développeurs peuvent définir un budget d’unités de calcul pour leurs transactions. En l’absence de budget, la valeur par défaut est de 200 000. De nombreuses transactions n’utilisent pas la totalité de ce budget, car aucune pénalité ne s’applique lorsqu’un budget supérieur aux besoins est demandé. Toutefois, demander trop d’unités de calcul dès le départ peut compliquer la planification efficace des transactions, car l’ordonnanceur ignore la capacité de calcul restante dans un bloc jusqu’à l’exécution de la transaction. Pour éviter cela, les développeurs doivent mieux ajuster leurs demandes de CU aux besoins de chaque transaction. Consultez ce guide pour optimiser le budget d’unités de calcul. Dans la prochaine mise à jour v1.18 du client Solana, les transactions nécessitant moins d’unités de calcul bénéficieront d’une priorité supérieure.

L’optimisation de votre utilisation des unités de calcul (CU) offre les avantages suivants :

  • Une transaction plus petite a plus de chances d’être incluse dans un bloc.
  • Des instructions moins coûteuses améliorent la composabilité de votre programme.
  • La consommation globale du bloc diminue, ce qui permet d’y inclure davantage de transactions.

Mettre en œuvre les frais de priorité

Des frais de priorité peuvent être ajoutés aux frais de base d’une transaction pour que les validateurs la traitent en priorité. Ces frais sont exprimés en micro-lamports par unité de calcul, c’est-à-dire en petites quantités de SOL. Ils sont ajoutés aux transactions pour inciter économiquement les nœuds validateurs à les inclure dans les blocs du réseau.

Il est cependant important de limiter le montant des frais de priorité. Payer plus que les frais habituels n’augmentera pas la probabilité de réussite de votre transaction. Il est donc recommandé de calculer dynamiquement ces frais afin de payer le montant approprié pour rester compétitif sans payer inutilement plus cher. Cette intégration est simple. Consultez la documentation officielle sur les frais de priorité ou utilisez l’API Helius prête à l’emploi.

Mettre en œuvre une logique robuste de nouvelle tentative

En cas de congestion du réseau, ajoutez à votre code une logique personnalisée pour gérer les échecs et réessayer manuellement les transactions. Pour cela, définissez le paramètre maxRetries sur 0 lorsque vous utilisez sendTransaction pour soumettre une transaction. Plusieurs méthodes permettent de réessayer une transaction :

  • Interrogez le transaction status avec différents niveaux d’engagement et réutilisez continuellement la même transaction signée jusqu’à sa confirmation. Employez un mécanisme de backoff exponentiel pour éviter le spam. Vous pouvez également soumettre les transactions à intervalles constants jusqu’à l’expiration d’un délai.
  • Stockez le lastValidBlockHeight provenant du getLatestBlockhash method. Interrogez ensuite la hauteur de bloc du cluster et réessayez manuellement la transaction lorsque la hauteur actuelle dépasse le lastValidBlockHeight. Lorsque vous effectuez cette interrogation via getLatestBlockhash, il est recommandé d’indiquer le niveau d’engagement souhaité. En définissant ce niveau sur confirmed (soumis au vote) ou finalized (environ ~30 blocs après confirmed), vous évitez d’interroger un blockhash provenant d’un fork minoritaire.

Connexions stakées

La bande passante réseau d’un leader est limitée. Pour l’utiliser efficacement, une pondération en fonction du stake est nécessaire afin d’éviter d’accepter aveuglément les transactions selon le principe du premier arrivé, premier servi, sans tenir compte de leur source. Solana étant un réseau de proof-of-stake, il est naturel d’étendre cette pondération pour améliorer la qualité de service des transactions. Ainsi, un nœud détenant 0,5 % du stake peut envoyer au moins 0,5 % des paquets au leader, sans que le reste du réseau, ni aucune combinaison du stake restant, ne puisse les évincer entièrement. Ce mécanisme est appelé Stake-Weighted Quality of Service (SWQoS).

Helius propose des connexions stakées avec ses offres payantes. Pour en savoir plus, consultez notre documentation : Envoyer des transactions sur Solana.

Nonces durables

Les nonces durables permettent de créer et de signer une transaction qui pourra être soumise à tout moment. Ils sont utilisés dans certains cas, notamment pour les services de conservation qui ont besoin de plus de temps pour produire la signature d’une transaction. Si votre transaction n’est pas urgente, vous pouvez utiliser cette méthode pour contourner la courte durée de vie de son recentBlockhash.

Pour commencer à utiliser des transactions durables, vous devez soumettre une transaction qui appelle des instructions afin de créer un compte « nonce » spécial on-chain et d’y stocker un « blockhash durable ». Le compte Nonce stocke la valeur du nonce. Tant que le compte de nonce n’a pas été utilisé, vous pouvez créer une transaction durable en respectant ces deux règles :

  • La liste d’instructions doit commencer par une instruction système « advance nonce », qui charge votre compte de nonce on-chain.
  • Le blockhash de la transaction doit être identique au blockhash durable stocké dans le compte de nonce on-chain.

Découvrez comment mettre en œuvre des nonces durables avec la CLI et Web3.js en consultant cet article.

L’approche de Helius pour soumettre des transactions

La requête sendTransaction est automatiquement acheminée vers notre nœud RPC le plus proche. Si maxRetries n’est pas indiqué, la transaction est réessayée toutes les deux secondes jusqu’à l’expiration du blockhash. Nous recommandons de définir maxRetries sur 0 et de rediffuser vous-même la transaction toutes les deux secondes jusqu’à sa confirmation. 

Pour faire face à la congestion actuelle du réseau, nous travaillons sans relâche afin d’améliorer le taux d’aboutissement des transactions de nos utilisateurs. Nous avons réduit la limite de débit de la requête sendTransaction. Cette mesure permet de gérer la congestion et d’éviter le spam auprès du validateur. Consultez les limites ici.

Nous acheminons également le trafic de haute qualité des offres payantes via des connexions stakées. Nous utilisons notre validateur pour ces connexions. Le trafic est considéré comme étant de haute qualité si le total des frais de priorité atteint au moins 10 000 lamports, soit la médiane du cluster.

Nous exigeons des frais totaux supérieurs à 10 000 lamports afin de fournir un trafic de haute qualité aux validateurs. Ceux-ci ont commencé à limiter le débit, voire à bloquer entièrement, les sources de trafic qui envoient des transactions à faibles frais.

L’utilisation de connexions stakées peut considérablement améliorer le taux d’aboutissement de vos transactions. Consultez cet article pour en savoir plus sur la définition des frais de priorité lors de la création d’une transaction.

Nos recommandations

Utilisateurs débutants et intermédiaires

Nous vous recommandons de définir skipPreflight sur false. Les vérifications préalables comprennent la vérification des signatures de la transaction et sa simulation sur le slot bancaire défini par le niveau d’engagement préalable. Si la vérification préalable échoue, une erreur est renvoyée. Sans cette vérification, vos transactions peuvent être abandonnées en raison d’une mauvaise configuration.

Utilisateurs avancés

Si vous êtes un utilisateur avancé et avez besoin de la latence la plus faible possible, définissez skipPreflight sur true. Il vous incombe toutefois de vous assurer que la transaction est correctement configurée. 

Conclusion

Pour faire aboutir des transactions sur le réseau Solana pendant les périodes de congestion, vous devez bien comprendre l’architecture du réseau et ses mécanismes de traitement. La maîtrise des concepts clés peut considérablement améliorer les performances : le rôle du blockhash dans l’unicité et la validité temporelle des transactions, le processus de soumission via des serveurs RPC ou des clients TPU, ainsi que l’importance de définir correctement les paramètres tels que skipPreflight, preflightCommitment et maxRetries. La mise en œuvre d’un mécanisme personnalisé de nouvelle tentative et l’utilisation de connexions stakées peuvent également augmenter le taux de réussite.

Il est en outre essentiel de connaître les limites actuelles du réseau et les efforts continus d’Anza pour les résoudre, comme l’illustre la prochaine version v1.18 du client. À mesure que le réseau évolue et passe à l’échelle, vous devrez rester informé et vous adapter pour interagir efficacement avec lui. 

Si vous avez besoin d’aide ou d’assistance, contactez-nous sur Discord. Saisissez votre adresse e-mail ci-dessous pour ne manquer aucune nouveauté sur Solana. Vous souhaitez aller plus loin ? Découvrez les derniers articles du blog Helius et poursuivez dès aujourd’hui votre parcours sur Solana.

Ressources

Abonnez-vous à Helius

Suivez les dernières actualités du développement sur Solana et recevez une notification à chaque publication

Image agrandie