新登場:Solana Privacy Ringsがmainnetで利用可能になりました
Solanaプログラミングモデル
ブログ/基礎

Solanaプログラミングモデル:Solana開発入門

Developer Experience EngineerXの0xIchigoLinkedInの0xIchigoGitHubの0xIchigo
読了時間:24分

この記事で扱う内容

Solanaの分散コンピューティングへのアプローチは、すべてをアカウントと呼ばれる固有のメモリ領域に保存するというシンプルな原則に基づいています。Solanaはグローバルなキーバリューストアとして動作し、公開鍵が対応するアカウントの一意な識別子となります。アカウントは状態を保存するため、Solanaの基盤です。プログラムからトークン残高まで、あらゆるものを保持します。トランザクションはアカウントを更新し、状態の変化を反映するために使用されます。

この記事では、Solanaアーキテクチャの複雑さを掘り下げます。まずクラスターと状態の概念を概観し、次にSolanaの基礎要素であるアカウントとプログラムの役割を説明します。その後、トランザクションによってアカウントとプログラム間の動的なやり取りがどのように可能になるかを見ていきます。

この記事を読み終える頃には、Solanaのプログラミングモデルを深く理解できるようになります。クラスターのアーキテクチャ、データ保存におけるアカウントの重要な役割、トランザクションがアカウントデータを更新する仕組みを把握できます。さらに、家賃システムやバージョン付きトランザクションなど、Solana固有の機能も学びます。

Solanaクラスターとは?

Solanaアーキテクチャの中核にあるのがクラスターです。これは、トランザクションを処理し、単一の台帳を維持するために連携するバリデーターの集合です。Solanaには、それぞれ特定の目的を持つ複数の独立したクラスターがあります。

  • Localhost:デフォルトのポート8899で稼働するローカル開発クラスターです。Solanaコマンドラインインターフェース(CLI)には組み込みのテストバリデーターが含まれ、エアドロップやレート制限を必要とせず、開発者個人のニーズに合わせてカスタマイズできます
  • Devnet:Solana上でのテストや実験に使える、実害のないサンドボックス環境です
  • Testnet:Solanaのコアコントリビューターが、mainnetへの導入前に新しい更新や機能を試すためのテスト環境です。パフォーマンステストを実行したい開発者にも使用されます
  • Mainnet Beta:現実のトランザクションが発生する、稼働中のパーミッションレスクラスターです。ユーザー、開発者、トークン保有者、バリデーターが日々やり取りする「本番」のSolanaです

各クラスターは互いをまったく認識せず、独立して動作します。各運用環境の整合性を守るため、誤ったクラスターに送信されたトランザクションは拒否されます。

クラスターを、データの巨大な単一ヒープとして想像してください。コンピューターサイエンスでヒープとは、データを動的に保存・変更できるメモリ領域を指します。ただし、クラスターが文字どおりヒープデータ構造を使用しているわけではありません。この比喩は、クラスターが必要に応じて割り当てと解放が可能な複数のメモリ領域で構成されることを理解するための概念的な手がかりです。クラスターを動的なヒープとして捉えることは、ネットワーク内でデータがどのように管理、アクセス、保護されるかを理解するうえで重要です。

この巨大な単一データヒープは、一種のデジタル倉庫とも考えられます。データは棚に置かれた箱のようなもので、それぞれに固有のラベルがあり、移動や内容変更について明確なルールがあります。これにより、許可された移動や変更だけが可能な、安全で秩序あるシステムが保たれます。

Solanaでプログラムと呼ばれるスマートコントラクトには、管理可能な倉庫、つまりヒープ内の固有領域が割り当てられます。プログラムは倉庫内のどの領域からでも読み取れますが、所有していない領域の内容を変更するには所定の権限が必要です。例外として常に許可されるのは、Solanaのネイティブ暗号資産であるlamportを倉庫内の任意の領域へ送ることです。

