トランザクションパイプライン

概要

トランザクションは8つの段階を経て処理されます:受信、署名検証、サニタイゼーション、予算/経過時間チェック、手数料支払者の検証、アカウント読み込み、instructions実行、そしてコミット。

トランザクション処理パイプライン

トランザクションがvalidatorに到達すると、一連の検証および実行段階を経ます。以下では、受信からコミットまでの完全なパイプラインを、agave validatorクライアントのソースファイル参照とともに説明します。

1. 受信とデシリアライゼーション

validatorはUDP/QUIC経由でトランザクションバイトを受信します。生バイトは単一パケット内に収まる必要があります(PACKET_DATA_SIZE = 1,232バイト)。バイトは VersionedTransaction にデシリアライズされ、これには署名配列と VersionedMessage (レガシーまたはv0)が含まれます。

2. 署名検証(sigverify)

署名は、トランザクションがバンキング段階に入る前に署名検証段階で検証されます。インデックスiの各署名に対して、検証者はEd25519(signatures[i], account_keys[i], message_bytes)をチェックします。いずれかの署名が無効な場合、そのパケットは破棄されます。

検証は並列化されています:validatorはパケットバッチをVERIFY_PACKET_CHUNK_SIZE(128)のチャンクに分割し、並列に処理します。

3. サニタイゼーション

デシリアライズされたトランザクションはサニタイズされ、 SanitizedTransaction (またはRuntimeTransaction)が生成されます。サニタイゼーションは構造的な不変条件を検証します:

  • 署名数がヘッダー内のnum_required_signaturesと一致すること
  • すべてのinstructionのprogram_id_indexおよびaccount_indicesが範囲内であること
  • 手数料支払者(アカウントインデックス0)が書き込み可能な署名者であること

RuntimeTransaction ラッパーは、TransactionMetaから事前計算されたメタデータをキャッシュします:メッセージハッシュ、投票トランザクションフラグ、プリコンパイル署名カウント(Ed25519/secp256k1/secp256r1)、計算予算instruction詳細、およびinstruction dataの合計長。

4. 計算バジェット、経過時間、およびステータスキャッシュを確認

check_transactions メソッドは、トランザクションごとにいくつかのチェックを実行します:

計算バジェット: トランザクションの計算バジェット instructions が最初に解析され、検証されます。手数料の詳細は、バジェット制限と優先順位付け手数料から計算されます。計算バジェットが無効または矛盾している場合、トランザクションは DuplicateInstructionInstructionError(..., InvalidInstructionData)、または InvalidLoadedAccountsDataSizeLimit などの計算バジェット解析エラーで失敗します。

ブロックハッシュの経過時間: トランザクションの recent_blockhashBlockhashQueue で検索されます。ハッシュが見つかり、その経過時間が MAX_PROCESSING_AGE (150 slot) 以内であれば、トランザクションは続行されます。見つからない場合、validator は有効な durable nonce をチェックします。

ステータスキャッシュ: トランザクションのメッセージハッシュがステータスキャッシュに対してチェックされます。見つかった場合、トランザクションは AlreadyProcessed で拒否されます。

5. nonce と手数料支払者を検証

SVMの validate_transaction_nonce_and_fee_payer メソッドは、2つの検証を処理します:

nonce 検証 (該当する場合): nonce トランザクションの場合、validator は nonce アカウントをロードし、以下を検証します:

  • アカウントが System Program によって所有されていること
  • State::Initialized として解析されること
  • 保存された durable nonce がトランザクションの recent_blockhash と一致すること
  • nonce を進めることができること(現在の durable nonce が次の durable nonce と異なる、つまり、nonce が現在のブロックで既に使用されていないこと)
  • nonce 権限がトランザクションに署名していること

有効な場合、nonce は次の durable nonce 値に進められます。validate_transaction_nonce を参照してください。

手数料支払者の検証: 手数料支払者アカウント(常にインデックス0)がロードされ、validate_fee_payer によってチェックされます:

  • アカウントが存在する必要があります(lamport > 0)、そうでなければ AccountNotFound
  • アカウントはシステムアカウントまたは nonce アカウントである必要があります、そうでなければ InvalidAccountForFee
  • lamport は min_balance + total_fee をカバーする必要があります。ここで、min_balance はシステムアカウントの場合は0、nonce アカウントの場合は rent.minimum_balance(NonceState::size()) です。そうでなければ InsufficientFundsForFee
  • 手数料控除後、アカウントは免除額を維持する必要があります(免除額状態から支払い状態に移行することはできません)

