
Versionnement des transactions Solana : legacy, v0 et v1
Sommaire
- Bref historique des formats de transaction de Solana
- Transactions legacy
- Transaction v0
- Ajout a posteriori des frais de priorité
- Transactions de plus grande taille
- Spécifications et comparaison des formats
- Spécification des transactions legacy
- Spécification des transactions v0
- Spécification des transactions v1
- Exemple de transaction
- Transactions legacy et v0
- Configuration des ressources
- Transaction v1
- Vérification des signatures
- Amélioration de l'analyse des instructions
- Masque de configuration de la transaction
- Conclusion
- Ressources complémentaires
Bref historique des formats de transaction de Solana
Solana propose trois formats de transaction : legacy, v0 et v1. Dans les grandes lignes, ils remplissent tous le même objectif : regrouper les signatures, les comptes et les instructions que les validateurs doivent exécuter sur les programmes en chaîne.
Ce qui a évolué au fil du temps, c'est le format de transmission, c'est-à-dire la manière dont ces éléments d'information sont sérialisés et transmis sur le réseau.
Chaque nouveau format a été introduit pour résoudre les limites apparues à mesure que les applications Solana se complexifiaient, tout en préservant la rétrocompatibilité afin que les anciens formats continuent de fonctionner.
Les trois formats coexistent sur le réseau, et les développeurs peuvent utiliser celui qui convient le mieux à la transaction qu'ils créent.
Transactions legacy
Le format d'origine est appelé legacy, car il précède le versionnement des transactions et ne contient aucun identifiant de version.
Sa taille maximale de 1 232 octets remonte à la conception réseau initiale de Solana : les transactions étaient dimensionnées pour tenir dans la MTU minimale IPv6 de 1 280 octets, soit 1 232 octets après déduction de la surcharge réseau.
Cette approche fonctionnait bien pour les petites transactions, mais l'espace est devenu de plus en plus limité à mesure que les applications se développaient. Chaque adresse de compte directement incluse dans une transaction occupe 32 octets, tandis que chaque signature Ed25519 en occupe 64 supplémentaires.
En pratique, les transactions utilisant de nombreux comptes pouvaient donc atteindre la limite d'octets bien avant la limite de verrouillage des comptes imposée par l'environnement d'exécution.
Transaction v0
La première tentative pour réduire cette contrainte a été la transaction v0, mise en service sur mainnet-beta en octobre 2022. Au lieu d'augmenter la limite de 1 232 octets, v0 a introduit les Address Lookup Tables (ALT).
Une ALT stocke les adresses de comptes en chaîne, ce qui permet à une transaction v0 de les référencer à l'aide d'indices d'un octet plutôt que de répéter chaque clé publique de 32 octets. Les transactions peuvent ainsi atteindre la limite de 64 comptes de l'environnement d'exécution tout en respectant la contrainte existante de taille des paquets.
v0 constituait une extension progressive du format legacy : elle ajoutait le préfixe de version 0x80 et une section de recherche d'adresses, tout en conservant le même encodage de base pour les messages et les instructions.
L'introduction du versionnement a également établi un cadre dans lequel les nouveaux formats de transaction pouvaient coexister avec les transactions legacy au lieu de les remplacer.
Les ALT ont résolu le problème immédiat de la taille des comptes, mais ont introduit leurs propres difficultés. Les applications doivent créer et maintenir des tables de recherche en chaîne, les validateurs doivent résoudre leurs entrées avant l'exécution, et les services RPC ainsi que les indexeurs ont besoin des adresses résolues pour reconstituer la transaction complète.
Le décodage historique devient également beaucoup plus difficile si la table de recherche référencée par une transaction brute a depuis été fermée. v0 compressait donc efficacement la liste des comptes, mais en introduisant une dépendance externe en chaîne dans la représentation de transmission de la transaction.
Les ALT ont été largement adoptées par les développeurs Solana. Une étude menée peu avant l'introduction des transactions v1 a révélé qu'environ 62 % des transactions v0 référencent au moins une ALT.
Ajout a posteriori des frais de priorité
Les frais de priorité ont également été introduits sur Solana en 2022, avant l'activation des transactions v0 sur le mainnet, même si le format v0 lui-même était déjà conçu à ce stade. Au lieu de repenser l'un ou l'autre des formats de transaction, Solana a ajouté la configuration des frais par l'intermédiaire du Compute Budget Program existant. Les transactions pouvaient inclure des instructions telles que SetComputeUnitLimit et SetComputeUnitPrice, permettant aux utilisateurs de demander un budget de calcul et d'ajouter des frais supplémentaires pour obtenir une priorité de planification. Cette solution fonctionnait aussi bien pour les transactions legacy que v0, sans nécessiter de mécanisme de frais distinct pour chaque format.
Il s'agissait d'une solution pragmatique et rétrocompatible, mais pas particulièrement élégante. Les frais de priorité et les limites de calcul sont en réalité des métadonnées propres à la transaction. Or, les formats legacy et v0 ne disposaient d'aucun champ dédié. Ils ont donc été ajoutés a posteriori au flux d'instructions sous forme d'appels de programme. Le Compute Budget Program doit ainsi être référencé par la transaction, la configuration consomme des octets d'instruction et les validateurs doivent examiner ces instructions lors de l'ingestion de la transaction pour récupérer les paramètres de frais et de ressources.
v0 n'était donc pas une refonte globale du format de transaction de Solana. Son principal objectif était de résoudre le goulot d'étranglement lié aux adresses de comptes grâce aux Address Lookup Tables, tout en conservant l'essentiel de la structure des messages legacy. Comme les instructions Compute Budget offraient déjà un moyen compatible de prendre en charge les frais de priorité dans les deux formats, il y avait peu de raisons d'élargir davantage le périmètre de v0.
Transactions de plus grande taille
Au fil du temps, Solana a fait passer l'ingestion des transactions de UDP à QUIC, dont les flux ne sont plus limités à une seule charge utile de la taille d'une MTU. Le plafond initial de 1 232 octets n'était donc plus une exigence de transport. Deux SIMD ont été proposés en 2025 pour introduire un nouveau format v1. SIMD-0296 a porté la taille maximale d'une transaction v1 à 4 096 octets, soit environ 3,3 fois la limite précédente, tandis que SIMD-0385 a défini un format de transmission entièrement nouveau, conçu autour de cet espace supplémentaire et d'une ingestion plus efficace par les validateurs.
Cet espace supplémentaire permet à v1 d'éliminer entièrement les ALT. La limite de 64 comptes avec des adresses de 32 octets nécessite au maximum 2 048 octets. La liste complète des comptes peut donc simplement être intégrée directement à la transaction.
L'analyse de Solana Foundation indique que l'intégration directe de toutes les adresses a un impact modéré lors de la migration de la plupart des transactions v0 existantes : 50 % augmentent de moins de 420 octets et 90 % de moins de 1 400 octets. Il reste donc une marge importante dans la limite de 4 096 octets de v1, même pour les transactions qui utilisent beaucoup les ALT.
Plus important encore, v1 n'est pas simplement une transaction v0 plus volumineuse. Elle réorganise considérablement le format de transmission : les signatures passent à la fin, la configuration des ressources quitte les instructions Compute Budget pour rejoindre une section de configuration de la transaction, et les charges utiles d'instruction de longueur variable sont séparées des en-têtes de largeur fixe. Ces changements rendent l'analyse et la priorisation des transactions moins coûteuses et plus simples pour les validateurs.
La limite supérieure de 4 096 octets ouvre également la voie à des charges de travail pour lesquelles la seule compression des adresses n'aurait jamais suffi. Les preuves à divulgation nulle de connaissance, les multisignatures imbriquées, les signatures Winternitz non tronquées, les signatures BLS et d'autres opérations cryptographiques peuvent nécessiter des centaines ou des milliers d'octets de données d'instruction. Les soldes confidentiels Token-2022, par exemple, peuvent désormais être construits sous la forme d'une seule transaction v1 atomique plutôt que d'être répartis entre plusieurs transactions.
Il convient de noter que le format v1 initial n'augmente pas les limites de calcul, de signatures, de nombre d'instructions ou de 64 comptes de Solana. Il supprime principalement le goulot d'étranglement lié à la taille et repense la représentation de la transaction sur le réseau.
SIMD-0596, proposé par Anza, porterait de 64 à 96 la limite de verrouillage des comptes de v1, en couvrant tous les comptes référencés par la transaction, y compris les signataires, les ID de programme ainsi que les comptes accessibles en écriture et en lecture seule. Cette proposition est toujours en cours d'examen au moment de la rédaction. Les transactions legacy et v0 resteraient limitées à 64.
Spécifications et comparaison des formats
| Limite | Legacy | v0 | v1 |
| Taille maximale | 1 232 octets | 1 232 octets | 4 096 octets |
| Adresses de comptes | ~32, selon la taille | 64, via des tables de recherche | 64, intégrées |
| Tables de recherche d'adresses | Non prises en charge | Prises en charge | Non prises en charge |
| Adresses en double | autorisées | autorisées | rejetées |
| Préfixe | Aucun | 0x80 | 0x81 |
| Frais de priorité | Instruction SetComputeUnitPrice, micro-lamports par CU | Instruction SetComputeUnitPrice, micro-lamports par CU | config.priorityFeeLamports, nombre total de lamports |
| Limite d'unités de calcul | Instruction SetComputeUnitLimit | Instruction SetComputeUnitLimit | config.computeUnitLimit |
| Taille du tas | Instruction RequestHeapFrame | Instruction RequestHeapFrame | config.heapSize |
| Plafond des comptes chargés | Instruction SetLoadedAccountsDataSizeLimit | Instruction SetLoadedAccountsDataSizeLimit | config.loadedAccountsDataSizeLimit |
Spécification des transactions legacy
Une transaction legacy sérialisée commence par un nombre de signatures compact-u16, suivi des signatures Ed25519 de 64 octets.
Vient ensuite le message, qui contient l'en-tête de trois octets, la liste des comptes préfixée par une longueur compacte, le blockhash récent de 32 octets et le tableau d'instructions compilées préfixé par une longueur compacte.
Les transactions legacy n'ont pas de préfixe de version.
NumSignatures (compact-u16)
Signatures [[u8; 64]]
-- Length = NumSignatures
MessageHeader (u8, u8, u8)
-- (NumRequiredSignatures,
NumReadonlySignedAccounts,
NumReadonlyUnsignedAccounts)
NumAccountKeys (compact-u16)
AccountKeys [[u8; 32]]
-- Length = NumAccountKeys
RecentBlockhash [u8; 32]
NumInstructions (compact-u16)
Instructions [CompiledInstruction]
-- Length = NumInstructions
CompiledInstruction:
ProgramIdIndex (u8)
NumInstructionAccounts (compact-u16)
InstructionAccountIndexes [u8]
-- Length = NumInstructionAccounts
InstructionDataLength (compact-u16)
InstructionData [u8]
-- Length = InstructionDataLengthUne caractéristique essentielle est que chaque CompiledInstruction est intégralement sérialisé avant le début de l'instruction suivante.
Les tableaux d'indices de comptes et de données sont de longueur variable, avec des préfixes compact-u16 qui définissent leur taille.
Spécification des transactions v0
La transaction v0 conserve presque intégralement le format de transmission legacy. Elle commence toujours par le nombre de signatures et les signatures, mais le message débute par le préfixe de version 0x80.
Le message utilise ensuite le même en-tête de trois octets, la même liste de comptes statiques, le même blockhash et le même format d'instructions compilées que le format legacy, avant d'ajouter une section Address Lookup Table.
C'est ce qui permet aux transactions v0 de charger des comptes supplémentaires au moyen d'ALT tout en respectant la limite de 1 232 octets par transaction.
NumSignatures (compact-u16)
Signatures [[u8; 64]]
-- Length = NumSignatures
VersionByte (u8)
-- 0x80
MessageHeader (u8, u8, u8)
NumStaticAccountKeys (compact-u16)
StaticAccountKeys [[u8; 32]]
-- Length = NumStaticAccountKeys
RecentBlockhash [u8; 32]
NumInstructions (compact-u16)
Instructions [CompiledInstruction]
-- Same encoding as legacy
NumAddressTableLookups (compact-u16)
AddressTableLookups [MessageAddressTableLookup]
-- Length = NumAddressTableLookups
MessageAddressTableLookup:
AccountKey [u8; 32]
-- Address of the lookup table account
NumWritableIndexes (compact-u16)
WritableIndexes [u8]
-- Indices into the lookup table
NumReadonlyIndexes (compact-u16)
ReadonlyIndexes [u8]
-- Indices into the lookup tableLa distinction entre les clés de comptes statiques et les adresses chargées est importante. Seules les clés statiques sont directement sérialisées dans le message. Les adresses référencées par l'intermédiaire d'ALT sont résolues à l'exécution et ajoutées à la liste effective des comptes.
Pour une transaction v0 qui utilise des ALT, le validateur doit d'abord charger chaque table de recherche référencée depuis Bank et AccountsDB, vérifier que le compte est une Address Lookup Table valide, désérialiser son état, convertir les indices demandés en adresses complètes de 32 octets, puis fusionner ces adresses avec les clés de comptes statiques de la transaction. Ce n'est qu'après cette étape de résolution que le validateur dispose de la liste complète des comptes nécessaire pour déterminer les verrouillages en lecture et en écriture ainsi que les conflits avec d'autres transactions.
Spécification des transactions v1
Une transaction v1 sérialisée commence par l'octet de version 0x81. Il est suivi d'un en-tête de message de trois octets de style legacy, d'un masque de configuration de transaction de 32 bits, d'un spécificateur de durée de validité de 32 octets, du nombre d'instructions et d'adresses, du tableau complet d'adresses de 32 octets, des valeurs de configuration, des en-têtes d'instruction de taille fixe, des charges utiles d'instruction contiguës et, enfin, des signatures.
VersionByte (u8)
-- 0x81
LegacyHeader (u8, u8, u8)
TransactionConfigMask (u32)
-- Bitmask describing which configuration values are present
LifetimeSpecifier [u8; 32]
NumInstructions (u8)
NumAddresses (u8)
Addresses [[u8; 32]]
-- Length = NumAddresses
ConfigValues [[u8; 4]]
-- Length = popcount(TransactionConfigMask)
-- Multi-word values, such as priority fee, consume multiple entries
InstructionHeaders [(u8, u8, u16)]
-- Length = NumInstructions
-- (ProgramAccountIndex,
NumInstructionAccounts,
NumInstructionDataBytes)
InstructionPayloads [InstructionPayload]
-- Length = NumInstructions
InstructionPayload:
InstructionAccountIndexes [u8]
-- Length = NumInstructionAccounts
InstructionData [u8]
-- Length = NumInstructionDataBytes
Signatures [[u8; 64]]
-- Length = LegacyHeader.NumRequiredSignaturesV1 diffère donc des formats précédents sur plusieurs points fondamentaux. L'identifiant de version se trouve à l'octet zéro, les signatures passent à la fin et n'ont plus besoin de leur propre préfixe de longueur, le nombre de comptes et d'instructions devient une valeur u8 de largeur fixe, la configuration des ressources est directement encodée dans la transaction, et les en-têtes d'instruction de taille fixe sont séparés de leurs charges utiles de longueur variable. Contrairement aux formats legacy et v0, v1 n'utilise pas d'Address Lookup Tables.
La transaction v1 remplace les instructions de configuration du Compute Budget Program par des champs dans l'en-tête de la transaction. Le TransactionConfigMask initial peut déclarer les éléments suivants pour la transaction :
- frais de priorité totaux en lamports
- limite d'unités de calcul
- limite de taille des données des comptes chargés
- taille de tas demandée
Chaque bit défini identifie une valeur de configuration associée de quatre octets, les frais de priorité sur 64 bits occupant deux positions du masque. Le masque est conçu pour pouvoir être étendu dans de futures versions de transaction.
Exemple de transaction
Pour comparer les différents formats de transaction de Solana, prenons l'exemple utile le plus simple : le transfert de SOL d'une adresse à une autre.
Notre exemple de transaction comporte un seul signataire, c'est-à-dire l'expéditeur, et invoque une fois le System Program pour transférer 1 000 lamports au destinataire. Il référence trois adresses : celles de l'expéditeur, du destinataire et du System Program.
Transactions legacy et v0
Les transactions legacy et v0 utilisent presque la même structure de premier niveau. Toutes deux commencent par le nombre de signatures, immédiatement suivi des signatures elles-mêmes. Le message de la transaction vient ensuite. Pour un transfert avec un seul signataire, le premier octet est 01, ce qui indique une seule signature, suivi de la signature Ed25519 de 64 octets de l'expéditeur.
Le message contient un en-tête de trois octets, les adresses des comptes, un blockhash récent servant de spécificateur de durée de validité et les instructions compilées.
L'expéditeur correspond à l'adresse 0, le destinataire à l'adresse 1 et le System Program à l'adresse 2. L'instruction de transfert référence le programme à l'indice 2 et lui transmet les indices de comptes 00 01.
Les formats legacy et v0 ne diffèrent que légèrement :
- Un message legacy commence directement par son en-tête de trois octets, tandis qu'un message v0 débute par l'octet de version
0x80, suivi de l'en-tête. - v0 ajoute également une section Address Lookup Table après les instructions. Notre exemple simple n'utilise aucune table de recherche. Cette section se compose donc d'un seul octet
00indiquant zéro recherche.
Dans notre exemple, la transaction de transfert de SOL occupe ainsi 215 octets au format legacy et 217 octets au format v0. v0 ajoute 1 octet pour le préfixe de version et 1 octet pour le tableau vide des tables de recherche. Le reste de la transaction est identique.
L'expéditeur signe uniquement le message. Dans notre exemple legacy, la signature couvre donc les octets 65 à 214. Pour v0, elle couvre les octets 65 à 216, en commençant par le préfixe de version de message 0x80.
Le tableau ci-dessous détaille la structure en octets de notre exemple de transfert de SOL entre deux adresses, au format de transaction v0.
| Décalage | Taille | Champ | Exemple de valeur | Description |
| 0 | 1 o | Nombre de signatures | 01 | Une signature ; compact-u16 |
| 1–64 | 64 o | Signature 0 | <Ed25519 signature> | Signature de l'expéditeur |
| 65 | 1 o | Préfixe de version | 80 | Message versionné, version 0 |
| 66–68 | 3 o | En-tête du message | 01 00 01 | 1 signataire requis, 0 compte signé en lecture seule, 1 compte non signé en lecture seule |
| 69 | 1 o | Nombre de comptes statiques | 03 | Trois comptes intégrés |
| 70–101 | 32 o | Compte 0 | <sender pubkey> | Expéditeur/payeur des frais ; signataire accessible en écriture |
| 102–133 | 32 o | Compte 1 | <recipient pubkey> | Destinataire ; non signé et accessible en écriture |
| 134–165 | 32 o | Compte 2 | 11111111111111111111111111111111 | System Program ; non signé et en lecture seule |
| 166–197 | 32 o | Blockhash récent | <recent blockhash> | Durée de validité de la transaction |
| 198 | 1 o | Nombre d'instructions | 01 | Une instruction |
| 199 | 1 o | Indice de l'ID de programme | 02 | System Program |
| 200 | 1 o | Nombre de comptes de l'instruction | 02 | Deux comptes d'instruction |
| 201–202 | 2 o | Indices de comptes | 00 01 | Expéditeur, destinataire |
| 203 | 1 o | Longueur des données | 0C | 12 octets |
| 204–207 | 4 o | Discriminateur de transfert | 02 00 00 00 | SystemInstruction::Transfer |
| 208–215 | 8 o | Lamports | E8 03 00 00 00 00 00 00 | 1 000 lamports |
| 216 | 1 o | Nombre de recherches dans les tables d'adresses | 00 | Aucune recherche ALT |
| Total | 217 o |
Configuration des ressources
Dans les transactions legacy et v0, la configuration des ressources est exprimée sous forme d'instructions adressées au Compute Budget Program. Les plus courantes comprennent SetComputeUnitLimit et SetComputeUnitPrice. SetLoadedAccountsDataSizeLimit et RequestHeapFrame sont également utilisées si nécessaire.
Dans notre exemple de transaction minimale, ces instructions sont absentes. L'environnement d'exécution fournit alors les valeurs par défaut.
L'absence de prix par unité de calcul signifie qu'il n'y a pas de frais de priorité, et la limite des données des comptes chargés prend par défaut la valeur maximale de l'environnement d'exécution.
Le manque d'efficacité dû au fait que les validateurs doivent parcourir la liste d'instructions à la recherche d'instructions Compute Budget lors de l'ingestion des transactions est l'une des raisons du nouveau format v1, dans lequel ces informations sont déplacées vers TransactionConfigMask et ConfigValues.
Le fait de représenter la configuration propre à la transaction sous forme d'instructions entraîne également une surcharge intrinsèque. Les formats legacy et v0 ne disposaient d'aucun champ dédié aux ressources de calcul demandées ou aux frais de priorité. Ces paramètres ont donc été ajoutés a posteriori par l'intermédiaire du Compute Budget Program : son ID de programme doit être référencé par la transaction, chaque paramètre occupe de l'espace dans la liste d'instructions, et l'invocation du programme elle-même n'effectue aucun travail applicatif tout en consommant des ressources de calcul.
v1 attribue à la place un emplacement natif à ces valeurs dans le format de transmission, ce qui évite la surcharge liée aux instructions supplémentaires et rend la configuration des ressources moins coûteuse et plus facile à examiner pour les validateurs.
Transaction v1
Au lieu de commencer par le nombre de signatures et les signatures, une transaction v1 débute directement par l'octet de version 0x81.
Viennent ensuite l'en-tête de trois octets, un nouveau TransactionConfigMask de quatre octets, le spécificateur de durée de validité, généralement un blockhash mais éventuellement un nonce, le nombre d'instructions et d'adresses, les adresses des comptes, les valeurs de configuration, les instructions et, enfin, les signatures.
| Décalage | Taille | Champ | Exemple de valeur | Description |
| 0 | 1 o | Octet de version | 81 | Transaction v1 |
| 1–3 | 3 o | En-tête legacy | 01 00 01 | 1 signature requise, 0 compte signé en lecture seule, 1 compte non signé en lecture seule |
| 4–7 | 4 o | Masque de configuration de la transaction | 0C 00 00 00 | 0x0000000C : bits 2 et 3 définis, indiquant deux valeurs de configuration de 4 octets |
| 8–39 | 32 o | Spécificateur de durée de validité | <recent blockhash> | Blockhash récent (ou nonce) |
| 40 | 1 o | Nombre d'instructions | 01 | 1 instruction System Program |
| 41 | 1 o | Nombre d'adresses | 03 | Expéditeur, destinataire, System Program |
| 42–73 | 32 o | Adresse 0 | <sender pubkey> | Expéditeur, payeur des frais, signataire accessible en écriture |
| 74–105 | 32 o | Adresse 1 | <recipient pubkey> | Destinataire, compte non signé accessible en écriture |
| 106–137 | 32 o | Adresse 2 | 11111111111111111111111111111111 | System Program, en lecture seule, non signé |
| 138–141 | 4 o | Valeur de configuration : limite d'unités de calcul | 10 27 00 00 | 10 000 CU sous forme de u32 petit-boutiste (correspond au bit 2 du masque) |
| 142–145 | 4 o | Valeur de configuration : limite de taille des données des comptes chargés | 00 00 01 00 | 65 536 octets / 64 Kio sous forme de u32 petit-boutiste (correspond au bit 3 du masque) |
| 146–149 | 4 o | Instruction : en-tête | 02 02 0C 00 | Indice de programme 2, 2 comptes, 12 octets de données |
| 150–151 | 2 o | Instruction : indices de comptes | 00 01 | Adresse 0 = expéditeur, adresse 1 = destinataire |
| 152–155 | 4 o | Instruction : discriminateur de transfert | 02 00 00 00 | SystemInstruction::Transfer |
| 156–163 | 8 o | Instruction : lamports | p. ex. E8 03 00 00 00 00 00 00 | 1 000 lamports sous forme de u64 petit-boutiste |
| 164–227 | 64 o | Signature 0 | <Ed25519 signature> | Signature de l'expéditeur sur les octets 0 à 155 |
| Total | 228 o |
Vérification des signatures
La vérification des signatures est l'une des premières opérations effectuées lorsqu'une transaction atteint un validateur. Les transactions dont les signatures sont invalides doivent être filtrées et abandonnées avant d'atteindre l'ordonnanceur. Placer les signatures après un message de longueur variable pourrait sembler les rendre plus difficiles d'accès, mais ce n'est pas le cas en pratique.
Avant d'effectuer la coûteuse vérification Ed25519, le validateur doit seulement déterminer où commence et se termine chaque partie de la transaction. Le format v1 est conçu pour rendre cette opération peu coûteuse et déterministe.
Le premier octet, 0x81, identifie immédiatement la transaction comme v1. L'en-tête qui suit contient num_required_signatures. Le validateur sait donc dès le départ combien de signatures de 64 octets il doit trouver.
L'analyseur TransactionView d'Agave parcourt ensuite la transaction vers l'avant tout en enregistrant les décalages. Il lit les champs de taille fixe et utilise NumAddresses pour passer le tableau d'adresses.
Il utilise le masque de configuration pour déterminer la taille de la section de configuration et lit chaque en-tête d'instruction de 4 octets afin de connaître la taille de la charge utile correspondante.
Une fois que l'analyseur a dépassé la dernière charge utile d'instruction, sa position actuelle est enregistrée comme décalage des signatures. À partir de cette position, Agave attend exactement num_required_signatures × 64 octets.
L'implémentation stocke un SignatureFrame contenant le nombre de signatures et le décalage en octets de la première signature dans le paquet. Le message signé de la transaction correspond à la plage d'octets allant du début du message v1 jusqu'au décalage de signature enregistré. Le validateur peut ainsi effectuer la coûteuse vérification Ed25519 sans exécuter la transaction ni charger les comptes.
v1 interdit également les octets supplémentaires après le tableau de signatures. Une fois que l'analyseur atteint les signatures, la taille des octets restants est déterminée sans ambiguïté par le nombre de signatures. SIMD-0385 exige exactement une signature de 64 octets pour chaque signataire requis et aucune donnée après la dernière signature.
Amélioration de l'analyse des instructions
La structure de la transaction v1 facilite l'analyse des instructions. Pour localiser une instruction ultérieure dans les transactions legacy et v0, un analyseur doit parcourir les instructions précédentes, décoder leur longueur et passer chaque charge utile de longueur variable.
v1 sépare les en-têtes d'instruction de largeur fixe des charges utiles de longueur variable. Chaque instruction fournit d'abord un en-tête de quatre octets contenant l'indice du compte du programme, le nombre de comptes de l'instruction et la longueur des données d'instruction. Ces en-têtes sont regroupés avant les charges utiles des indices de comptes et des données. Cela fournit dès le départ une description compacte de chaque instruction. Il devient moins coûteux de délimiter et de passer la section des instructions, et cela aide les analyseurs à déterminer le décalage du tableau final de signatures sans analyser les données d'instruction.
Masque de configuration de la transaction
L'autre ajout majeur du format v1 est le TransactionConfigMask de quatre octets.
Les transactions legacy et v0 configurent les ressources au moyen d'instructions adressées au Compute Budget Program. Par exemple, une transaction peut contenir des instructions SetComputeUnitLimit, SetComputeUnitPrice ou SetLoadedAccountsDataSizeLimit. Un validateur qui cherche à connaître les besoins en ressources ou la priorité de la transaction doit localiser et interpréter ces instructions. La transaction v1 extrait ces informations du flux d'instructions et les intègre directement au format de la transaction.
Le masque est un champ de bits petit-boutiste de 32 bits. Chaque bit défini correspond à un mot de quatre octets dans la section ConfigValues située plus loin dans la transaction :
- Bits 0 et 1 : frais de priorité totaux en lamports (encodés sous forme de
u64petit-boutiste de 8 octets) - Bit 2 : limite d'unités de calcul (un
u32de 4 octets) - Bit 3 : limite de taille des données des comptes chargés (un
u32de 4 octets) - Bit 4 : taille de tas demandée (un
u32de 4 octets)
N.B. La spécification v1 initiale n'attribue une signification qu'aux bits 0 à 4. Les autres bits ne sont actuellement pas attribués, ce qui laisse de la place pour de futurs champs de configuration propres à la transaction.
Le masque agit comme un schéma compact qui indique à l'analyseur quels champs existent et combien d'octets il doit attendre. Par exemple, notre simple transfert de SOL nécessite une limite d'unités de calcul et une limite de taille des données des comptes chargés, mais n'a pas besoin de frais de priorité ni d'une taille de tas personnalisée.
Comme ces deux bits sont définis, exactement deux valeurs de configuration de 4 octets suivent le tableau d'adresses de la transaction. Dans notre cas, elles demandent une limite de 20 000 CU et une limite de 64 Kio pour les données des comptes chargés.
Le masque indique au validateur que la première configuration de 4 octets correspond au bit 2, c'est-à-dire à la limite d'unités de calcul, et que la seconde correspond au bit 3, soit à la limite des données des comptes chargés.
Dans les transactions legacy et v0, l'absence d'une instruction définissant la limite d'unités de calcul attribue à la transaction un budget de calcul implicite. v1 n'utilise pas ces mêmes valeurs par défaut. Une limite d'unités de calcul non définie est résolue à zéro, tout comme une limite non définie de la taille des données des comptes chargés. Seul le tas conserve une valeur par défaut de 32 Kio. Une transaction v1 doit donc demander explicitement les ressources dont elle a besoin.
Le déplacement de ces paramètres vers une zone de configuration clairement définie donne aux validateurs un accès direct aux informations dont ils ont besoin lors de l'ingestion des transactions, sans avoir à les rechercher dans les instructions. Cela signifie également que les instructions du Compute Budget Program ne configurent plus les transactions v1. Si elles sont incluses, elles sont ignorées et traitées comme des instructions sans effet, tout en consommant des unités de calcul.
Cette approche permet également de gagner de l'espace. Dans les transactions legacy/v0, l'ajout d'instructions Compute Budget nécessite généralement l'adresse de 32 octets du Compute Budget Program dans la liste des comptes, ainsi que les instructions sérialisées elles-mêmes.
Conclusion
Les trois formats de transaction de Solana reflètent l'évolution du réseau. Le format legacy a établi le modèle d'origine, v0 l'a étendu avec les ALT afin de prendre en charge de plus grands ensembles de comptes dans la limite de 1 232 octets, et v1 repense plus profondément le format de transmission autour de transactions plus volumineuses, d'une analyse simplifiée, d'adresses intégrées et d'une configuration native des ressources.
Les trois formats restent valides, mais chacun représente une étape différente du développement de Solana. Ensemble, ils montrent comment le format de transaction s'est adapté à l'évolution des applications du réseau, des marchés de frais et des exigences des validateurs.
Ressources complémentaires
- Transactions v1 et compromis des ALT - Umberto Natale, Solana Foundation
- Transactions versionnées - Documentation Solana
- Augmenter la taille des transactions - Forums de discussion SIMD
- Transactions de plus grande taille - Mises à niveau de Solana
Articles associés
Abonnez-vous à Helius
Suivez les dernières actualités du développement sur Solana et recevez une notification à chaque publication