プログラムも含め、すべての状態はこのヒープに存在します。各領域にはそれを所有し、管理するプログラムがあります。たとえばプログラムは、オンチェーンプログラムの読み込み、デプロイ、アップグレードを担うプログラムであるBPFLoaderによって所有されます。こうしたメモリ領域、つまりデジタル倉庫の箱をアカウントと呼びます。

アカウントとは?

Solana上のすべてはアカウントです。アカウントは、コンピューター上のファイルのようにデータを永続的に保持するコンテナだと考えてください。アカウントはSolanaのプログラムモデルを構成する基本要素であり、状態(アカウント残高、所有権情報、プログラムを保持しているかどうか、家賃情報など)の保存に使用されます。

Solanaには3種類のアカウントがあります。

  • データを保存するアカウント
  • 実行可能プログラムを保存するアカウント
  • ネイティブプログラムを保存するアカウント

これらのアカウントは、機能に基づいてさらに次のように分類できます。

  • 実行可能アカウント - コードを実行できるアカウントです
  • 実行不可能アカウント - コードを実行する機能を持たず、データ保存に使用されるアカウントです(コードを一切保持していないためです)

上の図には、実行可能アカウントと実行不可能アカウントの例がいくつか示されています。実行可能アカウントでは、BubblegumがProgram Accountの一例です。これは圧縮NFTの作成と管理に使われるMetaplexのプログラムです。Vote ProgramはNative Program Accountの一例です。バリデーターの投票状態と報酬を追跡するアカウントの作成と管理に使用されます。Program AccountとNative Program Accountの違いは、プログラムとは?のセクションで説明します。現時点では、Solanaに複数種類の実行可能アカウントがあることを把握しておくことが重要です。

さらに、すべての実行不可能アカウントはData Accountに分類できます。Data Accountの例は次のとおりです。

  • Associated Token Account - 特定のトークン、その残高、所有者に関する情報を保持するアカウントです(例:Aliceが10 USDCを保有)
  • System Account - System Programによって作成、所有されるアカウントです
  • Stake Account - 報酬獲得を目的としてトークンをバリデーターに委任するためのアカウントです

アカウントの構造

アカウントはAccountInfo構造体に従って構成されます。

コード
pub struct AccountInfo<'a> {
    pub key: &'a Pubkey,
    pub lamports: Rc>,
    pub data: Rc>,
    pub owner: &'a Pubkey,
    pub rent_epoch: Epoch,
    pub is_signer: bool,
    pub is_writable: bool,
    pub executable: bool,
}

アカウントは、一意の32バイト公開鍵であるアドレス(key)によって識別されます。

lamportsフィールドには、このアカウントが所有するlamportの数量が格納されます。1 lamportは、Solanaのネイティブトークンである1 SOLの10億分の1です。

dataは、このアカウントに保存される生のデータバイト配列を指します。デジタル資産のメタデータからトークン残高まで、あらゆる情報を保存でき、プログラムによる変更が可能です。

ownerフィールドには、プログラムアカウントのアドレスで表される、このアカウントの所有者が格納されます。アカウントの所有権にはいくつかのルールがあります。

  • アカウントのデータを変更し、lamportを引き出せるのは、そのアカウントの所有者だけです
  • 誰でもアカウントにlamportを入金できます
  • アカウントデータがゼロにリセットされていれば、アカウントの所有者は所有権を新しい所有者へ移転できます

is_signerフィールドは、対象アカウントの所有者がトランザクションに署名したかどうかを示すブール値です。つまり、トランザクションに関与するプログラムに、そのアカウントが署名者かどうかを伝えます。署名者であることは、そのアカウントが公開鍵に対応する秘密鍵を保持し、提案されたトランザクションを承認する権限を持つことを意味します。

