概要
トランザクションは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 が最初に解析され、検証されます。手数料の詳細は、バジェット制限と優先順位付け手数料から計算されます。計算バジェットが無効または矛盾している場合、トランザクションは
DuplicateInstruction、InstructionError(..., InvalidInstructionData)、または
InvalidLoadedAccountsDataSizeLimit
などの計算バジェット解析エラーで失敗します。
ブロックハッシュの経過時間: トランザクションの recent_blockhash が
BlockhashQueue
で検索されます。ハッシュが見つかり、その経過時間が 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は、外部アカウントストアをラップし、バッチローカルキャッシュを維持することで、同じバッチ内の前のトランザクションによって変更されたアカウントが後のトランザクションから見えるようにします。
手数料支払者以外の各アカウントについて、ローダーは以下を実行します:
- キャッシュまたはaccounts-dbからアカウントを取得
- 必要に応じてrent-exemptステータスを更新
- アカウントのデータサイズを
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を呼び出します:
- 各instructionに対して、ランタイムは
prepare_next_top_level_instructionを呼び出し、InstructionContextを構築します。このコンテキストには、instructionのアカウント(コンパイルされたインデックスから解決)、instruction data、およびprogram accountインデックスへの参照が含まれます。 - ランタイムは、プログラムが プリコンパイル(Ed25519、Secp256k1、Secp256r1)であるかどうかをチェックします。プリコンパイルはBPF VMを起動せずに直接検証されます。
- その他すべてのプログラムに対して、ランタイムは
process_instructionを呼び出し、キャッシュからプログラムを読み込んでBPF仮想マシンで実行します。 - instructionの実行完了後、ランタイムは
検証を行い、すべてのinstructionアカウント全体のlamport残高が変更されていないことを確認します(
UnbalancedInstructionチェック)。 - いずれかの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?