この段階で、手数料は手数料支払者から差し引かれます。手数料が差し引かれた後の手数料支払者(該当する場合は進められたnonce)のスナップショットが RollbackAccounts として保存されます。これらは、実行が失敗した場合でもコミットされるアカウントです。

6. アカウントの読み込み

load_transactionは、トランザクションで参照されるすべてのアカウントを読み込みます。AccountLoaderは、外部アカウントストアをラップし、バッチローカルキャッシュを維持することで、同じバッチ内の前のトランザクションによって変更されたアカウントが後のトランザクションから見えるようにします。

手数料支払者以外の各アカウントについて、ローダーは以下を実行します:

  1. キャッシュまたはaccounts-dbからアカウントを取得
  2. 必要に応じてrent-exemptステータスを更新
  3. アカウントのデータサイズをloaded_accounts_data_size_limit(デフォルト64 MiB)に向けて累積。各アカウントにはTRANSACTION_ACCOUNT_BASE_SIZE(64バイト)の基本オーバーヘッドとデータ長が発生

トランザクションのinstructionsによって呼び出される各programについて、ローダーはprogram accountが存在し、有効なローダー(NativeLoaderまたは PROGRAM_OWNERS のいずれか)によって所有されていることを検証します。無効なprogramは ProgramAccountNotFound または InvalidProgramForExecution で失敗します。

LoaderV3(アップグレード可能)programは、関連するprogramdata accountを暗黙的に読み込みます。これも読み込まれたデータサイズの制限にカウントされます。

アカウントの読み込みが失敗したが、手数料支払者の検証が成功した場合、トランザクションはFeesOnly結果となります:手数料は引き続き徴収されますが、instructionsは実行されません。

7. instructionsの実行

execute_loaded_transactionは、読み込まれたすべてのアカウントを含む TransactionContext を作成し、process_messageを呼び出します。instructionsは、メッセージ内に出現する順序で順次実行されます。各instructionの呼び出しは InvokeContext を作成し、ターゲットprogramを呼び出します。

instructionの処理の詳細

ランタイムのprocess_message関数は、各instructionを反復処理し、ターゲットprogramを呼び出します:

  1. 各instructionに対して、ランタイムは prepare_next_top_level_instructionを呼び出し、 InstructionContextを構築します。このコンテキストには、instructionのアカウント(コンパイルされたインデックスから解決)、instruction data、およびprogram accountインデックスへの参照が含まれます。
  2. ランタイムは、プログラムが プリコンパイル(Ed25519、Secp256k1、Secp256r1)であるかどうかをチェックします。プリコンパイルはBPF VMを起動せずに直接検証されます。
  3. その他すべてのプログラムに対して、ランタイムは process_instructionを呼び出し、キャッシュからプログラムを読み込んでBPF仮想マシンで実行します。
  4. instructionの実行完了後、ランタイムは 検証を行い、すべてのinstructionアカウント全体のlamport残高が変更されていないことを確認します(UnbalancedInstruction チェック)。
  5. いずれかのinstructionが失敗した場合、トランザクション全体がロールバックされます。中間状態の変更はコミットされません。

各instructionはinstructionトレースをインクリメントします。トレースには、トップレベルのinstructionsと、それらが呼び出すすべてのCPIが含まれます。トレースの合計長(トップレベルのinstructionsとすべてのネストされたCPI)は64 (MAX_INSTRUCTION_TRACE_LENGTH)を超えることはできません。この制限を超えると、 InstructionError::MaxInstructionTraceLengthExceeded が返されます。

実行後、ランタイムは以下を検証します:

  • すべてのアカウント全体のlamportの合計が変更されていないこと
  • レント免除からレント支払いに移行したアカウントがないこと

8. コミットまたはロールバック

実行が成功した場合、TransactionContextから変更されたアカウント状態が AccountLoader のバッチローカルキャッシュに書き戻されます。実行が失敗した場合、 RollbackAccounts (手数料が差し引かれた手数料支払者と進められたnonce)のみが書き戻されます。手数料は引き続き徴収されますが、その他すべてのアカウント変更は破棄されます。

パイプラインの概要

Receive packet (UDP/QUIC)
--> Deserialize into VersionedTransaction
--> Sigverify (parallel Ed25519 verification)
--> Sanitize (structural validation, metadata extraction)
--> Parse compute budget, calculate fees
--> Check blockhash age (or verify nonce account)
--> Check status cache (dedup)
--> Validate nonce authority and advanceability (if nonce transaction)
--> Validate fee payer (load, check balance, deduct fee)
--> Load all accounts (with data size limits)
--> Load programs (verify loaders)
--> Execute instructions sequentially
--> Verify post-conditions (lamport balance, rent state)
--> Commit account changes (or rollback on failure)