is_writableフィールドは、アカウントのデータを変更できるかどうかを示すブール値です。Solanaでは並列処理を可能にするため、トランザクション内でアカウントを読み取り専用として指定できます。ランタイムは、異なるプログラムが読み取り専用アカウントへ同時にアクセスすることを許可する一方、トランザクションの処理順序を用いて書き込み可能アカウントへの潜在的な書き込み競合を処理します。これにより、競合しないトランザクションだけが並列に処理されます。

executableフィールドは、アカウントが命令を処理できるかどうかを示すブール値です。つまり、プログラムはアカウント内に保存されています。これについては次のセクションで詳しく説明します。その前に、家賃の概念を取り上げます。

rent_epochフィールドは、このアカウントに次回の家賃支払い義務が生じるエポックを示します。エポックとは、リーダースケジュールが有効なスロット数です。OSの従来のファイルとは異なり、Solana上のアカウントの存続期間はlamportの量で表されます。アカウントの存続がlamport残高に左右されるという考え方から、家賃の概念が生まれます。

家賃

家賃は、Solana上でアカウントを存続させ、バリデーターのメモリに保持するために発生するストレージコストです。家賃の徴収は、リーダースケジュールが有効なスロットによって定義される時間単位であるエポックに基づいて評価されます。家賃の仕組みは次のとおりです。

  • 家賃の徴収 - 家賃はエポックごとに1回徴収されます。トランザクションがアカウントを参照した際にも徴収される場合があります
  • 家賃の分配 - 徴収された家賃の一部はバーンされ、流通から永久に除外されます。残りは各スロットの後にVote Accountへ分配されます
  • 家賃の支払い - アカウントに家賃を支払うだけのlamportがない場合、ガベージコレクションと呼ばれる処理でデータが削除され、アカウントの割り当てが解除されます
  • 家賃免除 - 2年分の家賃支払いに相当する最低残高を維持すると、アカウントは家賃免除になります。すべての新規アカウントは、アカウントサイズに応じて決まる家賃免除基準を満たす必要があります
  • 家賃の回収 - ユーザーはアカウントを閉じて、残っているlamportを回収できます。これにより、アカウントに保管された家賃を取り戻せます

特定のアカウントサイズに対する家賃は、getMinimumBalanceForRentExemption RPCエンドポイントで見積もれます。Helius API Playgroundでは、アカウントのデータ長をusizeで指定するだけで簡単に計算できます。Solana rent CLIサブコマンドでも、アカウントが家賃免除になるために必要なSOLの最小額を見積もれます。たとえばこの記事の執筆時点では、コマンドsolana rent 20000を実行すると、Rent-exempt minimum: 0.14009088 SOLが返されます。

Solana上のアドレス

Solanaには実際には2「種類」のアドレスがあります。Solanaはアドレス作成に、SHA-512(SHA-2)とCurve22519楕円曲線を使用するEdDSA署名方式のed25519を採用しています。これにより、主要なアドレス形式として機能する32バイトの公開鍵が生成されます。ハッシュ化されていないため、そのまま使用できます。

有効なアドレスは、ed25519曲線上の点でなければなりません。ただし、すべてのアドレスをこの曲線から導出する必要はありません。Program Derived Address(PDA)は曲線外で生成されるため、対応する秘密鍵を持たず、署名には使用できません。PDAはSystem Programを介して作成され、プログラムがアカウントを管理する必要がある場合に使用されます。これは、Solanaに異なる種類のアドレスがあることを読者に知ってもらうための補足です。PDAについては今後の記事で取り上げます。

SolanaのアカウントはEthereumのアカウントとどう違うのか?

Ethereumには、外部所有アカウント(EOA)とコントラクトアカウントという2つの主要なアカウントタイプがあります。EOAは秘密鍵で制御されます。一方、コントラクトアカウントはコントラクトコードによって管理され、単独でトランザクションを開始することはできません。

