NOUVEAU : Helius acquiert Light Protocol
Frais de priorité : comprendre le fonctionnement des frais de transaction de Solana
Blog/Fondamentaux

Frais de priorité : comprendre le fonctionnement des frais de transaction de Solana

Developer Experience Engineer0xIchigo sur X0xIchigo sur LinkedIn0xIchigo sur GitHub
10 min de lecture

De quoi parle cet article ?

Solana est rapide. Pourtant, même sur la blockchain la plus rapide disponible, les utilisateurs souhaitent optimiser le traitement des transactions importantes. Les frais de priorité permettent de placer la transaction d’un utilisateur en tête de la file d’attente d’exécution. Il s’agit de frais supplémentaires et facultatifs qu’un utilisateur peut ajouter à sa transaction.

Cet article présente brièvement les subtilités du traitement des transactions sur Solana. Il aborde les transactions, leur cycle de vie et le fonctionnement des frais de transaction. Il examine ensuite les frais de priorité, leur implémentation par programmation et les bonnes pratiques.

Les transactions et leur cycle de vie

Les transactions servent à invoquer des programmes Solana et à appliquer des changements d’état. Ce sont des ensembles d’instructions (c’est-à-dire des directives pour une seule invocation de programme) qui indiquent au validateur les actions à effectuer, les comptes concernés et si les autorisations nécessaires sont présentes.

Le cycle de vie général d’une transaction sur Solana est le suivant :

  • L’utilisateur a un objectif précis pour l’action qu’il souhaite effectuer. Par exemple, Alice veut envoyer 10 SOL à Bob
  • L’utilisateur génère une transaction pour l’action souhaitée. Par exemple, Alice crée une transaction comprenant une instruction pour transférer 10 SOL de son compte vers celui de Bob. Alice inclut également un blockhash récent et signe la transaction avec sa clé privée
  • L’utilisateur envoie la transaction au réseau. Il reçoit ensuite des informations indiquant si la transaction a bien été ajoutée. Par exemple, Alice envoie la transaction avec le niveau d’engagement confirmed. Une fois la transaction confirmée, elle reçoit une signature de transaction. Alice peut utiliser cette signature dans un explorateur de blocs, tel qu’Orb, pour vérifier qu’elle a bien envoyé 10 SOL à Bob. Son compte a été débité de 10 SOL, tandis que celui de Bob a été crédité de 10 SOL

Lorsque les utilisateurs envoient une transaction signée au réseau, ils passent par un fournisseur RPC tel que Helius. Les RPC de Helius reçoivent la transaction et consultent le calendrier des leaders actuel. Sur Solana, seuls certains validateurs sont chargés d’ajouter des entrées au registre à des moments précis. Le leader est chargé de produire un bloc pour son slot actuel et se voit attribuer quatre slots consécutifs. La transaction signée est envoyée au leader actuel et aux deux leaders suivants.

Le leader actuel valide la transaction signée et effectue d’autres étapes de prétraitement avant de planifier son exécution. Contrairement à d’autres L1 comme Ethereum, Solana ne dispose d’aucune file d’attente globale pour les transactions. La plupart des validateurs utilisent l’implémentation du planificateur fournie par Solana Labs. Cependant, les validateurs exécutant le client de validation Jito utilisent une pseudo-mempool (c’est-à-dire MempoolStream) pour ordonner les transactions. Le planificateur par défaut est multithread, chaque thread conservant une file de transactions en attente d’exécution. Les transactions sont ordonnées dans les blocs en combinant le principe du premier entré, premier sorti (FIFO) et les frais de priorité. Il est important de noter que cet ordre est intrinsèquement non déterministe, car les transactions sont affectées aux threads d’exécution de manière plus ou moins aléatoire.

Lorsqu’une transaction est exécutée, elle est propagée via Turbine, et ses frais sont payés en conséquence.

Fonctionnement des frais de transaction sur Solana

Les frais de transaction sont de petits montants payés pour traiter les transactions sur Solana. Le leader actuel traite les transactions envoyées sur le réseau afin de produire des entrées dans le registre. Lorsque la transaction est confirmée comme faisant partie de l’état, des frais sont payés pour soutenir le modèle économique de Solana. Les frais de transaction bénéficient fondamentalement à Solana. Ils permettent de :

  • Rémunérer les validateurs
  • Réduire l’occupation du réseau en imposant un coût en temps réel aux transactions
  • Assurer la stabilité économique à long terme du réseau grâce aux frais minimaux prélevés par le protocole

