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