EOAとコントラクトアカウントは、どちらも同じアカウント構造に従います。

  • 残高 - すべてのアカウントにEther建ての残高があります
  • Nonce - EOAでは、そのアカウントから送信されたトランザクション数です。コントラクトでは、そのアカウントが作成したコントラクト数です
  • Storage Root - アカウントのストレージ内容をエンコードしたMerkle Patricia Trieのルートノードを表す256ビットハッシュです
  • CodeHash - コントラクトのEthereum Virtual Machine(EVM)コードのハッシュです。これは不変であり、状態は変化しても、一度作成されたコードは変わりません。ただし、プロキシパターンの使用など、Ethereumでコントラクトをアップグレードする例外もありますが、本記事の範囲外です。EOAにはコードが含まれないため、空文字列のハッシュになります

Solanaは、どのアカウントもプログラムになり得る、より統一的なアカウントモデルを採用しています。コードとデータを分離することで、効率的かつ柔軟な環境を実現します。Solanaのプログラムはステートレスであり、重複してデプロイすることなく、さまざまなData Accountと連携します。これは、ユーザーが異なるプログラム間で資産を移動せずに複数のプロトコルを利用したい分散型金融(DeFi)アプリケーションで特に有利です。対照的に、Ethereumのプログラミングモデルはコードと状態を単一のエンティティに統合します。そのため、やり取りが複雑になり、状態変更に必要なガスによってコストが高くなる可能性があります。

以前のSolanaアカウントは家賃を支払う必要があり、稼働し続けるには最低残高を保持する必要がありました。これにより、未使用または資金不足のアカウントが最終的にネットワークによって回収され、状態の肥大化が抑えられます。最近の更新により、mainnetには家賃を支払うアカウントが存在しなくなり、すべてのアカウントが家賃免除であることが必須になりました。一方、Ethereumはリソース割り当ての管理にガスを使用します。このモデルでは、コントラクトストレージは明示的に消去されない限り永続します。Solanaの方式は状態保存のコストを予測しやすくしますが、Ethereumのコストは変動し、ネットワーク混雑時には非常に高額になる場合があります。

次のセクションでは、Solanaがプログラムロジックを状態から分離する仕組みを見ていきます。Ethereumのプログラミングモデルと比較しながら、このモジュール型アプローチが、開発者に透明で予測可能なコスト構造を提供しつつ、オンチェーン処理を効率化する仕組みを説明します。

Solanaプログラムとは?

プログラムはBPF Loaderが所有する実行可能アカウントです。トランザクションとプログラムロジックを処理するよう設計されたSolana Runtimeによって実行されます。

Solanaのプログラミングモデルを特徴づける要素の1つが、コードとデータの分離です。プログラムはステートレスであり、内部に状態を保存しません。代わりに、処理に必要なすべてのデータは別のアカウントに保存され、トランザクションを介して参照としてプログラムに渡されます。この設計により、単一の汎用的なプログラムデプロイメントで異なるアカウントとやり取りできます。

Solana上のプログラムには、次の機能があります。

  • 追加のアカウントを所有する
  • 他のアカウントから読み取る、または他のアカウントに入金する
  • 所有するアカウントのデータを変更する、または出金する

プログラムには2種類あります。

  • オンチェーンプログラム - ユーザーが作成し、Solanaにデプロイするプログラムです。通常はプログラムをデプロイしたアカウントであるアップグレード権限者がアップグレードできます
  • ネイティブプログラム - Solanaのコアに統合されたプログラムです。バリデーターの動作に必要な基本機能を提供します。ネイティブプログラムは、ネットワーク全体のソフトウェア更新を通じてのみアップグレードできます。代表例にはSystem Program、BPF Loader Program、Vote Programがあります。

オンチェーンプログラムとネイティブプログラムは、どちらもユーザーや他のプログラムから呼び出せます。主な違いはアップグレード方法です。オンチェーンプログラムはアップグレード権限者が更新できますが、ネイティブプログラムはクラスター更新の一部としてのみ更新できます。