À court terme, Solana s’appuie sur des récompenses inflationnistes fondées sur le protocole pour sécuriser le réseau. Le réseau applique un taux d’inflation global planifié afin de récompenser les validateurs. À long terme, Solana compte sur les frais de transaction pour maintenir sa sécurité. Une part fixe (initialement définie à 50 %) de chaque frais de transaction est brûlée, le reste étant envoyé au leader actuel. Solana brûle ces frais afin de renforcer la valeur du SOL tout en dissuadant les validateurs malveillants de censurer les transactions.

Les frais de transaction sont calculés à partir de frais de base définis statiquement pour chaque signature et des ressources de calcul utilisées pendant la transaction, mesurées en unités de calcul (CU). Ces frais de base peuvent varier entre 50 % et 1 000 % du nombre cible de lamports par signature. La valeur cible par défaut pour chaque signature est actuellement fixée à 10 000. Chaque transaction reçoit un budget maximal de CU appelé Compute Budget. Si ce budget est dépassé, l’environnement d’exécution interrompt la transaction et renvoie une erreur. Le budget maximal par transaction est de 1,4 million de CU, et la limite d’espace de bloc est de 48 millions de CU.

Que sont les frais de priorité ?

En raison de ces limites, les transactions nécessitant beaucoup de calculs peuvent occuper tout l’espace de bloc et retarder d’autres transactions. Solana a introduit des frais facultatifs permettant à une transaction d’être prioritaire sur les autres transactions dans la file d’attente du leader : les frais de priorité. Le paiement de ces frais donne effectivement la priorité à votre transaction, ce qui accélère son exécution. Cela s’avère utile pour les transactions urgentes ou de grande valeur. La priorité tarifaire d’une transaction est déterminée par le nombre d’unités de calcul qu’elle demande. Plus une transaction demande d’unités de calcul, plus les frais qu’elle devra payer pour conserver sa priorité dans la file d’attente seront élevés. Facturer davantage pour un plus grand nombre d’unités de calcul empêche le spam de transactions nécessitant beaucoup de calculs.

Les frais de priorité correspondent au produit du budget de calcul d’une transaction et du prix de son unité de calcul, mesuré en micro-lamports : priorityFees = computeBudget * computeUnitPrice

Budget de calcul

Le computeBudget définit le nombre maximal d’unités de calcul qu’une transaction peut consommer, les coûts associés aux différentes opérations qu’elle peut effectuer et les limites opérationnelles qu’elle doit respecter. Les opérations suivantes entraînent un coût de calcul :

  • Exécution d’instructions SBF
  • Transmission de données entre les programmes
  • Appel d’appels système (par exemple, journalisation, création d’une adresse de programme, CPI)

Pour les invocations interprogrammes (CPI), le programme appelé s’exécute dans le budget de calcul du programme appelant (c’est-à-dire le programme parent). Si le programme appelé épuise tout le budget de calcul restant ou dépasse une limite définie, l’ensemble de la chaîne d’appels de programmes échoue. Cela inclut l’exécution de la transaction initiale qui a lancé le processus.

Le budget de calcul actuel est disponible ici.

Comment implémenter les frais de priorité par programmation

Les frais de priorité d’une transaction sont définis au moyen d’une instruction SetComputeUnitPricehttps://github.com/solana-labs/solana/blob/2971e84ec87815adb1e4def95cbcd8d0d96845f5/sdk/src/compute_budget.rs#L59et d’une instruction SetComputeUnitLimit facultative. Si aucune instruction SetComputeUnitPrice n’est fournie, la transaction adopte par défaut la priorité la plus faible puisqu’aucuns frais supplémentaires ne sont ajoutés. Si aucune instruction SetComputeUnitLimit n’est fournie, la limite correspond au produit du nombre d’instructions de la transaction et de la limite d’unités de calcul par défaut. L’environnement d’exécution utilise le prix et la limite des unités de calcul pour calculer les frais de priorité qui servent à établir la priorité de la transaction concernée.

Pour ajouter des frais de priorité par programmation, nous devons ajouter ces instructions à la transaction souhaitée. En JavaScript, cela donne ceci :

Code
import {
  Keypair,
  Connection,
  PublicKey,
  Transaction,
  SystemProgram,
  LAMPORTS_PER_SOL,
  sendAndConfirmTransaction,
  ComputeBudgetProgram,
} from "@solana/web3.js";

