概要
CPIは権限チェック、アカウント変換、データ同期を含む11のランタイムステップを通過します。最大呼び出し深度: 5 (SIMD-0268では9)。権限ルールは、呼び出し先が呼び出し元が付与した権限を超えてエスカレートすることを防ぎます。
権限ルール
CPIは呼び出し元のアカウント権限を厳格な強制により呼び出し先に拡張します。ランタイムはprepare_next_instructionでこれらのルールをチェックします:
| シナリオ | 許可? | 強制ポイント | エラー |
|---|---|---|---|
| 呼び出し元がアカウントを書き込み可能として渡し、呼び出し先が書き込み可能としてマーク | はい | -- | -- |
| 呼び出し元がアカウントを読み取り専用として渡し、呼び出し先が書き込み可能としてマーク | いいえ | prepare_next_instruction | PrivilegeEscalation |
| 呼び出し元がアカウントを書き込み可能として渡し、呼び出し先が読み取り専用としてマーク | はい | -- | -- |
| 呼び出し元がアカウントを署名者として渡し、呼び出し先が署名者としてマーク | はい | -- | -- |
| 呼び出し元がアカウントを非署名者として渡し、呼び出し先が署名者としてマーク、アカウントが呼び出し元のシードから派生したPDA | はい | prepare_next_instruction | -- |
| 呼び出し元がアカウントを非署名者として渡し、呼び出し先が署名者としてマーク、アカウントが呼び出し元からのPDAではない | いいえ | prepare_next_instruction | PrivilegeEscalation |
| 呼び出し元がアカウントを署名者として渡し、呼び出し先が非署名者としてマーク | はい | -- | -- |
| プログラムAが自身を直接呼び出す (A -> A) | はい | push() | -- |
| プログラムAがBを呼び出し、BがAを呼び出す (間接的な再入) | いいえ | push() | ReentrancyNotAllowed |
| ネイティブローダー、bpf_loader、bpf_loader_deprecated、またはプリコンパイルへのCPI | いいえ | check_authorized_program | ProgramNotSupported |
| トランザクション内にアカウントが見つからない | いいえ | prepare_next_instruction | MissingAccount |
権限ルールは次のように要約できます。
- 書き込み権限はエスカレートできません。 呼び出し元がアカウントを読み取り専用としてマークした場合、呼び出し先はそれを書き込み可能としてマークできません。
- 署名者権限には承認が必要です。
アカウントが呼び出し先で署名者になれるのは、(a) 呼び出し元ですでに署名者であった場合、または (b)
invoke_signedを介して呼び出し元プログラムのシードから派生したPDAである場合のみです。 - 権限の削減は常に許可されます。 呼び出し先は、呼び出し元が付与した権限よりも少ない権限を使用できます。
CPI実行フロー
CPIは複数のランタイムレイヤーを通過します。このセクションでは、プログラムSDK呼び出しからシステムコール境界を経てランタイムに入り、戻るまでの完全なパイプラインを説明します。各ステップは、それを実装するソースファイルを参照しています。
プログラム命令呼び出しの最大の高さは
max_instruction_stack_depth
と呼ばれ、
MAX_INSTRUCTION_STACK_DEPTH
定数の5に設定されています。MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 がアクティブな場合、これは9に増加します。
スタック高さ1は初期トランザクション命令です。各CPIは高さを1ずつ増やします。最大5は、プログラムが最大4レベルの深さまでCPIを実行できることを意味します(SIMD-0268では8レベルの深さ)。
ステップ1: プログラムがinvokeまたはinvoke_signedを呼び出す
プログラムは
invoke
または
invoke_signed
を呼び出します。 invoke は、空の署名者シード配列で invoke_signed
を呼び出す薄いラッパーです。SDK関数は Instruction、 AccountInfo
スライス、および署名者シードをVMメモリにシリアライズし、その後システムコールをトリガーします。
ステップ2: システムコールエントリ
SBF VMは
sol_invoke_signed_rust
システムコールハンドラにディスパッチし、共有エントリポイント
cpi_common
を呼び出します。
ステップ3: 呼び出しコストの消費
*rscpi_common*内の最初のアクションは、共有コンピュートメーターから
固定呼び出しコストを請求する
ことです:
invoke_units
= 1,000 CU(またはSIMD-0339では946 CU)。
ステップ4: VM メモリから instruction を変換
システムコールハンドラーは、プログラムのVMアドレス空間からホスト側のRust型へinstructionを変換します。これは
translate_instruction_rust
を介して行われ、*rsStableInstruction*構造体を読み取り、データ長を
MAX_INSTRUCTION_DATA_LEN
(10,240バイト)に対して検証し、その後
データシリアライゼーションコストを請求します。
ステップ5: 署名者seedの変換とPDAの導出
ハンドラーは
translate_signers_rustを呼び出します。各署名者seedのセットに対して、ランタイムは以下を実行します:
- 署名者seedセットの数を
MAX_SIGNERS(16)に対してチェックします。 - 各seedセットの長さを
MAX_SEEDS(セットあたり16個のseed)に対してチェックします。 - seedと呼び出し元のプログラムIDを使用して
Pubkey::create_program_addressを呼び出します。seedが有効なPDAを生成しない場合、CPIはBadSeedsで失敗します。 - 結果として得られたPDA公開鍵を
signersベクターに収集します。
これらの導出されたPDAは、呼び出し先instructionの有効な署名者として扱われます。
ステップ6: 認可されたプログラムのチェック
処理を進める前に、ランタイムは
check_authorized_program
を呼び出して、ターゲットプログラムがCPIに許可されているかを検証します。以下のプログラムはブロックされます:
- ネイティブローダー
bpf_loaderおよびbpf_loader_deprecatedbpf_loader_upgradeable(特定の管理instructionsを除く:upgrade、set_authority、set_authority_checked(機能ゲート付き)、extend_program_checked(機能ゲート付き)、close)- プリコンパイルプログラム(ed25519、secp256k1など)
違反は
ProgramNotSupportedを返します。
ステップ7: 権限検証(prepare_next_instruction)
ランタイムは
prepare_next_instruction
を呼び出し、呼び出し先の*rsInstructionAccount*リストを構築し、権限ルールを適用します。完全な決定テーブルについては、以下の権限ルールを参照してください。
ステップ8: アカウント情報の変換
ハンドラーはtranslate_accountsを呼び出し、以下を実行します:
- アカウント情報数の検証
*rs
MAX_CPI_ACCOUNT_INFOS*に対して(128、またはSIMD-0339では255)。 - アカウント情報変換コストの課金
(SIMD-0339のみ):
(num_account_infos * 80) / 250CU。 - 実行不可能で重複していない各アカウントに対して、VMメモリからホストメモリへのポインタを変換することで
CallerAccountを構築します。これには アカウントごとのデータシリアライゼーションコストの課金が含まれます:account_data_len / cpi_bytes_per_unitCU。
ステップ9: CPI前のアカウント同期(呼び出し元から呼び出し先へ)
呼び出し先を実行する前に、ランタイムは呼び出し元のアカウント変更を同期し、呼び出し先がそれらを確認できるようにします。関数
update_callee_account
が変換された各アカウントに対して呼び出され、lamport、データ、所有者をコピーします。詳細なフィールドマッピングについては、
アカウントデータの同期
を参照してください。
ステップ10: 命令コンテキストのプッシュ、呼び出し先の実行、ポップ
ランタイムは
process_instruction
を呼び出し、以下を実行します:
push()を呼び出して、命令スタックに新しいフレームを追加します。*rspush()*は 再入規則を強制します: プログラムは直接の呼び出し元である場合にのみ自身を呼び出すことができます(つまり、プログラムAはAを呼び出せますが、AはBを呼び出してそのBがAを呼び出すことはできません)。違反すると *rsReentrancyNotAllowed*が返されます。process_executable_chainを呼び出し、呼び出し先のプログラムエントリポイントを解決して実行します。呼び出し先は同じ共有コンピュートメーターで実行されます。呼び出し先によるすべてのCU消費は、呼び出し元の残りの予算を減らします。pop()を呼び出して、呼び出し先のフレームを削除し、lamport残高が変更されていないことを確認します (変更されている場合はUnbalancedInstruction)。
ステップ11: CPI後のアカウント同期(呼び出し先から呼び出し元へ)
process_instructionが返された後(ポップを含む)、ランタイムは
update_caller_account
を介して、書き込み可能な各アカウントの変更を呼び出し元に同期します。さらに、
update_caller_account_region
は、データ領域が変更されたアカウントのVMメモリ領域マッピングを更新します。詳細なフィールドマッピングについては、
アカウントデータの同期
を参照してください。
CPI syscallは、呼び出し元プログラムに0(成功)を返します。
Is this page helpful?