Solana Labsは、Solana Program Libraryと呼ばれる厳選されたオンチェーンプログラム群を管理しています。このライブラリは、トークンの貸し出しやステークプールの作成など、さまざまなオンチェーン処理を容易にします。たとえばAssociated Token Account Programは、ユーザーのウォレットを対応するToken Accountに関連付けるための標準と仕組みを定めています。また、SPLは動的に進化しています。Token-2022などのプログラムは、Token Programが提供する機能を基盤として拡張しています。

Solanaでのプログラム開発は通常、Anchorを利用してRustで行われます。Anchorは、定型コードを減らし、シリアライズとデシリアライズを効率化することでプログラム作成を簡素化する規約重視のフレームワークです。Rustが推奨されますが、開発者はRustだけに限定されません。C、C++、LLVMのBPFバックエンド(プログラムをBPFバイトコードへコンパイルできるLLVMのコンポーネント)をターゲットにする任意の言語を使用できます。SolangとNeon Labsの最近の進展により、プログラム開発でSolidityも使用できるようになりました。

プログラムは通常、LocalhostとDevnetで開発、テストした後、TestnetまたはMainnet Betaへデプロイします。開発者はSolana CLIからsolana program deploy <path to program>コマンドを使ってプログラムをデプロイできます。プログラムは、BPFバイトコードを含むELF共有オブジェクトにコンパイルされた後、指定したSolanaクラスターへアップロードされます。デプロイ済みプログラムはexecutableとマークされたアカウントに格納され、そのアカウントのアドレスがprogram_idになります。

当初、Solana上のプログラムは、プログラムサイズの2倍のアカウントを使ってデプロイされていました。Solanaの1.16アップデートでは、サイズ変更可能なアカウントのサポートが導入されました。これにより、開発者は柔軟にリソースを割り当てられます。現在は、小さなアカウントでプログラムをデプロイし、後からサイズを拡張できます。

前述のとおり、プログラムがやり取りするデータはすべて、参照として渡される別のアカウントに保存されるため、プログラムはステートレスと見なされます。すべてのプログラムには命令を処理する単一のエントリーポイントがあり、program_id、アカウントの配列、バイト配列としての命令データを受け取ります。プログラムは、トランザクションによって呼び出されるとSolana Runtimeにより実行されます。

トランザクションとは?

トランザクションはオンチェーン活動の基盤です。プログラムの呼び出しと状態変更を実行する仕組みとして機能します。Solanaのトランザクションは、どのアカウントに対してどの処理を実行すべきか、またその処理に必要な権限があるかをバリデーターに伝える命令の集合です。

トランザクションは、主に3つの要素で構成されます。

  • 読み取りまたは書き込み対象となるアカウントの配列
  • 1つ以上の命令
  • 1つ以上の署名

SolanaのトランザクションはTransaction構造体に従います。この構造体は、ネットワークが処理を実行、検証するために必要な情報を提供します。定義は次のとおりです。

コード
pub struct Transaction {
    pub signatures: Vec,
    pub message: Message,
}

signaturesフィールドには、シリアライズされたMessageに対応する署名の集合が格納されます。各署名は、手数料支払者を先頭として、Messageのaccount_keysリストに含まれるアカウントキーに関連付けられます。手数料支払者は、トランザクション処理時に発生する手数料を負担するアカウントです。通常はトランザクションを開始したアカウントです。必要な署名数は、メッセージのMessageHeaderで定義されるnum_required_signaturesと同じです。

message自体はMessage型の構造体です。次のように定義されます。

コード
pub struct Message {
    pub header: MessageHeader,
    pub account_keys: Vec,
    pub recent_blockhash: Hash,
    pub instructions: Vec,
}

メッセージのheaderには、符号なし8ビット整数が3つ格納されます。必要な署名数(num_required_signatures)、読み取り専用の署名者数、読み取り専用の非署名者数です。

account_keysフィールドには、トランザクションに関与するすべてのアカウントアドレスが列挙されます。読み書きアクセスを要求するアカウントが先に並び、その後に読み取り専用アカウントが続きます。

