
Solanaトランザクションのバージョニング:レガシー、v0、v1
Solanaトランザクション形式の略史
Solanaには、レガシー、v0、v1という3つのトランザクション形式があります。大まかな目的はすべて同じです。つまり、バリデータがオンチェーンプログラムに対して実行する署名、アカウント、命令をまとめます。
時代とともに変化したのはワイヤ形式、つまり、これらの情報をどのようにシリアライズし、ネットワーク経由で送信するかです。
各形式は、Solanaアプリケーションが複雑になるにつれて明らかになった制約に対処するために導入されました。同時に、古い形式も引き続き動作できるよう、後方互換性が維持されています。
3つの形式はすべてネットワーク上で共存しており、開発者は構築するトランザクションに最適な形式を使用できます。
レガシートランザクション
元の形式は、トランザクションのバージョニングより前から存在し、バージョン識別子を含まないため、レガシーと呼ばれています。
最大サイズが1,232バイトなのは、Solanaの当初のネットワーク設計に由来します。トランザクションは、IPv6の最小MTUである1,280バイト以内に収まるよう設計され、ネットワークのオーバーヘッドを差し引くと1,232バイトが残りました。
これは小さなトランザクションには適していましたが、アプリケーションの成長に伴い、容量の制約が厳しくなりました。トランザクションに直接含まれる各アカウントアドレスは32バイト、各Ed25519署名はさらに64バイトを占有します。
そのため実際には、アカウント数の多いトランザクションでは、ランタイムのアカウントロック上限に達するよりもはるかに前に、バイト数の上限を使い切る可能性がありました。
トランザクションv0
この制約を緩和する最初の試みがトランザクションv0です。2022年10月にmainnet-betaで稼働を開始しました。v0では1,232バイトの上限を引き上げるのではなく、**Address Lookup Tables(ALT)**が導入されました。
ALTはアカウントアドレスをオンチェーンに保存します。これによりv0トランザクションは、各32バイトの公開鍵を繰り返す代わりに、1バイトのインデックスで参照できます。そのため、既存のパケットサイズ制約を維持しながら、ランタイムの64アカウント上限まで利用できます。
v0はレガシー形式を段階的に拡張したものです。基本的なメッセージと命令のエンコーディングを維持しながら、0x80バージョンプレフィックスとアドレスルックアップセクションを追加しました。
バージョニングの導入により、新しいトランザクション形式がレガシートランザクションを置き換えるのではなく、共存できるフレームワークも確立されました。
ALTは差し迫ったアカウントサイズの問題を解決しましたが、新たな複雑さももたらしました。アプリケーションはオンチェーンでルックアップテーブルを作成・維持する必要があり、バリデータは実行前にそのエントリを解決しなければなりません。また、RPCサービスとインデクサーは、完全なトランザクションを再構築するために解決済みアドレスを必要とします。
生のトランザクションが参照するルックアップテーブルが後に閉じられた場合、過去のデコードもはるかに難しくなります。したがってv0はアカウントリストを効果的に圧縮しましたが、その代わり、トランザクションのワイヤ表現に外部のオンチェーン依存関係を持ち込みました。
ALTはSolana開発者に広く採用されました。v1トランザクションの導入直前に行われた調査では、v0トランザクションの約62%が少なくとも1つのALTを参照していることが分かりました。
優先手数料の後付け
優先手数料も2022年にSolanaへ導入されました。v0トランザクションがメインネットで有効になる前ですが、その時点ですでにv0形式自体は設計済みでした。いずれかのトランザクション形式を再設計する代わりに、Solanaは既存のCompute Budget Programを通じて手数料設定を追加しました。トランザクションにSetComputeUnitLimitやSetComputeUnitPriceなどの命令を含めることで、ユーザーはコンピュート予算を要求し、スケジューリングの優先度を高める追加手数料を設定できました。形式ごとに個別の手数料メカニズムを用意せず、レガシーとv0の両方で同様に機能しました。
これは実用的で後方互換性のある解決策でしたが、特に洗練されたものではありませんでした。優先手数料とコンピュート上限は本来トランザクションレベルのメタデータですが、レガシーとv0には専用フィールドがありません。そのため、プログラム呼び出しとして命令ストリームに後付けされました。つまり、トランザクションからCompute Budget Programを参照する必要があり、設定が命令のバイト数を消費し、バリデータはトランザクションの取り込み時にそれらの命令を調べて手数料とリソース設定を取得しなければなりません。
したがってv0は、Solanaのトランザクション形式を全面的に再設計したものではありません。主な目的はAddress Lookup Tablesによってアカウントアドレスのボトルネックを解消しつつ、レガシーメッセージの構造をほぼ維持することでした。Compute Budget命令によって両形式で優先手数料をサポートする互換性のある方法がすでに提供されていたため、v0の対象範囲をさらに広げる理由はほとんどありませんでした。
トランザクションサイズの拡大
その後、Solanaはトランザクションの取り込みをUDPからQUICへ移行しました。QUICのストリームは、単一のMTUサイズのペイロードに制約されません。そのため、当初の1,232バイトという上限は、もはや転送上の要件ではなくなりました。新しいv1形式を導入するため、2025年に2つのSIMDが提案されました。SIMD-0296はv1トランザクションの最大サイズを従来の約3.3倍にあたる4,096バイトへ引き上げ、SIMD-0385は追加容量とより効率的なバリデータ取り込みを前提に、まったく新しいワイヤ形式を定義しました。
追加された容量により、v1ではALTを完全に排除できます。32バイトのアドレスを64アカウント分格納しても最大2,048バイトで済むため、完全なアカウントリストをそのままインラインで保持できます。
Solana Foundationの分析によると、既存のv0トランザクションの大半をアップグレードする際、すべてのアドレスをインライン化する影響は限定的です。50%は増加量が420バイト未満、90%は1,400バイト未満です。ALTを多用するトランザクションでも、v1の4,096バイト上限には十分な余裕が残ります。
さらに重要なのは、v1が単に大きなv0トランザクションではないことです。ワイヤ形式が大幅に再編されています。署名は末尾へ移動し、リソース設定はCompute Budget命令からトランザクション設定セクションへ移り、可変長の命令ペイロードは固定幅のヘッダーから分離されました。これらの変更により、バリデータによるトランザクションの解析と優先順位付けが低コストかつ容易になります。
4,096バイトという大きな上限により、アドレス圧縮だけでは対応できなかったワークロードも扱えます。ゼロ知識証明、ネストされたマルチシグ、切り詰められていないWinternitz署名、BLS署名、その他の暗号処理では、数百から数千バイトの命令データが必要になる場合があります。たとえば、Token-2022 Confidential Balancesは、複数のトランザクションに分割せず、単一のアトミックなv1トランザクションとして構築できるようになりました。
特筆すべき点として、初期のv1形式ではSolanaのコンピュート、署名、命令数、64アカウントの上限は引き上げられません。主にサイズのボトルネックを解消し、トランザクションのワイヤ上の表現方法を再設計しています。
Anzaが提案したSIMD-0596では、v1のアカウントロック上限を64から96へ引き上げます。署名者、プログラムID、書き込み可能アカウント、読み取り専用アカウントを含め、トランザクションが参照するすべてのアカウントが対象です。本稿執筆時点で、この提案は審査中です。レガシーとv0トランザクションの上限は引き続き64です。
形式の仕様と比較
| 上限 | レガシー | v0 | v1 |
| 最大サイズ | 1,232バイト | 1,232バイト | 4,096バイト |
| アカウントアドレス | 約32、サイズに依存 | 64、ルックアップテーブル経由 | 64、インライン |
| Address Lookup Tables | 非対応 | 対応 | 非対応 |
| 重複アドレス | 許可 | 許可 | 拒否 |
| プレフィックス | なし | 0x80 | 0x81 |
| 優先手数料 | SetComputeUnitPrice命令、CUあたりのマイクロlamports | SetComputeUnitPrice命令、CUあたりのマイクロlamports | config.priorityFeeLamports、合計lamports |
| コンピュートユニット上限 | SetComputeUnitLimit命令 | SetComputeUnitLimit命令 | config.computeUnitLimit |
| ヒープサイズ | RequestHeapFrame命令 | RequestHeapFrame命令 | config.heapSize |
| ロード済みアカウントの上限 | SetLoadedAccountsDataSizeLimit命令 | SetLoadedAccountsDataSizeLimit命令 | config.loadedAccountsDataSizeLimit |
レガシートランザクションの仕様
シリアライズされたレガシートランザクションは、compact-u16形式の署名数で始まり、その後に64バイトのEd25519署名が続きます。
次にメッセージが続きます。メッセージには3バイトのヘッダー、compact-lengthプレフィックス付きアカウントリスト、32バイトの直近のblockhash、compact-lengthプレフィックス付きのコンパイル済み命令配列が含まれます。
レガシートランザクションにはバージョンプレフィックスがありません。
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 = InstructionDataLength重要な特徴は、各CompiledInstructionが、次の命令が始まる前に完全な形でシリアライズされることです。
アカウントインデックスとデータの配列は可変長で、そのサイズはcompact-u16プレフィックスで定義されます。
トランザクションv0の仕様
トランザクションv0は、レガシーのワイヤ形式をほぼすべて維持しています。トランザクションは引き続き署名数と署名で始まりますが、メッセージはバージョンプレフィックス0x80で始まります。
その後、メッセージはレガシーと同じ3バイトのヘッダー、静的アカウントリスト、blockhash、コンパイル済み命令形式を使用し、最後にAddress Lookup Tableセクションを追加します。
これにより、v0トランザクションは1,232バイトのトランザクション上限を維持しながら、ALTを通じて追加のアカウントをロードできます。
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 table静的アカウントキーとロード済みアドレスの違いが重要です。メッセージへ直接シリアライズされるのは静的キーだけです。ALT経由で参照されるアドレスは実行時に解決され、有効なアカウントリストへ追加されます。
ALTを使用するv0トランザクションでは、バリデータはまず、参照される各ルックアップテーブルをBankとAccountsDBからロードし、そのアカウントが有効なAddress Lookup Tableであることを検証して状態をデシリアライズし、要求されたインデックスを完全な32バイトアドレスへ解決します。その後、それらのアドレスをトランザクションの静的アカウントキーと統合します。この解決処理が完了して初めて、バリデータは読み取り/書き込みロックと他のトランザクションとの競合を判断するために必要な完全なアカウントリストを取得できます。
トランザクションv1の仕様
シリアライズされたv1トランザクションは、バージョンバイト0x81で始まります。その後に、レガシー形式の3バイトのメッセージヘッダー、32ビットのトランザクション設定マスク、32バイトの有効期間指定子、命令数とアドレス数、32バイトアドレスの完全な配列、設定値、固定サイズの命令ヘッダー、連続した命令ペイロード、最後に署名が続きます。
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.NumRequiredSignaturesそのため、v1はいくつかの根本的な点で以前の形式と異なります。バージョン識別子はバイト0に置かれ、署名は末尾へ移動して固有の長さプレフィックスが不要になります。アカウント数と命令数は固定幅のu8値になり、リソース設定はトランザクションへ直接エンコードされます。また、固定サイズの命令ヘッダーが可変長のペイロードから分離されます。レガシーやv0とは異なり、v1はAddress Lookup Tablesを使用しません。
トランザクションv1は、Compute Budgetプログラムの設定命令をトランザクションヘッダー内のフィールドに置き換えます。初期のTransactionConfigMaskでは、トランザクションの以下の項目を宣言できます。
- lamports単位の優先手数料の合計
- コンピュートユニット上限
- ロード済みアカウントデータのサイズ上限
- 要求するヒープサイズ
セットされた各ビットは、対応する4バイトの設定値を示します。64ビットの優先手数料は、マスクの2つの位置を占有します。このマスクは、将来のトランザクションバージョンで拡張できるよう設計されています。
トランザクションの例
Solanaの各トランザクション形式を比較するため、最も単純で実用的な例として、あるアドレスから別のアドレスへのSOL送金を取り上げます。
このサンプルトランザクションには、単一の署名者、つまり送信者がいます。System Programを1回呼び出し、受信者へ1,000 lamportsを送金します。送信者、受信者、System Programという3つのアドレスを参照します。
レガシーおよびv0トランザクション
レガシーとv0トランザクションは、最上位のレイアウトがほぼ同じです。どちらも署名数で始まり、その直後に署名自体が続きます。その後にトランザクションのメッセージが続きます。単一署名者による送金では、最初のバイトは01で、署名が1つであることを示します。その後に送信者の64バイトEd25519署名が続きます。
メッセージには、3バイトのヘッダー、アカウントアドレス、直近のblockhash(有効期間指定子)、コンパイル済み命令が含まれます。
送信者はアドレス0、受信者はアドレス1、System Programはアドレス2です。送金命令はインデックス2のプログラムを参照し、アカウントインデックス00 01をプログラムへ渡します。
レガシー形式とv0形式の違いはわずかです。
- レガシーメッセージは3バイトのメッセージヘッダーから直接始まります。一方、v0メッセージはバージョンバイト
0x80で始まり、その後にヘッダーが続きます。 - v0では、命令の後にAddress Lookup Tableセクションも追加されます。この単純な例ではルックアップテーブルを使用しないため、このセクションはルックアップ数がゼロであることを示す1つの
00バイトで構成されます。
このSOL送金の例は、レガシートランザクションでは215バイト、v0では217バイトになります。v0では、バージョンプレフィックスに1バイト、空のルックアップテーブル配列に1バイトが追加されます。トランザクションの残りの部分は同一です。
送信者が署名するのはメッセージだけです。レガシーの例では、署名の対象はバイト65〜214です。v0ではバイト65〜216が対象となり、0x80メッセージバージョンプレフィックスから始まります。
以下の表は、v0トランザクション形式を使用した、2つのアドレス間のSOL送金例のバイトレイアウトを詳しく示しています。
| オフセット | サイズ | フィールド | 値の例 | 説明 |
| 0 | 1 B | 署名数 | 01 | 署名1つ、compact-u16 |
| 1–64 | 64 B | 署名0 | <Ed25519 signature> | 送信者の署名 |
| 65 | 1 B | バージョンプレフィックス | 80 | バージョン付きメッセージ、バージョン0 |
| 66–68 | 3 B | メッセージヘッダー | 01 00 01 | 必須署名者1、読み取り専用の署名済みアカウント0、読み取り専用の未署名アカウント1 |
| 69 | 1 B | 静的アカウント数 | 03 | インラインアカウント3つ |
| 70–101 | 32 B | アカウント0 | <sender pubkey> | 送信者/手数料支払者、書き込み可能な署名者 |
| 102–133 | 32 B | アカウント1 | <recipient pubkey> | 受信者、書き込み可能、未署名 |
| 134–165 | 32 B | アカウント2 | 11111111111111111111111111111111 | System Program、読み取り専用、未署名 |
| 166–197 | 32 B | 直近のblockhash | <recent blockhash> | トランザクションの有効期間 |
| 198 | 1 B | 命令数 | 01 | 命令1つ |
| 199 | 1 B | プログラムIDインデックス | 02 | System Program |
| 200 | 1 B | 命令アカウント数 | 02 | 命令アカウント2つ |
| 201–202 | 2 B | アカウントインデックス | 00 01 | 送信者、受信者 |
| 203 | 1 B | データ長 | 0C | 12バイト |
| 204–207 | 4 B | 送金ディスクリミネータ | 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 | アドレステーブルのルックアップ数 | 00 | ALTルックアップなし |
| 合計 | 217 B |
リソース設定
レガシーとv0トランザクションでは、リソース設定はCompute Budget Programへの命令として表現されます。一般的なものにはSetComputeUnitLimitとSetComputeUnitPriceがあります。必要に応じてSetLoadedAccountsDataSizeLimitとRequestHeapFrameも使用されます。
最小構成のトランザクション例には、これらの命令は含まれていません。代わりに、ランタイムがデフォルト値を提供します。
コンピュートユニット価格を指定しない場合、優先手数料はありません。ロード済みアカウントデータの上限には、ランタイムの最大値がデフォルトで適用されます。
トランザクションの取り込み時に、バリデータが命令リストを走査してCompute Budget命令を探す必要があるという非効率性は、更新されたv1形式を導入する動機の1つです。v1では、この情報が代わりにTransactionConfigMaskとConfigValuesへ移されます。
そもそも、トランザクションレベルの設定を命令として表現すること自体にも、一定のオーバーヘッドがあります。レガシーとv0には、要求するコンピュート量や優先手数料の専用フィールドがなかったため、これらの設定はCompute Budget Programを通じて後付けされました。トランザクションからそのプログラムIDを参照する必要があり、各設定が命令リストの容量を消費します。また、プログラム呼び出し自体はアプリケーション処理を行わないにもかかわらず、コンピュートを消費します。
v1では代わりに、これらの値をワイヤ形式内のネイティブな領域へ配置します。余分な命令のオーバーヘッドを回避し、リソース設定を低コストにするとともに、バリデータが容易に確認できるようにします。
トランザクションv1
v1トランザクションは、署名数と署名で始まるのではなく、バージョンバイト0x81から直接始まります。
次に3バイトのヘッダーが続き、その後に新しい4バイトのTransactionConfigMask、有効期間指定子(通常はblockhashですが、nonceの場合もあります)、命令数とアドレス数、アカウントアドレス、設定値、命令、最後に署名が続きます。
| オフセット | サイズ | フィールド | 値の例 | 説明 |
| 0 | 1 B | バージョンバイト | 81 | v1トランザクション |
| 1–3 | 3 B | レガシーヘッダー | 01 00 01 | 必須署名1、読み取り専用の署名済みアカウント0、読み取り専用の未署名アカウント1 |
| 4–7 | 4 B | トランザクション設定マスク | 0C 00 00 00 | 0x0000000C:ビット2と3がセットされ、2つの4バイト設定値を指定 |
| 8–39 | 32 B | 有効期間指定子 | <recent blockhash> | 直近のblockhash(またはnonce) |
| 40 | 1 B | 命令数 | 01 | System Program命令1つ |
| 41 | 1 B | アドレス数 | 03 | 送信者、受信者、System Program |
| 42–73 | 32 B | アドレス0 | <sender pubkey> | 送信者、手数料支払者、書き込み可能な署名者 |
| 74–105 | 32 B | アドレス1 | <recipient pubkey> | 受信者、書き込み可能な未署名アカウント |
| 106–137 | 32 B | アドレス2 | 11111111111111111111111111111111 | System Program、読み取り専用、未署名 |
| 138–141 | 4 B | 設定値:コンピュートユニット上限 | 10 27 00 00 | リトルエンディアンu32で10,000 CU(マスクのビット2に対応) |
| 142–145 | 4 B | 設定値:ロード済みアカウントデータのサイズ上限 | 00 00 01 00 | リトルエンディアンu32で65,536バイト/64 KiB(マスクのビット3に対応) |
| 146–149 | 4 B | 命令:ヘッダー | 02 02 0C 00 | プログラムインデックス2、アカウント2つ、データ12バイト |
| 150–151 | 2 B | 命令:アカウントインデックス | 00 01 | アドレス0=送信者、アドレス1=受信者 |
| 152–155 | 4 B | 命令:送金ディスクリミネータ | 02 00 00 00 | SystemInstruction::Transfer |
| 156–163 | 8 B | 命令:Lamports | 例:E8 03 00 00 00 00 00 00 | リトルエンディアンu64で1,000 lamports |
| 164–227 | 64 B | 署名0 | <Ed25519 signature> | バイト0〜155に対する送信者の署名 |
| 合計 | 228 B |
署名検証
署名検証は、トランザクションがバリデータへ到達した際に最初に実行される処理の1つです。無効な署名を持つトランザクションは、スケジューラへ到達する前にフィルタリングして破棄する必要があります。可変長メッセージの後ろに署名を追加するとアクセスが難しくなるように見えますが、実際にはそうではありません。
高コストなEd25519検証を実行する前に、バリデータが必要とするのは、トランザクションの各部分がどこで始まり、どこで終わるかを特定することだけです。v1形式は、この処理を低コストかつ決定論的に行えるよう設計されています。
最初のバイト0x81により、トランザクションがv1であることを即座に識別できます。後続のヘッダーにはnum_required_signaturesが含まれるため、バリデータは64バイトの署名がいくつあるべきかを最初から把握できます。
その後、AgaveのTransactionViewパーサーは、オフセットを記録しながらトランザクション内を前方へ走査します。固定サイズのフィールドを読み取り、NumAddressesを使用してアドレス配列をスキップします。
設定マスクを使用して設定セクションのサイズを判定し、各4バイトの命令ヘッダーを読み取って、対応する命令ペイロードのサイズを把握します。
パーサーが最後の命令ペイロードを通過すると、現在位置が署名オフセットとして記録されます。その位置以降では、Agaveは正確にnum_required_signatures × 64バイトを想定します。
実装では、署名数とパケット内の最初の署名のバイトオフセットを含むSignatureFrameを保存します。トランザクションの署名対象メッセージは、v1メッセージの先頭から記録された署名オフセットまでのバイト範囲です。そのためバリデータは、トランザクションを実行したりアカウントをロードしたりせずに、高コストなEd25519検証を実行できます。
v1では、署名配列の後に末尾バイトを置くことも禁止されています。パーサーが署名へ到達すると、残りのバイト数は署名数によって一意に決まります。SIMD-0385では、必須署名者ごとに正確に1つの64バイト署名があり、最後の署名以降にデータが存在しないことを求めています。
改善された命令解析
トランザクションv1のレイアウトでは、命令をより簡単に解析できます。レガシーとv0トランザクションで後方の命令を特定するには、パーサーは先行する命令を順に走査し、その長さをデコードして、各可変長ペイロードをスキップする必要があります。
v1では、固定幅の命令ヘッダーと可変長のペイロードを分離しています。 各命令はまず、プログラムアカウントインデックス、命令アカウント数、命令データ長を含む4バイトのヘッダーを持ちます。これらのヘッダーは、アカウントインデックスとデータペイロードより前にまとめられます。これにより、すべての命令の簡潔な説明を最初に取得できます。命令セクションのフレーム化とスキップが低コストになり、命令データを解析せずに末尾の署名配列のオフセットを判定しやすくなります。
トランザクション設定マスク
v1形式におけるもう1つの主要な追加要素は、4バイトのTransactionConfigMaskです。
レガシーとv0トランザクションでは、Compute Budget Programへの命令を通じてリソースを設定します。たとえば、トランザクションにはSetComputeUnitLimit、SetComputeUnitPrice、SetLoadedAccountsDataSizeLimit命令が含まれる場合があります。トランザクションのリソース要件や優先度を確認するバリデータは、これらの命令を見つけて解釈する必要があります。トランザクションv1では、この情報を命令ストリームからトランザクション形式自体へ移します。
マスクは32ビットのリトルエンディアンビットフィールドです。セットされた各ビットは、トランザクションの後半にあるConfigValuesセクション内の4バイトワードに対応します。
- ビット0と1:lamports単位の優先手数料の合計(8バイトのリトルエンディアン
u64としてエンコード) - ビット2:コンピュートユニット上限(4バイトの
u32) - ビット3:ロード済みアカウントデータのサイズ上限(4バイトの
u32) - ビット4:要求するヒープサイズ(4バイトの
u32)
注:初期のv1仕様で意味が割り当てられているのはビット0〜4だけです。残りのビットは現在未割り当てで、将来のトランザクションレベルの設定フィールド用に予約されています。
このマスクは、どのフィールドが存在し、何バイトを想定すべきかをパーサーへ伝える簡潔なスキーマとして機能します。たとえば、単純なSOL送金にはコンピュートユニット上限とロード済みアカウントデータのサイズ上限が必要ですが、優先手数料やカスタムヒープサイズは不要です。
これら2つのビットがセットされているため、トランザクションのアドレス配列の後に、正確に2つの4バイト設定値が続きます。この例では、20,000 CUの上限と64 KiBのロード済みアカウントデータ上限を要求します。
マスクは、最初の4バイト設定がビット2(コンピュートユニット上限)に属し、2番目がビット3(ロード済みアカウントデータ上限)に属することをバリデータへ伝えます。
レガシーとv0トランザクションでは、コンピュートユニット上限命令を省略すると、暗黙的なコンピュート予算がトランザクションに適用されます。v1では同じデフォルト値を使用しません。未設定のコンピュートユニット上限はゼロになり、未設定のロード済みアカウントデータのサイズ上限もゼロになります。32 KiBのデフォルト値が維持されるのはヒープだけです。そのため、v1トランザクションは必要なリソースを明示的に要求する必要があります。
これらの設定を明確に定義された設定領域へ移すことで、バリデータは命令を検索せず、トランザクションの取り込み時に必要な情報へ直接アクセスできます。また、Compute Budget Program命令はv1トランザクションを設定しなくなります。含まれている場合は無視され、コンピュートユニットを消費しながらno-op命令として処理されます。
これには、容量面での追加メリットもあります。レガシー/v0トランザクションでCompute Budget命令を追加する場合、通常は32バイトのCompute Budget Programアドレスをアカウントリストに含める必要があり、さらにシリアライズされた命令自体も必要です。
まとめ
Solanaの3つのトランザクション形式は、ネットワークの進化を反映しています。レガシーは当初のモデルを確立し、v0はALTによって拡張することで、1,232バイトの上限内でより多くのアカウントを扱えるようにしました。v1は、より大きなトランザクション、簡素な解析、インラインアドレス、ネイティブなリソース設定を軸に、ワイヤ形式をより根本的に再設計しています。
3つの形式はすべて引き続き有効ですが、それぞれがSolanaの発展における異なる段階を表しています。これらを通じて、ネットワーク上のアプリケーション、手数料市場、バリデータ要件の進化に合わせて、トランザクション形式がどのように適応してきたかが分かります。
関連リソース
- v1トランザクションとALTのトレードオフ - Umberto Natale、Solana Foundation
- バージョン付きトランザクション - Solana Docs
- トランザクションサイズの拡大 - SIMDディスカッションフォーラム
- より大きなトランザクションサイズ - Solanaアップグレード
関連記事
Heliusを購読
Solana開発の最新情報や新しい記事の公開通知を受け取れます


