概要
実行前に、ランタイムはアカウントを読み込み、手数料支払者を検証し、レント免除を確認し、プログラムがアクセスできるメモリレイアウトにアカウントデータをシリアライズします。
このページはランタイムの内部構造について説明しています。ほとんどの開発者は、プログラムを構築する際にこの情報を必要としません。開発者向けの説明については、アカウント構造を参照してください。
アカウントの読み込み
トランザクションが実行される前に、ランタイムはload_transaction_accounts()を介して参照されるすべてのアカウントを読み込みます。このプロセスでは、いくつかの検証が実行されます。
-
手数料支払者の検証: 手数料支払者(最初のアカウント)は存在し、システムアカウントまたはnonceアカウントである必要があり、手数料をカバーするのに十分なlamportを持っている必要があります(
validate_fee_payer())。手数料の支払い後、アカウントはレント免除を維持するか、正確に0 lamportになる必要があります。0とレント免除最小値の間になることはできません。nonceアカウントは常にレント免除を維持するのに十分なlamportを保持する必要があります。支払者がシステムアカウントでもnonceアカウントでもない場合、トランザクションは*rsTransactionError::InvalidAccountForFee*で失敗します。 -
読み込みデータサイズの制限: 読み込まれたすべてのアカウントの合計サイズ(アカウントごとに64バイトの
TRANSACTION_ACCOUNT_BASE_SIZEを含む)は、MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES(64 MiB)を超えてはなりません。この制限を超えると、*rsTransactionError::MaxLoadedAccountsDataSizeExceeded*が発生します。 -
プログラムアカウントの検証: インストラクションによって呼び出されるすべてのプログラムは存在し、有効なローダーによって所有されている必要があります。有効なローダーとは、
PROGRAM_OWNERS(BPF Loader Upgradeable、BPF Loader、BPF Loader Deprecated、Loader V4)またはネイティブローダーのいずれかです。プログラムアカウントが存在しない場合、トランザクションは*rsTransactionError::ProgramAccountNotFoundで失敗します。存在するが無効な所有者を持つ場合、トランザクションはrsTransactionError::InvalidProgramForExecution*で失敗します。 -
存在しないアカウント: オンチェーン上に存在しないアカウントは、デフォルトアカウント(0 lamports、空のデータ、System Programが所有)としてロードされ、
rent_epochがu64::MAXに設定されます。
BPFシリアライゼーション形式
プログラムが呼び出されると、ランタイムはアカウントを連続したメモリバッファにシリアライズし、BPF
VMに渡します。シリアライゼーション形式(非推奨のloader-v1を除くすべてのローダーで使用される標準アライメント形式)は、serialize_parameters_aligned()で定義されています。
バッファは、アカウント数を含むu64(8バイト、リトルエンディアン)で始まります。その後、命令内の各アカウントについて、バッファには以下が含まれます:
| オフセット | サイズ | フィールド | 型 |
|---|---|---|---|
| 0 | 1 | 重複マーカー | u8 (0xFF = 一意、index = そのアカウントの重複) |
| 1 | 1 | is_signer | u8 (0または1) |
| 2 | 1 | is_writable | u8 (0または1) |
| 3 | 1 | executable | u8 (0または1) |
| 4 | 4 | original_data_len (予約済み、常に0) | [0u8; 4] |
| 8 | 32 | key | Pubkey |
| 40 | 32 | owner | Pubkey |
| 72 | 8 | lamports | u64 (リトルエンディアン) |
| 80 | 8 | data_len | u64 (リトルエンディアン) |
| 88 | data_len | data | [u8] |
| 88 + data_len | 10240 + padding | realloc領域 + アライメント | MAX_PERMITTED_DATA_INCREASE (10 KiB)までゼロ埋め + BPF_ALIGN_OF_U128 (8バイト)へのアライメントのためのパディング |
| ... | 8 | rent_epoch | u64 (リトルエンディアン) |
すべてのアカウントの後、バッファは以下を追加します:
| サイズ | フィールド |
|---|---|
| 8 | instruction_data_len (u64、リトルエンディアン) |
| instruction_data_len | instruction_data |
| 32 | program_id (Pubkey) |
重複するアカウントの場合、1バイト(元のインデックスを持つ重複マーカー)と7バイトのパディングのみが書き込まれます。
アカウントの重複排除
同じアカウント公開鍵がインストラクションのaccounts配列に複数回出現する場合、ランタイムはそれらを重複排除します。インストラクションのアカウントリストの各エントリは独自のInstructionAccount構造体を取得しますが、同じトランザクションレベルのアカウントを参照するエントリは同じ基礎データを指します。
is_instruction_account_duplicateメソッドは、トランザクションレベルのアカウントインデックスを検索し、それにマッピングされる最初のインストラクションレベルのインデックスを見つけることで、指定されたインストラクションアカウントインデックスが最初の出現か重複かを判断します:
- 現在のインストラクションアカウントインデックスが最初のマッピングされたインデックスと等しい場合、それは重複ではありません(
Noneを返します)。 - それ以外の場合、
Some(first_index)を返します。ここでfirst_indexは最初の出現のインデックスです。
同じアカウントへのすべての参照は同じ基礎*rsAccountSharedData*を共有するため、任意の参照を通じた変更は他のすべての参照を通じて即座に可視化されます。ただし、一度に保持できる可変借用は1つだけです。2つの異なるインストラクションアカウントインデックスを通じて同じアカウントを同時に可変的に借用しようとすると、*rsInstructionError::AccountBorrowFailed*が返されます。プログラムは、同じ基礎アカウントで別の借用を取得する前に、1つの借用をドロップする必要があります。
プログラムの実行後、ランタイムはバッファを逆シリアル化し(deserialize_parameters_aligned())、lamports、data(*rsMAX_PERMITTED_DATA_INCREASE*までの長さの変更を含む)、およびownerへの変更を適用します。
Is this page helpful?