recent_blockhashは、32バイトのSHA-256ハッシュを含む最近のブロックハッシュです。クライアントが最後に台帳を確認した時点を示し、最近のトランザクションの有効期間として機能します。バリデーターは古いブロックハッシュを持つトランザクションを拒否します。また、最近のブロックハッシュを含めることで、過去のものと完全に同一のトランザクションが拒否されるため、重複トランザクションを防げます。何らかの理由でネットワークへの送信よりもかなり前にトランザクションへ署名する必要がある場合は、最近のブロックハッシュの代わりに永続的なトランザクションnonceを使用し、一意のトランザクションであることを保証できます。

instructionsフィールドには1つ以上のCompiledInstruction構造体が格納され、それぞれがネットワークのバリデーターに実行させる特定の処理を指定します。

命令

命令は、Solanaプログラムを1回呼び出すための指示です。プログラムにおける実行ロジックの最小単位であり、Solana上で最も基本的な処理単位として機能します。プログラムは命令から渡されたデータを解釈し、指定されたアカウントを操作します。Instruction構造体は次のように定義されます。

コード
pub struct Instruction {
    pub program_id: Pubkey,
    pub accounts: Vec,
    pub data: Vec,
}

program_idフィールドは、実行するプログラムの公開鍵を指定します。これは命令を処理するプログラムのアドレスです。この公開鍵で示されるプログラムアカウントの所有者は、プログラムの初期化と実行を担うローダーを指定します。ローダーは、デプロイ後のオンチェーンSolana Bytecode Format(SBF)プログラムを実行可能としてマークします。Solanaのランタイムは、実行可能としてマークされていないアカウントを呼び出そうとするトランザクションを拒否します。

accountsフィールドには、命令が読み取りまたは書き込みを行う可能性のあるアカウントが列挙されます。これらのアカウントはAccountMeta値として渡す必要があります。命令によってデータが変更される可能性があるアカウントは、書き込み可能として指定しなければトランザクションが失敗します。これは、プログラムが所有していない、または必要な権限を持たないアカウントには書き込めないためです。アカウントのlamportを変更する場合も同様です。プログラムが所有していないアカウントからlamportを差し引くとトランザクションは失敗しますが、任意のアカウントにlamportを追加することはできます。accountsフィールドには、プログラムが読み書きしないアカウントも指定できます。これはランタイムによるプログラム実行のスケジューリングに影響を与えるためのもので、それ以外の場合は無視されます。

dataは、プログラムに渡す入力として機能する、汎用的な符号なし8ビット整数のベクターです。プログラムが実行するエンコード済み命令を格納する重要なフィールドです。

Solanaは命令データの形式に依存しません。ただし、bincodeとborsh(Binary Object Representation Serializer for Hashing)によるシリアライズを標準でサポートしています。シリアライズとは、複雑なデータ構造を、送信または保存可能な連続したバイト列へ変換する処理です。すべてオンチェーンで行われるため、データのエンコード方法を選ぶ際はデコードのオーバーヘッドを考慮する必要があります。Borshは仕様が安定し、JavaScript実装があり、一般に効率も高いため、bincodeより好まれることがよくあります。

プログラムは、サポートする命令の構築を効率化するためにヘルパー関数を使用します。たとえばSystem Programは、SystemInstruction::Assign命令を構築するヘルパー関数を提供します。

コード
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
    let account_metas = vec![AccountMeta::new(*pubkey, true)];
    Instruction::new(
        system_program::id(),
        &SystemInstruction::Assign { owner: *owner },
        account_metas,
    )
}

この関数は、処理されると指定されたアカウントの所有者を、渡された新しい所有者へ変更する命令を構築します。

1つのトランザクションには複数の命令を含めることができ、列挙された順に逐次かつアトミックに実行されます。つまり、すべての命令が成功するか、すべて失敗するかのどちらかです。また、命令の順序が重要になる場合もあります。潜在的な悪用を防ぐため、プログラムは考えられるあらゆる命令シーケンスを安全に処理できるよう堅牢化する必要があります。