async function main() {
  // Initialize an RPC client
  const clusterUrl = "http://127.0.0.1:8899";
  const connection = new Connection(clusterUrl, "confirmed");

  // Initialize new sender and receiver keypairs
  const fromKeypair = Keypair.generate();
  const toPubkey = new PublicKey(Keypair.generate().publicKey);

  // Airdrop SOL to the from_keypair
  const airdropAmount = 100 * LAMPORTS_PER_SOL;
  try {
    const signature = await connection.requestAirdrop(
      fromKeypair.publicKey,
      airdropAmount
    );
    console.log("Airdrop requested. Signature:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("Failed to request airdrop:", e);
    return;
  }

  // Check if airdrop was successful
  const balance = await connection.getBalance(fromKeypair.publicKey);
  if (balance < airdropAmount) {
    console.error(
      "Airdrop was not successful. The current balance is insufficient"
    );
    return;
  }

  // Airdrop SOL to the toPubkey
  const airdropAmountTo = 100 * LAMPORTS_PER_SOL; // 1 SOL in lamports
  try {
    const signature = await connection.requestAirdrop(toPubkey, airdropAmount);
    console.log("Airdrop requested. Signature:", signature);
    await connection.confirmTransaction({
      signature,
      confirmation: "confirmed",
    });
  } catch (e) {
    console.error("Failed to request airdrop:", e);
    return;
  }

  // Check if airdrop was successful
  const balanceTo = await connection.getBalance(toPubkey);
  if (balance < airdropAmount) {
    console.error(
      "Airdrop was not successful. The current balance is insufficient"
    );
    return;
  }

  console.log(`Account balance: ${balance / LAMPORTS_PER_SOL} SOL`);

  // Create the priority fee instructions
  const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 1,
  });

  const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200_000,
  });

  // Create the transfer instruction
  const transferIx = SystemProgram.transfer({
    fromPubkey: fromKeypair.publicKey,
    toPubkey,
    lamports: 100_000,
  });

  // Create the transaction with priority fees
  const transaction = new Transaction().add(
    computePriceIx,
    computeLimitIx,
    transferIx
  );

  // Fetch the recent blockhash and sign the transaction
  transaction.recentBlockhash = (
    await connection.getLatestBlockhash()
  ).blockhash;
  transaction.sign(fromKeypair);

  // Send the transaction
  try {
    const txid = await sendAndConfirmTransaction(connection, transaction, [
      fromKeypair,
    ]);
    console.log("Transaction sent successfully with signature", txid);
  } catch (e) {
    console.error("Failed to send transaction:", e);
  }
}

main();

Dans cet extrait, nous :

  • Configurons un environnement de test avec Localhost
  • Créons deux nouveaux portefeuilles (c’est-à-dire fromKeypair et toPubkey)
  • Distribuons 100 SOL par airdrop aux deux portefeuilles
  • Vérifions que les airdrops ont réussi
  • Créons les instructions relatives aux frais de priorité (c’est-à-dire computePriceIx et computeLimitIx)
  • Créons une instruction de transfert envoyant 100 000 lamports de fromKeypair vers toPubkey
  • Créons une nouvelle transaction et y ajoutons toutes les instructions
  • Joignons le blockhash le plus récent à la transaction et la signons
  • Envoyons la transaction et confirmons qu’elle a bien été transmise

C’est tout : les frais de priorité ne sont que des instructions ajoutées à une transaction pour payer une exécution plus rapide. La majeure partie de cet extrait configure notre environnement de développement et deux portefeuilles afin de transférer des SOL entre eux. Ce qui nous intéresse, c’est de créer les instructions permettant d’ajouter des frais de priorité et de les joindre à une transaction :

Code
// Other code

const computePriceIx = ComputeBudgetProgram.setComputeUnitPrice({
	microLamports: 1,
});

const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
	units: 200_000,
});

// Other code

const transaction = new Transaction().add(computePriceIx, computeLimitIx, transferIx);

// Rest of the code

Bonnes pratiques

L’ordre de ces instructions est important, car elles sont exécutées séquentiellement. Si, par exemple, une transaction dépasse la limite de calcul par défaut (c’est-à-dire 200 000 CU) avant que la limite de calcul ne soit augmentée, elle échouera. Imaginons la transaction suivante :

  • L’instruction 1 utilise 100 000 CU
  • L’instruction 2 utilise 150 000 CU
  • L’instruction 3 augmente la limite de calcul