トランザクションエラーリファレンス

以下の表は、すべての TransactionError バリアントと、それらが発生するパイプラインステージを示しています:

エラーステージ原因
AccountInUseスケジューリングアカウントは同じバッチ内の別のトランザクションによって既にロックされています
AccountLoadedTwiceスケジューリングトランザクションのaccount_keysにpubkeyが2回出現しています
AccountNotFound手数料支払者の検証手数料支払者アカウントが存在しません
ProgramAccountNotFoundアカウントの読み込み呼び出されたプログラムが存在しません
InsufficientFundsForFee手数料支払者の検証手数料支払者は手数料 + rent-exempt最小額をカバーできません
InvalidAccountForFee手数料支払者の検証手数料支払者はシステムアカウントまたはnonceアカウントではありません
AlreadyProcessedステータスキャッシュトランザクションは既に処理されています
BlockhashNotFoundエイジチェックブロックハッシュがキューになく、有効なnonceでもありません
InstructionError実行命令の処理中にエラーが発生しました(命令インデックスと具体的なInstructionErrorを含みます)
CallChainTooDeepアカウントの読み込みローダーの呼び出しチェーンが深すぎます
MissingSignatureForFeeサニタイズトランザクションには手数料が必要ですが、署名がありません
InvalidAccountIndexサニタイズトランザクションに無効なアカウント参照が含まれています
SignatureFailure署名検証Ed25519署名が検証されません(パケットは破棄されます)
InvalidProgramForExecutionアカウントの読み込みプログラムが有効なローダーによって所有されていません
SanitizeFailureサニタイズトランザクションがアカウントオフセットを正しくサニタイズできませんでした
ClusterMaintenanceスケジューリングクラスターメンテナンスのため、トランザクションは現在無効になっています
AccountBorrowOutstanding実行トランザクション処理により、アカウントに未処理の借用参照が残りました
WouldExceedMaxBlockCostLimitスケジューリングトランザクションが最大ブロックコスト制限を超過します
UnsupportedVersionサニタイズトランザクションバージョンがサポートされていません
InvalidWritableAccountアカウントの読み込みトランザクションが書き込み不可の書き込み可能アカウントを読み込んでいます
WouldExceedMaxAccountCostLimitスケジューリングトランザクションがブロック内の最大アカウントコスト制限を超過します
WouldExceedAccountDataBlockLimitスケジューリングトランザクションがブロック内のアカウントデータ制限を超過します
TooManyAccountLocksスケジューリングトランザクションが過剰な数のアカウントをロックしました
AddressLookupTableNotFoundアカウントの読み込みアドレスルックアップテーブルアカウントが存在しません
InvalidAddressLookupTableOwnerアカウントの読み込みアドレスルックアップテーブルが誤ったプログラムによって所有されています
InvalidAddressLookupTableDataアカウントの読み込みアドレスルックアップテーブルに無効なデータが含まれています
InvalidAddressLookupTableIndexアカウントの読み込みアドレステーブルルックアップが無効なインデックスを使用しています
InvalidRentPayingAccount実行後チェックアカウントがrent-exemptからrent支払いに移行しました
WouldExceedMaxVoteCostLimitスケジューリングトランザクションが最大投票コスト制限を超過します
WouldExceedAccountDataTotalLimitスケジューリングトランザクションが総アカウントデータ制限を超過します
DuplicateInstructionコンピュートバジェット解析同じトランザクション内に重複したコンピュートバジェット命令バリアントがあります
InsufficientFundsForRent実行後チェックアカウントにデータサイズのrentをカバーするのに十分なlamportがありません
MaxLoadedAccountsDataSizeExceededアカウントの読み込み読み込まれたデータの合計が64 MiB制限を超えています
InvalidLoadedAccountsDataSizeLimitコンピュートバジェット解析SetLoadedAccountsDataSizeLimitが0に設定されています
ResanitizationNeededサニタイズトランザクションが機能有効化の前後で異なっており、再サニタイズが必要です
ProgramExecutionTemporarilyRestrictedアカウントの読み込み参照されたアカウントでのプログラム実行が一時的に制限されています
UnbalancedTransaction実行後チェックトランザクション前のlamport残高合計がトランザクション後の残高と一致しません
ProgramCacheHitMaxLimitアカウントの読み込みプログラムキャッシュが最大制限に達しました
CommitCancelledコミットコミットが内部的にキャンセルされました

Is this page helpful?

管理運営

© 2026 Solana Foundation.
無断転載を禁じます。
つながろう