たとえば初期化解除時に、プログラムがlamport残高をゼロに設定してアカウントを初期化解除しようとする場合があります。これはSolanaランタイムがアカウントを削除するという前提に基づきます。この前提はトランザクション間では正しいものの、命令間やCross-Program Invocation間では正しくありません(Cross-Program Invocationについては今後の記事で説明します)。初期化解除処理におけるこの潜在的な欠陥へ対処するため、プログラムはアカウントデータを明示的にゼロにする必要があります。そうしなければ、攻撃者は後続の命令を発行し、トランザクション完了前にアカウントを再利用するなど、削除されたという誤った前提を悪用できます。

バージョン付きトランザクションとは?

Solanaのトランザクションは、クラスター全体で高速かつ信頼性の高いデータ転送を保証するため、IPv6の最大伝送単位(MTU)標準を使用します。Solanaのネットワークスタックは、保守的な1280バイトのMTUサイズを採用しています。ヘッダー用の領域を確保した後、パケットデータには1232バイトを使用できます。そのため、Solanaのトランザクションはこのサイズに制限されます。

このサイズ制約はさまざまなネットワーク改善を可能にする一方、単一トランザクションで実行できる処理の複雑さを制限します。各アカウントアドレスが32バイトのストレージを占めるため、命令を含まないトランザクションでも保存できるアカウントは最大35個です。この制限は、単一トランザクション内で署名不要のアカウントを35個より多く必要とするユースケースに課題をもたらします。

これに対処するため、複数バージョンのトランザクション形式をサポートする新しい形式が導入されました。Solanaランタイムは現在、2つのトランザクションバージョンをサポートしています。

  • legacy - 元のトランザクション形式です
  • 0(Version 0) - Address Lookup Tableをサポートする最新のトランザクション形式です

Version 0は、Address Lookup Table(ALT)をサポートするためにリリースされました。これは、アカウントアドレスをオンチェーンのテーブル状データ構造に保存する仕組みです。このテーブルはアカウントアドレスを保存する独立したアカウントであり、トランザクションでは1バイトのu8インデックスを使って参照できます。各アカウントに32バイトではなく1バイトだけを使用すればよいため、トランザクションのサイズが大幅に縮小します。ALTは、DeFiアプリケーションで一般的な、多数のアカウントを伴う複雑な処理に特に有用です。

この図はSolana Cookbookのバージョン付きトランザクションのセクションを基に作成しました

「バージョン付きトランザクション」という用語は、Solanaがレガシー形式とVersion 0形式の両方をサポートする方式を指します。このアプローチにより、ランタイムの改善を取り入れながらコンポーザビリティを維持できます。

バージョン付きトランザクションの構造

VersionedTransactionは次のように定義されます。

コード
pub struct VersionedTransaction {
    pub signatures: Vec,
    pub message: VersionedMessage,
}

signaturesフィールドは、トランザクションの署名者による署名のリストです。トランザクションを認証し、その整合性を維持します。messageはトランザクションの実際の内容です。これは、レガシーメッセージとVersion 0メッセージの両方を処理する薄いenumラッパーであるVersionedMessage型によってカプセル化されます。

コード
pub enum VersionedMessage {
    Legacy(Message),
    V0(Message),
}

メッセージのバージョンは、シリアライズ処理の先頭ビットで決まります。先頭ビットが設定されている場合、残りの7ビットを使って、Version 0から始まるどのMessageバージョンがシリアライズされているかを判定します。先頭ビットが設定されていない場合は、すべてのバイトがレガシーMessage形式のエンコードに使用されます。これは、同名のMessage構造体が2つ存在し、異なるモジュールであるlegacyとv0に分かれているためです。

