概要
プログラムはLLVMを通じてsBPFにコンパイルされ、トランザクションあたり140万CUのバジェットを持つサンドボックス化されたVMで実行されます。ランタイムは最大512個のコンパイル済みプログラムをキャッシュし、ログ記録、CPI、暗号化、メモリ用のsyscallを提供し、新しいデプロイを1 slot遅延させます。
コンパイル
SolanaはLLVMを使用してプログラムをSolana Bytecode Format(sBPF)を含むELFバイナリにコンパイルします。ELFバイナリは実行可能アカウントとしてオンチェーンに保存されます。
sBPFはSolanaランタイム向けにカスタマイズされた、Solana独自のeBPFバイトコードの派生版です。標準のeBPFではなく、Solana固有の変更が加えられています。
プログラムの作成
Solanaプログラムは主にRustで、以下の3つのアプローチのいずれかを使用して記述されます:
Anchor
ボイラープレートを削減するためにRustマクロを使用するフレームワークです。ほとんどの開発者に推奨されます。
Pinocchio
コンピュート消費とバイナリサイズに最適化された軽量のゼロコピーRustライブラリです。一般的なCPI向けのプログラム固有クレートを含みます。
Native Rust
フレームワークを使用しない直接Rustです。完全な制御が可能ですが、より多くの手動実装が必要です。
これらのアプローチ全体に適用されるCPIの概念と例については、Cross-Program Invocationsを参照してください。
プログラム実行モデル
トランザクションが処理されると、ランタイムはprocess_message()を通じて各instructionsを順次実行します。各instructionsに対して、ランタイムは以下を行います:
-
instructionsコンテキストの準備。
prepare_next_top_level_instruction()を呼び出して、instructionsのアカウントインデックスをマッピングし、署名者および書き込み可能フラグを設定し、TransactionContextを構成します。 -
プリコンパイルの確認。 プログラムがプリコンパイルである場合、ランタイムは
process_precompile()を呼び出します。これはスタックフレームのプッシュとポップ(_rspush()_および_rspop()_経由)を行いますが、sBPF VMとプログラムキャッシュのルックアップをバイパスし、ネイティブコードを直接実行します。 -
スタックフレームのプッシュ。(ステップ3〜6は_rs
process_message()_から呼び出されるInvokeContext::process_instruction()およびprocess_executable_chain()の内部で実行されます。)InvokeContextに対してpush()を呼び出します。これによりinstructionsスタックの高さがインクリメントされ、再入性ルールが適用されます:プログラムが自身に再入できるのは、直近の呼び出し元(instructionsスタックの現在のトップにあるプログラム)が同じプログラムである場合のみです。深い自己再帰(A -> A -> A)はスタック深度制限に従い許可されます。その他の再入パターン(例:AがBを呼び出し、BがAを呼び出す)は_rsInstructionError::ReentrancyNotAllowed_を返します。 -
プログラムの解決。 ランタイムは
process_executable_chain()を呼び出し、ローダーを特定します。program accountのオーナーがネイティブローダーである場合、プログラムはビルトインであり、そのエントリーポイント関数はProgramCacheForTxBatchから直接ルックアップされます。オーナーがBPFローダーのいずれか(bpf_loader_deprecated、bpf_loader、bpf_loader_upgradeable、またはloader_v4)である場合、ローダー自身のビルトインエントリーポイントが代わりに呼び出されます。 -
BPFプログラムの実行。 BPFプログラムの場合、ローダーエントリーポイントはプログラムキャッシュからコンパイル済み実行ファイルをルックアップします。
execute()関数は以下を実行します:- アカウントデータをフラットなパラメータバッファにシリアライズする
- スタック、ヒープ、メモリ領域を持つsBPF VMを作成する
- コンパイル済みコードを実行し、実行中にコンピュートユニットを消費する。バジェットを超過した場合は_rs
ComputationalBudgetExceeded_を返す。 - バッファからアカウントデータをデシリアライズしてアカウント状態に戻す
-
スタックフレームのポップ。
pop()を呼び出します。これによりinstructionsがランタイムの会計ルールに違反していないことを検証します(lamport残高が均衡していること、読み取り専用アカウントが変更されていないこと、アカウントデータサイズが制限内であること)。 -
コンピュートユニットの累積。 instructionsによって消費されたコンピュートユニットは
saturating_addを通じてトランザクション合計に加算されます。
プログラムキャッシュ
ランタイムは検証済みおよびコンパイル済みプログラムを保存するグローバルなProgramCacheを維持します。これはフォークグラフを認識し、デプロイの可視性ルール、エビクション、およびepoch境界での再コンパイルを処理します。
キャッシュエントリーの種類
キャッシュされたすべてのプログラムには、ランタイムの動作を決定するProgramCacheEntryTypeがあります:
| 種類 | 説明 |
|---|---|
Loaded | 検証済みおよびコンパイル済みプログラム。実行準備完了。 |
Builtin | validatorバイナリにコンパイルされたネイティブプログラム(System、Stake、Voteなど)。オンチェーンには保存されない。 |
Unloaded | コンパイル済み実行ファイルがスペース確保のためメモリからエビクトされた、以前に検証済みのプログラム。使用統計は引き続き追跡される。再検証なしで再ロード可能。 |
FailedVerification | 現在の機能セットでsBPF検証器を通過しなかったプログラムのトゥームストーン。機能のアクティベーションにより検証ルールが変更された場合、Loadedになる可能性がある。 |
Closed | 明示的にクローズされたか、一度もデプロイされていないプログラムのトゥームストーン。ローダーに属するが実行可能コードを含まないアカウント(バッファアカウントなど)にも使用される。 |
DelayVisibility | ProgramCacheForTxBatch::find()がLoadedエントリーは存在するがまだ有効でない場合(そのeffective_slotが未来にある場合)に返す合成トゥームストーン。キャッシュに直接保存されることはない。 |
可視性の遅延
新たにデプロイまたはアップグレードされたプログラムはすぐには有効になりません。DELAY_VISIBILITY_SLOT_OFFSET定数は1であり、slot Nにデプロイされたプログラムはslot N+1で有効になることを意味します。デプロイされたslot中、新しいバージョンを呼び出そうとすると_rsDelayVisibility_が返され、ランタイムは「Program is not deployed.」と報告します。
エビクションポリシー
キャッシュは最大MAX_LOADED_ENTRY_COUNT(512)個のコンパイル済みプログラムエントリーを保持します。上限に達すると、最も使用頻度の低いプログラムが_rsUnloaded_状態にエビクトされます。使用状況はtx_usage_counter(トランザクションがプログラムを参照するたびにインクリメント)およびlatest_access_slotによって追跡されます。
epoch境界での再コンパイル
機能のアクティベーションによりepoch境界でProgramRuntimeEnvironmentsが変更された場合、キャッシュされたすべてのプログラムが新しい環境に対して再コンパイルされます。
戻りデータ
プログラムはsol_set_return_data syscallを通じて戻りデータを設定できます。データはデータバイトとsyscallを呼び出したinstructionsのプログラムのprogram_idを保持するトランザクションレベルのTransactionReturnData構造体に保存されます。最大サイズは1,024バイト(MAX_RETURN_DATA)です。
Is this page helpful?