
Solana-Transaktionsversionierung: Legacy, v0 und v1
Inhaltsverzeichnis
- Eine kurze Geschichte der Transaktionsformate von Solana
- Legacy-Transaktionen
- Transaktion v0
- Nachträgliche Integration von Priority Fees
- Größere Transaktionen
- Formatspezifikationen und Vergleich
- Spezifikation von Legacy-Transaktionen
- Spezifikation von Transaktion v0
- Spezifikation von Transaktion v1
- Beispieltransaktion
- Legacy- und v0-Transaktionen
- Ressourcenkonfigurationen
- Transaktion v1
- Signaturprüfung
- Verbessertes Parsen von Anweisungen
- Die Maske der Transaktionskonfiguration
- Fazit
- Weitere Ressourcen
Eine kurze Geschichte der Transaktionsformate von Solana
Solana hat drei Transaktionsformate: Legacy, v0 und v1. Im Wesentlichen erfüllen sie alle denselben Zweck. Sie bündeln Signaturen, Accounts und Anweisungen, die Validatoren für Onchain-Programme ausführen.
Im Laufe der Zeit hat sich das Wire-Format verändert, also die Art, wie diese Informationen serialisiert und über das Netzwerk übertragen werden.
Jedes neue Format wurde eingeführt, um Einschränkungen zu beheben, die mit zunehmend komplexen Solana-Anwendungen auftraten. Gleichzeitig blieb die Abwärtskompatibilität erhalten, damit ältere Formate weiter funktionieren.
Alle drei Formate existieren parallel im Netzwerk. Entwickler können das Format verwenden, das am besten zur jeweiligen Transaktion passt.
Legacy-Transaktionen
Das ursprüngliche Format heißt Legacy, weil es vor der Transaktionsversionierung entstand und keine Versionskennung enthält.
Die maximale Größe von 1.232 Byte geht auf das ursprüngliche Netzwerkdesign von Solana zurück: Transaktionen sollten in die minimale IPv6-MTU von 1.280 Byte passen. Nach Abzug des Netzwerk-Overheads blieben 1.232 Byte.
Für kleine Transaktionen funktionierte das gut. Mit dem Wachstum der Anwendungen wurde der Platz jedoch zunehmend knapp. Jede direkt in einer Transaktion enthaltene Account-Adresse belegt 32 Byte, jede Ed25519-Signatur weitere 64 Byte.
In der Praxis konnten Transaktionen mit vielen Accounts daher das Byte-Limit lange vor dem Account-Lock-Limit der Runtime erreichen.
Transaktion v0
Der erste Versuch, diesen Engpass zu beheben, war Transaktion v0. Sie ging im Oktober 2022 auf Mainnet-Beta live. Statt das Limit von 1.232 Byte zu erhöhen, führte v0 Address Lookup Tables (ALTs) ein.
Eine ALT speichert Account-Adressen onchain. Eine v0-Transaktion kann über Ein-Byte-Indizes auf sie verweisen, statt jeden öffentlichen 32-Byte-Schlüssel zu wiederholen. So erreichen Transaktionen das Runtime-Limit von 64 Accounts und bleiben zugleich innerhalb der bestehenden Paketgrößenbeschränkung.
v0 war eine inkrementelle Erweiterung des Legacy-Formats: Es ergänzte das Versionspräfix 0x80 und einen Abschnitt für Address Lookups, behielt aber die grundlegende Nachrichten- und Anweisungscodierung bei.
Mit der Versionierung entstand außerdem ein Framework, in dem neue Transaktionsformate neben Legacy-Transaktionen existieren können, statt sie zu ersetzen.
ALTs lösten das unmittelbare Problem der Account-Größe, brachten aber neue Komplexität mit sich. Anwendungen müssen Lookup Tables onchain erstellen und verwalten. Validatoren müssen deren Einträge vor der Ausführung auflösen. RPC-Dienste und Indexer benötigen die aufgelösten Adressen, um die vollständige Transaktion zu rekonstruieren.
Auch die historische Decodierung wird deutlich schwieriger, wenn die Lookup Table, auf die eine Rohtransaktion verweist, inzwischen geschlossen wurde. v0 komprimierte die Account-Liste also effektiv, führte dafür aber eine externe Onchain-Abhängigkeit in die Wire-Darstellung der Transaktion ein.
ALTs wurden von Solana-Entwicklern intensiv genutzt. Eine kurz vor der Einführung von v1-Transaktionen durchgeführte Untersuchung ergab, dass rund 62 % der v0-Transaktionen auf mindestens eine ALT verweisen.
Nachträgliche Integration von Priority Fees
Priority Fees wurden 2022 ebenfalls auf Solana eingeführt, noch bevor v0-Transaktionen im Mainnet aktiviert wurden. Das v0-Format war zu diesem Zeitpunkt allerdings bereits entworfen. Statt eines der beiden Transaktionsformate neu zu gestalten, ergänzte Solana die Gebührenkonfiguration über das bestehende Compute Budget Program. Transaktionen konnten Anweisungen wie SetComputeUnitLimit und SetComputeUnitPrice enthalten. So konnten Nutzer ein Compute-Budget anfordern und eine zusätzliche Gebühr für eine bevorzugte Einplanung festlegen. Das funktionierte für Legacy- und v0-Transaktionen gleichermaßen und vermied einen separaten Gebührenmechanismus für jedes Format.
Das war eine pragmatische, abwärtskompatible, aber nicht besonders elegante Lösung. Priority Fees und Compute-Limits sind eigentlich Metadaten auf Transaktionsebene. Legacy und v0 hatten dafür jedoch keine eigenen Felder, sodass sie als Programmaufrufe nachträglich in den Anweisungsstream integriert wurden. Deshalb muss die Transaktion auf das Compute Budget Program verweisen, die Konfiguration belegt Anweisungsbytes und Validatoren müssen diese Anweisungen beim Einlesen der Transaktion prüfen, um die Gebühren- und Ressourceneinstellungen zu ermitteln.
v0 war daher keine umfassende Neugestaltung des Solana-Transaktionsformats. Das Hauptziel bestand darin, den Engpass bei Account-Adressen mithilfe von Address Lookup Tables zu beheben und den Großteil der Legacy-Nachrichtenstruktur beizubehalten. Da Compute-Budget-Anweisungen bereits eine kompatible Unterstützung von Priority Fees in beiden Formaten ermöglichten, gab es kaum einen Grund, den Umfang von v0 weiter auszudehnen.
Größere Transaktionen
Im Laufe der Zeit stellte Solana das Einlesen von Transaktionen von UDP auf QUIC um. Dessen Streams sind nicht mehr auf eine einzelne Nutzlast in MTU-Größe beschränkt. Die ursprüngliche Obergrenze von 1.232 Byte war damit keine Transportanforderung mehr. 2025 wurden zwei SIMDs für ein neues v1-Format vorgeschlagen. SIMD-0296 erhöhte die maximale Größe einer v1-Transaktion auf 4.096 Byte, etwa das 3,3-Fache des bisherigen Limits. SIMD-0385 definierte ein vollständig neues Wire-Format, das diesen zusätzlichen Platz nutzt und Validatoren ein effizienteres Einlesen ermöglicht.
Durch den zusätzlichen Platz kann v1 vollständig auf ALTs verzichten. Das Limit von 64 Account-Adressen mit je 32 Byte erfordert höchstens 2.048 Byte. Die vollständige Account-Liste lässt sich daher einfach inline übertragen.
Analysen der Solana Foundation deuten darauf hin, dass die Auswirkungen des Inline-Speicherns aller Adressen beim Upgrade der meisten bestehenden v0-Transaktionen moderat sind: 50 % wachsen um weniger als 420 Byte, 90 % um weniger als 1.400 Byte. Selbst Transaktionen, die ALTs intensiv nutzen, behalten damit innerhalb des v1-Limits von 4.096 Byte erheblichen Spielraum.
Noch wichtiger ist, dass v1 nicht bloß eine größere v0-Transaktion ist. Das Wire-Format wurde umfassend neu strukturiert: Signaturen stehen am Ende, die Ressourcenkonfiguration wandert aus den Compute-Budget-Anweisungen in einen Abschnitt für die Transaktionskonfiguration und Anweisungsnutzlasten variabler Länge werden von Headern fester Breite getrennt. Dadurch können Validatoren Transaktionen günstiger und einfacher parsen und priorisieren.
Das größere Limit von 4.096 Byte ermöglicht außerdem Workloads, bei denen eine reine Adresskomprimierung nie geholfen hätte. Zero-Knowledge-Beweise, verschachtelte Multisigs, ungekürzte Winternitz-Signaturen, BLS-Signaturen und andere kryptografische Operationen können Hunderte oder Tausende Byte an Anweisungsdaten erfordern. Vertrauliche Token-2022-Guthaben lassen sich nun beispielsweise als eine einzige atomare v1-Transaktion erstellen, statt sie auf mehrere Transaktionen aufzuteilen.
Das anfängliche v1-Format erhöht insbesondere weder die Compute-, Signatur- und Anweisungsanzahl-Limits noch das Limit von 64 Accounts. Es beseitigt hauptsächlich den Größenengpass und gestaltet die Wire-Darstellung der Transaktion neu.
SIMD-0596, vorgeschlagen von Anza, würde das Account-Lock-Limit von v1 von 64 auf 96 erhöhen. Das umfasst alle Accounts, auf die die Transaktion verweist, darunter Signierer, Programm-IDs sowie schreibbare und schreibgeschützte Accounts. Zum Zeitpunkt der Veröffentlichung wird dieser Vorschlag noch geprüft. Legacy- und v0-Transaktionen blieben auf 64 begrenzt.
Formatspezifikationen und Vergleich
| Limit | Legacy | v0 | v1 |
| Maximale Größe | 1.232 Byte | 1.232 Byte | 4.096 Byte |
| Account-Adressen | ~32, größenbegrenzt | 64, über Lookup Tables | 64, inline |
| Address Lookup Tables | Nicht unterstützt | Unterstützt | Nicht unterstützt |
| Doppelte Adressen | zulässig | zulässig | abgelehnt |
| Präfix | Keines | 0x80 | 0x81 |
| Priority Fee | SetComputeUnitPrice-Anweisung, Mikro-Lamports pro CU | SetComputeUnitPrice-Anweisung, Mikro-Lamports pro CU | config.priorityFeeLamports, gesamte Lamports |
| Compute-Unit-Limit | SetComputeUnitLimit-Anweisung | SetComputeUnitLimit-Anweisung | config.computeUnitLimit |
| Heap-Größe | RequestHeapFrame-Anweisung | RequestHeapFrame-Anweisung | config.heapSize |
| Limit geladener Accounts | SetLoadedAccountsDataSizeLimit-Anweisung | SetLoadedAccountsDataSizeLimit-Anweisung | config.loadedAccountsDataSizeLimit |
Spezifikation von Legacy-Transaktionen
Eine serialisierte Legacy-Transaktion beginnt mit einer als compact-u16 codierten Signaturanzahl, gefolgt von den 64 Byte großen Ed25519-Signaturen.
Danach folgt die Nachricht. Sie enthält den Drei-Byte-Header, die Account-Liste mit vorangestellter kompakter Länge, den 32 Byte großen aktuellen Blockhash und ein Array kompilierter Anweisungen mit vorangestellter kompakter Länge.
Legacy-Transaktionen haben kein Versionspräfix.
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 = InstructionDataLengthEin wichtiges Merkmal ist, dass jede CompiledInstruction vollständig serialisiert wird, bevor die nächste Anweisung beginnt.
Die Arrays für Account-Indizes und Daten haben eine variable Länge. compact-u16-Präfixe definieren ihre Größe.
Spezifikation von Transaktion v0
Transaktion v0 behält fast das gesamte Legacy-Wire-Format bei. Die Transaktion beginnt weiterhin mit der Signaturanzahl und den Signaturen. Die Nachricht beginnt jedoch mit dem Versionspräfix 0x80.
Danach verwendet die Nachricht denselben Drei-Byte-Header, dieselbe statische Account-Liste, denselben Blockhash und dasselbe Format für kompilierte Anweisungen wie Legacy. Anschließend wird ein Abschnitt für Address Lookup Tables angehängt.
So können v0-Transaktionen über ALTs zusätzliche Accounts laden und zugleich innerhalb des Transaktionslimits von 1.232 Byte bleiben.
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 tableDie Unterscheidung zwischen statischen Account-Schlüsseln und geladenen Adressen ist wichtig. Nur statische Schlüssel werden direkt in der Nachricht serialisiert. Über ALTs referenzierte Adressen werden zur Laufzeit aufgelöst und an die effektive Account-Liste angehängt.
Bei einer v0-Transaktion mit ALTs muss der Validator zunächst jede referenzierte Lookup Table aus Bank und AccountsDB laden. Dann prüft er, ob der Account eine gültige Address Lookup Table ist, deserialisiert ihren Zustand, löst die angeforderten Indizes in vollständige 32-Byte-Adressen auf und führt diese Adressen mit den statischen Account-Schlüsseln der Transaktion zusammen. Erst danach verfügt der Validator über die vollständige Account-Liste, mit der er Lese- und Schreibsperren sowie Konflikte mit anderen Transaktionen ermitteln kann.
Spezifikation von Transaktion v1
Eine serialisierte v1-Transaktion beginnt mit dem Versionsbyte 0x81. Danach folgen ein Drei-Byte-Nachrichtenheader im Legacy-Stil, eine 32-Bit-Maske für die Transaktionskonfiguration, eine 32 Byte große Gültigkeitsangabe, die Anzahl der Anweisungen und Adressen, das vollständige Array der 32-Byte-Adressen, Konfigurationswerte, Anweisungsheader fester Größe, zusammenhängende Anweisungsnutzlasten und schließlich die Signaturen.
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 unterscheidet sich daher in mehreren grundlegenden Punkten von den früheren Formaten. Die Versionskennung steht bei Byte null. Signaturen wandern ans Ende und benötigen kein eigenes Längenpräfix mehr. Die Anzahl der Accounts und Anweisungen wird zu u8-Werten fester Breite. Die Ressourcenkonfiguration wird direkt in der Transaktion codiert. Außerdem werden Anweisungsheader fester Größe von ihren Nutzlasten variabler Länge getrennt. Anders als Legacy und v0 verwendet v1 keine Address Lookup Tables.
Transaktion v1 ersetzt Konfigurationsanweisungen für das Compute Budget Program durch Felder im Transaktionsheader. Die anfängliche TransactionConfigMask kann Folgendes für die Transaktion deklarieren:
- gesamte Priority Fee in Lamports
- Compute-Unit-Limit
- Größenlimit für Daten geladener Accounts
- angeforderte Heap-Größe
Jedes gesetzte Bit kennzeichnet einen zugehörigen Vier-Byte-Konfigurationswert. Die 64-Bit-Priority-Fee belegt zwei Maskenpositionen. Die Maske ist so ausgelegt, dass sie in zukünftigen Transaktionsversionen erweitert werden kann.
Beispieltransaktion
Um die verschiedenen Transaktionsformate von Solana zu vergleichen, betrachten wir das einfachste nützliche Beispiel: die Übertragung von SOL von einer Adresse an eine andere.
Unsere Beispieltransaktion hat einen einzigen Signierer, also den Absender, und ruft das System Program einmal auf, um 1.000 Lamports an den Empfänger zu übertragen. Sie verweist auf drei Adressen: den Absender, den Empfänger und das System Program.
Legacy- und v0-Transaktionen
Legacy- und v0-Transaktionen verwenden nahezu dasselbe Layout auf oberster Ebene. Beide beginnen mit der Anzahl der Signaturen, unmittelbar gefolgt von den Signaturen selbst. Danach folgt die Nachricht der Transaktion. Bei einer Übertragung mit einem Signierer lautet das erste Byte 01 und gibt eine einzelne Signatur an. Darauf folgt die 64 Byte große Ed25519-Signatur des Absenders.
Die Nachricht enthält einen Drei-Byte-Header, Account-Adressen, einen aktuellen Blockhash als Gültigkeitsangabe und kompilierte Anweisungen.
Der Absender ist Adresse 0, der Empfänger Adresse 1 und das System Program Adresse 2. Die Übertragungsanweisung verweist auf das Programm an Index 2 und übergibt dem Programm die Account-Indizes 00 01.
Die Formate Legacy und v0 unterscheiden sich nur geringfügig:
- Eine Legacy-Nachricht beginnt direkt mit ihrem Drei-Byte-Nachrichtenheader. Eine v0-Nachricht beginnt dagegen mit dem Versionsbyte
0x80, gefolgt vom Header. - v0 hängt nach den Anweisungen außerdem einen Abschnitt für Address Lookup Tables an. Unser einfaches Beispiel verwendet keine Lookup Table. Dieser Abschnitt besteht daher aus einem einzelnen Byte
00, das null Lookups angibt.
Dadurch ist unsere beispielhafte SOL-Übertragung als Legacy-Transaktion 215 Byte und als v0-Transaktion 217 Byte groß. v0 ergänzt 1 Byte für das Versionspräfix und 1 Byte für das leere Lookup-Table-Array. Der Rest der Transaktion ist identisch.
Der Absender signiert nur die Nachricht. In unserem Legacy-Beispiel deckt die Signatur somit die Bytes 65–214 ab. Bei v0 deckt sie die Bytes 65–216 ab, beginnend mit dem Nachrichtenversionspräfix 0x80.
Die folgende Tabelle zeigt das Byte-Layout unserer beispielhaften SOL-Übertragung zwischen zwei Adressen im v0-Transaktionsformat.
| Offset | Größe | Feld | Beispielwert | Beschreibung |
| 0 | 1 B | Signaturanzahl | 01 | Eine Signatur; compact-u16 |
| 1–64 | 64 B | Signatur 0 | <Ed25519 signature> | Signatur des Absenders |
| 65 | 1 B | Versionspräfix | 80 | Versionierte Nachricht, Version 0 |
| 66–68 | 3 B | Nachrichtenheader | 01 00 01 | 1 erforderlicher Signierer, 0 schreibgeschützte signierte, 1 schreibgeschützter unsignierter Account |
| 69 | 1 B | Anzahl statischer Accounts | 03 | Drei Inline-Accounts |
| 70–101 | 32 B | Account 0 | <sender pubkey> | Absender/Gebührenzahler; schreibbarer Signierer |
| 102–133 | 32 B | Account 1 | <recipient pubkey> | Empfänger; schreibbar, unsigniert |
| 134–165 | 32 B | Account 2 | 11111111111111111111111111111111 | System Program; schreibgeschützt, unsigniert |
| 166–197 | 32 B | Aktueller Blockhash | <recent blockhash> | Gültigkeitsdauer der Transaktion |
| 198 | 1 B | Anweisungsanzahl | 01 | Eine Anweisung |
| 199 | 1 B | Programm-ID-Index | 02 | System Program |
| 200 | 1 B | Anzahl der Anweisungs-Accounts | 02 | Zwei Anweisungs-Accounts |
| 201–202 | 2 B | Account-Indizes | 00 01 | Absender, Empfänger |
| 203 | 1 B | Datenlänge | 0C | 12 Byte |
| 204–207 | 4 B | Übertragungsdiskriminator | 02 00 00 00 | SystemInstruction::Transfer |
| 208–215 | 8 B | Lamports | E8 03 00 00 00 00 00 00 | 1.000 Lamports |
| 216 | 1 B | Anzahl der Address-Table-Lookups | 00 | Keine ALT-Lookups |
| Gesamt | 217 B |
Ressourcenkonfigurationen
In Legacy- und v0-Transaktionen werden Ressourcenkonfigurationen als Anweisungen an das Compute Budget Program ausgedrückt. Häufig verwendet werden SetComputeUnitLimit und SetComputeUnitPrice. Bei Bedarf kommen auch SetLoadedAccountsDataSizeLimit und RequestHeapFrame zum Einsatz.
In unserem minimalen Transaktionsbeispiel fehlen diese Anweisungen. Stattdessen stellt die Runtime Standardwerte bereit.
Ohne Compute-Unit-Preis gibt es keine Priority Fee. Das Limit für Daten geladener Accounts entspricht standardmäßig dem Runtime-Maximum.
Dass Validatoren beim Einlesen einer Transaktion die Anweisungsliste nach Compute-Budget-Anweisungen durchsuchen müssen, ist einer der Gründe für das aktualisierte v1-Format. Dort werden diese Informationen stattdessen in TransactionConfigMask und ConfigValues abgelegt.
Die Darstellung einer Konfiguration auf Transaktionsebene als Anweisungen verursacht außerdem grundsätzlich Overhead. Legacy und v0 hatten keine eigenen Felder für angeforderte Compute-Ressourcen oder Priority Fees. Daher wurden diese Einstellungen nachträglich über das Compute Budget Program integriert: Die Transaktion muss auf dessen Programm-ID verweisen, jede Einstellung belegt Platz in der Anweisungsliste und der Programmaufruf selbst erledigt keine Anwendungsarbeit, verbraucht aber dennoch Compute-Ressourcen.
v1 gibt diesen Werten stattdessen einen nativen Platz im Wire-Format. Das vermeidet den zusätzlichen Anweisungs-Overhead und macht die Ressourcenkonfiguration günstiger und für Validatoren leichter prüfbar.
Transaktion v1
Statt mit einer Signaturanzahl und den Signaturen zu beginnen, startet eine v1-Transaktion direkt mit dem Versionsbyte 0x81.
Danach folgen der Drei-Byte-Header, eine neue vier Byte große TransactionConfigMask, die Gültigkeitsangabe — normalerweise ein Blockhash, aber auch eine Nonce ist möglich —, die Anzahl der Anweisungen und Adressen, Account-Adressen, Konfigurationswerte, Anweisungen und schließlich die Signaturen.
| Offset | Größe | Feld | Beispielwert | Beschreibung |
| 0 | 1 B | Versionsbyte | 81 | v1-Transaktion |
| 1–3 | 3 B | Legacy-Header | 01 00 01 | 1 erforderliche Signatur, 0 schreibgeschützte signierte Accounts, 1 schreibgeschützter unsignierter Account |
| 4–7 | 4 B | Maske der Transaktionskonfiguration | 0C 00 00 00 | 0x0000000C: Bits 2 und 3 gesetzt, gibt zwei 4-Byte-Konfigurationswerte an |
| 8–39 | 32 B | Gültigkeitsangabe | <recent blockhash> | Aktueller Blockhash (oder Nonce) |
| 40 | 1 B | Anzahl der Anweisungen | 01 | 1 Anweisung des System Program |
| 41 | 1 B | Anzahl der Adressen | 03 | Absender, Empfänger, System Program |
| 42–73 | 32 B | Adresse 0 | <sender pubkey> | Absender, Gebührenzahler, schreibbarer Signierer |
| 74–105 | 32 B | Adresse 1 | <recipient pubkey> | Empfänger, schreibbarer unsignierter Account |
| 106–137 | 32 B | Adresse 2 | 11111111111111111111111111111111 | System Program, schreibgeschützt, unsigniert |
| 138–141 | 4 B | Konfigurationswert: Compute-Unit-Limit | 10 27 00 00 | 10.000 CU als Little-Endian-u32 (entspricht Maskenbit 2) |
| 142–145 | 4 B | Konfigurationswert: Größenlimit für Daten geladener Accounts | 00 00 01 00 | 65.536 Byte / 64 KiB als Little-Endian-u32 (entspricht Maskenbit 3) |
| 146–149 | 4 B | Anweisung: Header | 02 02 0C 00 | Programmindex 2, 2 Accounts, 12 Byte Daten |
| 150–151 | 2 B | Anweisung: Account-Indizes | 00 01 | Adresse 0 = Absender, Adresse 1 = Empfänger |
| 152–155 | 4 B | Anweisung: Übertragungsdiskriminator | 02 00 00 00 | SystemInstruction::Transfer |
| 156–163 | 8 B | Anweisung: Lamports | z. B. E8 03 00 00 00 00 00 00 | 1.000 Lamports als Little-Endian-u64 |
| 164–227 | 64 B | Signatur 0 | <Ed25519 signature> | Signatur des Absenders über die Bytes 0–155 |
| Gesamt | 228 B |
Signaturprüfung
Die Signaturprüfung ist eine der ersten Operationen, wenn eine Transaktion einen Validator erreicht. Transaktionen mit ungültigen Signaturen müssen herausgefiltert und verworfen werden, bevor sie den Scheduler erreichen. Signaturen hinter einer Nachricht variabler Länge zu platzieren scheint den Zugriff zu erschweren. In der Praxis ist das jedoch nicht der Fall.
Vor der aufwendigen Ed25519-Prüfung muss der Validator lediglich erkennen, wo die einzelnen Teile der Transaktion beginnen und enden. Das v1-Format macht dies günstig und deterministisch.
Das erste Byte, 0x81, kennzeichnet die Transaktion sofort als v1. Der folgende Header enthält num_required_signatures. Der Validator weiß daher von Anfang an, wie viele 64-Byte-Signaturen er finden muss.
Der Parser TransactionView von Agave durchläuft die Transaktion anschließend vorwärts und zeichnet dabei Offsets auf. Er liest die Felder fester Größe und verwendet NumAddresses, um das Adress-Array zu überspringen.
Anhand der Konfigurationsmaske bestimmt er die Größe des Konfigurationsabschnitts. Außerdem liest er jeden 4-Byte-Anweisungsheader, um die Größe der zugehörigen Anweisungsnutzlast zu ermitteln.
Sobald der Parser die letzte Anweisungsnutzlast passiert hat, wird seine aktuelle Position als Signatur-Offset gespeichert. Ab dieser Position erwartet Agave genau num_required_signatures × 64 Byte.
Die Implementierung speichert einen SignatureFrame, der die Anzahl der Signaturen und den Byte-Offset der ersten Signatur im Paket enthält. Die signierte Nachricht der Transaktion ist der Byte-Bereich vom Anfang der v1-Nachricht bis zum gespeicherten Signatur-Offset. So kann der Validator die aufwendige Ed25519-Prüfung durchführen, ohne die Transaktion auszuführen oder Accounts zu laden.
v1 verbietet außerdem nachgestellte Bytes hinter dem Signatur-Array. Sobald der Parser die Signaturen erreicht, haben die verbleibenden Bytes eine eindeutige, von der Signaturanzahl bestimmte Größe. SIMD-0385 verlangt für jeden erforderlichen Signierer genau eine 64-Byte-Signatur und keine Daten nach der letzten Signatur.
Verbessertes Parsen von Anweisungen
Das Layout von Transaktion v1 erleichtert das Parsen von Anweisungen. Um eine spätere Anweisung in Legacy- und v0-Transaktionen zu finden, muss ein Parser die vorherigen Anweisungen durchlaufen, ihre Längen decodieren und jede Nutzlast variabler Länge überspringen.
v1 trennt Anweisungsheader fester Breite von Nutzlasten variabler Länge. Jede Anweisung fügt zunächst einen Vier-Byte-Header hinzu. Dieser enthält den Index des Programm-Accounts, die Anzahl der Anweisungs-Accounts und die Länge der Anweisungsdaten. Diese Header werden vor den Account-Index- und Datennutzlasten gruppiert. So steht vorab eine kompakte Beschreibung jeder Anweisung bereit. Dadurch lassen sich der Anweisungsabschnitt günstiger erfassen und überspringen sowie der Offset des nachgestellten Signatur-Arrays bestimmen, ohne Anweisungsdaten zu parsen.
Die Maske der Transaktionskonfiguration
Die andere wichtige Ergänzung des v1-Formats ist die vier Byte große TransactionConfigMask.
Legacy- und v0-Transaktionen konfigurieren Ressourcen über Anweisungen an das Compute Budget Program. Eine Transaktion kann beispielsweise SetComputeUnitLimit-, SetComputeUnitPrice- oder SetLoadedAccountsDataSizeLimit-Anweisungen enthalten. Ein Validator, der die Ressourcenanforderungen oder Priorität der Transaktion ermitteln möchte, muss diese Anweisungen finden und interpretieren. Transaktion v1 verschiebt diese Informationen aus dem Anweisungsstream direkt in das Transaktionsformat.
Die Maske ist ein 32-Bit-Bitfeld im Little-Endian-Format. Jedes gesetzte Bit entspricht einem Vier-Byte-Wort im späteren Abschnitt ConfigValues der Transaktion:
- Bits 0 und 1: gesamte Priority Fee in Lamports (codiert als 8-Byte-Little-Endian-
u64) - Bit 2: Compute-Unit-Limit (ein 4-Byte-
u32) - Bit 3: Größenlimit für Daten geladener Accounts (ein 4-Byte-
u32) - Bit 4: angeforderte Heap-Größe (ein 4-Byte-
u32)
Hinweis: Die anfängliche v1-Spezifikation weist nur den Bits 0–4 Bedeutungen zu. Die übrigen Bits sind derzeit nicht belegt. So bleibt Platz für zukünftige Konfigurationsfelder auf Transaktionsebene.
Die Maske dient als kompaktes Schema. Sie teilt dem Parser mit, welche Felder vorhanden sind und wie viele Bytes er erwarten muss. Unsere einfache SOL-Übertragung benötigt beispielsweise ein Compute-Unit-Limit und ein Größenlimit für Daten geladener Accounts, aber weder eine Priority Fee noch eine benutzerdefinierte Heap-Größe.
Da diese beiden Bits gesetzt sind, folgen auf das Adress-Array der Transaktion genau zwei 4-Byte-Konfigurationswerte. In unserem Fall fordern sie ein Limit von 20.000 CU und ein Limit von 64 KiB für Daten geladener Accounts an.
Die Maske teilt dem Validator mit, dass der erste 4-Byte-Konfigurationswert zu Bit 2, dem Compute-Unit-Limit, und der zweite zu Bit 3, dem Größenlimit für Daten geladener Accounts, gehört.
Wenn eine Anweisung für das Compute-Unit-Limit in Legacy- und v0-Transaktionen fehlt, erhält die Transaktion ein implizites Compute-Budget. v1 verwendet nicht dieselben Standardwerte. Ein nicht festgelegtes Compute-Unit-Limit wird ebenso wie ein nicht festgelegtes Größenlimit für Daten geladener Accounts als null interpretiert. Nur der Heap behält einen Standardwert von 32 KiB. Eine v1-Transaktion muss die benötigten Ressourcen daher ausdrücklich anfordern.
Wenn diese Einstellungen in einen klar definierten Konfigurationsbereich verschoben werden, erhalten Validatoren beim Einlesen der Transaktion direkten Zugriff auf die benötigten Informationen und müssen die Anweisungen nicht mehr durchsuchen. Das bedeutet auch, dass Anweisungen des Compute Budget Program v1-Transaktionen nicht mehr konfigurieren. Falls sie enthalten sind, werden sie ignoriert und als No-op-Anweisungen verarbeitet, verbrauchen aber weiterhin Compute Units.
Das spart zusätzlich Platz. Wenn du Legacy-/v0-Transaktionen Compute-Budget-Anweisungen hinzufügst, benötigst du in der Regel die 32 Byte große Adresse des Compute Budget Program in der Account-Liste sowie die serialisierten Anweisungen selbst.
Fazit
Die drei Transaktionsformate von Solana spiegeln die Entwicklung des Netzwerks wider. Legacy etablierte das ursprüngliche Modell. v0 erweiterte es mit ALTs, um innerhalb der Obergrenze von 1.232 Byte größere Account-Gruppen zu unterstützen. v1 denkt das Wire-Format grundlegend neu: mit größeren Transaktionen, einfacherem Parsing, Inline-Adressen und nativer Ressourcenkonfiguration.
Alle drei bleiben gültig, repräsentieren aber jeweils eine andere Phase der Solana-Entwicklung. Zusammen zeigen sie, wie sich das Transaktionsformat an die wachsenden Anforderungen der Anwendungen, Gebührenmärkte und Validatoren im Netzwerk angepasst hat.
Weitere Ressourcen
- v1-Transaktionen und der ALT-Kompromiss - Umberto Natale, Solana Foundation
- Versionierte Transaktionen - Solana Docs
- Transaktionsgröße erhöhen - SIMD-Diskussionsforen
- Größere Transaktionen - Solana Upgrades
Ähnliche Artikel
Helius abonnieren
Bleib bei der Solana-Entwicklung auf dem Laufenden und erhalte Updates, wenn wir neue Beiträge veröffentlichen