Messageは、トランザクションを圧縮した内部形式です。ネットワーク送信とランタイムによる操作に使用されます。トランザクションの命令が使用するすべてのアカウントの線形リスト、アカウント配列の構造を示すMessageHeader、最近のブロックハッシュ、メッセージ命令のコンパクトなエンコーディングが含まれます。v0 Message構造体は次のようになります。

コード
pub struct Message {
    pub header: MessageHeader,
    pub account_keys: Vec,
    pub recent_blockhash: Hash,
    pub instructions: Vec,
    pub address_table_lookups: Vec,
}

レガシーメッセージとv0メッセージの違いは、address_table_lookupsフィールドを含むかどうかです。

プログラミングモデルとSolanaのトランザクションフローの統合

Solanaのプログラミングモデルは、アカウントおよびトランザクションシステムと深く統合されています。各概念は次のように結びついています。

  • 状態としてのアカウント - Solana上のアカウントは、プログラムの状態コンテナとして機能します。プログラミングモデルの中心となるのは、命令に応じてこれらのコンテナに保存されたデータを変更することです
  • 命令 - プログラムは、トランザクションに含まれる命令を処理するロジックを定義します。命令はアカウントデータとやり取りする、実行可能な構成要素です
  • シリアライズと処理 - トランザクションがシリアライズされると、プログラムの命令によってアカウントの状態変更が決まります。シリアライズ処理は、レガシー形式とVersion 0形式のどちらを使用する場合でも、プログラムの設計に従います
  • アトミック性 - Solanaのプログラミングモデルは命令処理のアトミック性を保証します。プログラムは、同時実行されるトランザクションを安全かつ効率的に処理できるよう設計する必要があります
  • スケーラビリティ - Solanaのプログラミングモデルは、Address Lookup Table(ALT)などの機能によってスケーラビリティを実現します。これらのテーブルはトランザクションサイズを縮小し、トランザクションが参照できるアカウント数を増やします

Solanaのプログラミングモデルは、単にコードを書くためのものではありません。そのコードが広範なエコシステム内でどう機能するかを理解することが重要です。アカウントはこのモデルに不可欠であり、ネットワーク上でデータを保存、変更する主要な手段です。トランザクションは、どのデータを作成、更新、削除する必要があるかをバリデーターに伝え、オンチェーン活動を可能にします。開発者がSolanaエコシステム内でパフォーマンスと連携を最適化したアプリケーションを構築するには、これらを深く理解することが不可欠です。

まとめ

お疲れさまでした。この記事では、Solanaのシステムアーキテクチャの複雑さをたどり、クラスターを巨大な単一データヒープとして捉える概念を解説しました。このヒープがアカウントと呼ばれる個別のメモリ領域に整理され、Solanaのプログラミングモデルの基盤を形成する仕組みも確認しました。アカウントにはユーザーのトークンからネットワークの動作を定義するプログラムまで、あらゆるものが保存され、すべてトランザクションを通じて変更されます。

開発者にとって、Solanaの分散コンピューティングへのアプローチを理解することは重要です。Solanaの能力を最大限に活用するアプリケーションを構築するには、アカウント、プログラム、トランザクションの詳細を把握する必要があります。これは、コードと状態が分離されたシステムを理解することでもあります。その結果、ステートレスなプログラムがアカウントを介してデータとやり取りし、かつてない規模のコンポーザビリティとアップグレード可能性を実現します。

投資家や一般ユーザーにとっても、Solanaの設計が堅牢で柔軟かつ効率的なエコシステムを生み出す仕組みを理解することは、プラットフォームの実用性と、Solanaでのみ可能な革新的アプリケーションを育む能力を評価するうえで重要です。

ここまで読んでくださった皆さん、ありがとうございます。さらに深く学ぶ準備はできましたか?今すぐDiscordに参加して、Solanaでのプログラミングを始めましょう。

その他のリソース/参考資料

Heliusを購読

Solana開発の最新情報や新しい記事の公開通知を受け取れます

拡大画像