Cette transaction échouera si les instructions 2 et 3 ne sont pas interverties. Il est donc recommandé d’ajouter l’instruction définissant la limite de calcul avant les autres instructions de votre transaction. N’oubliez pas : vous n’avez pas besoin d’utiliser l’instruction SetComputeLimit pour ajouter des frais de priorité à votre transaction ; elle est entièrement facultative. L’emplacement de l’instruction SetComputePrice n’a aucune importance.

Il est recommandé d’utiliser la méthode RPC getRecentPrioritizationFees afin d’obtenir la liste des frais de priorité récemment payés. Ces données permettent d’estimer des frais de priorité adaptés aux transactions afin de garantir leur traitement par le cluster tout en réduisant les frais payés. Helius propose également une nouvelle API de frais de priorité, que nous aborderons dans la section suivante.

Les transactions doivent également demander le nombre minimal d’unités de calcul nécessaires à leur exécution afin de réduire ces frais. Notez que les coûts ne sont pas ajustés lorsque le nombre d’unités de calcul demandées dépasse le nombre total d’unités utilisées par une transaction.

Certains fournisseurs de portefeuilles, tels que Phantom, reconnaissent que les dApps peuvent définir les frais de priorité des transactions. Ils déconseillent toutefois de le faire, car cela crée souvent une complexité inutile pour les utilisateurs finaux. Ils encouragent plutôt les développeurs de dApps à laisser Phantom appliquer les frais de priorité pour le compte de l’utilisateur. Solfare, par exemple, traite le problème en détectant automatiquement si Solana est soumis à une forte charge et augmente légèrement les frais afin de donner la priorité à votre transaction sur les autres.

API Helius Priority Fee

Le calcul des frais de priorité avec la méthode RPC getRecentPrioritizationFees semble intuitivement adapté à un calcul des frais par slot. Cependant, cela peut s’avérer difficile en raison de l’évolution constante de l’état du réseau et de la nature de la réponse de getRecentPrioritizationFees' (c’est-à-dire une liste de valeurs pour les 150 derniers blocs, qui sert uniquement à évaluer la valeur minimale à définir pour les frais).

La Helius Priority Fee API introduit une nouvelle méthode, getPriorityFeeEstimate, qui simplifie la réponse en une seule valeur tenant compte des marchés de frais mondiaux et locaux. Cette méthode utilise un ensemble prédéfini de centiles pour déterminer l’estimation. Ces centiles, ou niveaux, vont de NONE (0e centile) à UNSAFE_MAX (100e centile, marqué comme dangereux afin d’éviter que les utilisateurs n’épuisent accidentellement leurs fonds). Les utilisateurs peuvent également choisir de recevoir tous les niveaux de priorité et d’ajuster, via lookbackSlots, la plage utilisée pour ce calcul. Ainsi, comme avec getRecentPrioritizationFees, les utilisateurs peuvent prendre en compte un certain nombre de slots antérieurs dans le calcul de l’estimation.

Par exemple, nous pouvons calculer tous les niveaux de frais de priorité pour le programme Jupiter v6 avec le script suivant :

Code
const url = `https://mainnet.helius-rpc.com/?api-key=`;

const getRecentPrioritizationFees = async () => {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "getPriorityFeeEstimate",
      params: [{
        "accountKeys": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
        "options": {
            "includeAllPriorityFeeLevels": true,
        }
      }]
    }),
  });
  const data = await response.json();
  console.log("Fee: ", data);
};

getRecentPrioritizationFees();

Cet endpoint est en cours de développement actif. Pour en savoir plus, consultez la documentation Helius.

Conclusion

L’étude des transactions Solana révèle un système sophistiqué qui équilibre l’efficacité du réseau et les incitations économiques. Comprendre le fonctionnement des transactions, ainsi que leurs frais et leurs frais de priorité, permet aux développeurs et aux utilisateurs de prendre des décisions plus éclairées afin d’optimiser leurs interactions sur Solana. La possibilité d’implémenter des frais de priorité par programmation ouvre de nouvelles perspectives pour les transactions urgentes ou de grande valeur. Les opérations sur Solana gagnent ainsi en flexibilité et en efficacité.

Si vous avez lu jusqu’ici, merci, anon ! Saisissez votre adresse e-mail ci-dessous pour ne manquer aucune actualité de Solana. Prêt à aller plus loin ? Rejoignez notre Discord pour commencer dès aujourd’hui à bâtir l’avenir sur la blockchain la plus performante.

Ressources supplémentaires / Pour aller plus loin

Abonnez-vous à Helius

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

Image agrandie