原文
Repurposing FOCIL as an L2 forced transaction mechanism — donnoh (2026-06-19)
フィードバックとレビューをいただいた Péter Garamvölgyi、Thomas Thiery、Francesco Risitano、Jihoon Song に感謝いたします。
以下の記事は、非常に異なるEIPのインクルージョンステージに基づいているか、関連しています。特に、FOCIL (強制オンチェーンインクルージョンリスト) (SFI)、Optional Execution Proofs (PFI)、ブロックレベルアクセスリスト (BALs) (SFI)、Validity-Only Partial Statelessness (EIPなし)、Native rollups (まだ提案されていません)。したがって、詳細は時間の経過とともに変更される可能性があります。この研究は主に、ネイティブロールアップ向けのシンプルな強制トランザクションメカニズムを見つける必要性によって動機付けられていますが、その知見は既存のEVM ロールアップを含むすべてのEVM L2に一般的に適用できます。
概要
既存のソリューションとは異なり、状態遷移関数 (STF)の変更や新しいトランザクションタイプを導入することなく、EVM L2の集中型シーケンサーをバイパスするために使用できる、FOCILを介した強制トランザクションメカニズムの実装を提示します。
背景
FOCILは、ブロックがアテステーションされるために満たす必要のある新しいトランザクションリスト(「インクルージョンリスト」)を追加することで、イーサリアムのSTFを更新します。このようなトランザクションは、コンセンサス層 (CL)側で16のイーサリアムバリデータで構成される「IL委員会」によって選択され、更新されたエンジンAPIを介して実行レイヤー (EL)に渡されます。
def state_transition(chain: BlockChain, block: Block, inclusion_list_transactions: Tuple[LegacyTransaction | Bytes, ...]) -> None:
IL内のトランザクションは、以下の3つの理由により、ブロックから有効に除外される可能性があります。
-
内部チェック(Intrinsic checks)の失敗:不正なトランザクション、誤ったチェーンID、ガス不足、無効な署名、範囲外のパラメータ。
-
ステートフルチェック(Stateful checks)の失敗:ナンスの不一致、残高不足。
ビルダーが「ブロックスタッフィング」によって意図的にトランザクションを除外する可能性はありますが、プロトコルはEIP-1559のベースフィーを増加させ、トランザクションをメムプール (Mempool)に保持することで、攻撃者に指数関数的なコストを課し、検閲耐性を提供します。このトランザクションは次のILに挿入されます。
既存のすべてのEVM L2は現在、新しいトランザクションタイプを導入し、状態遷移関数を変更することで強制トランザクションを実装しています。OPスタックは「デポジットされたトランザクション」タイプを導入しています。これはレイヤー1 (L1)イベントから派生し、対応するL2ブロックの先頭に自動的に挿入され、L2には署名がなく、L1でガスを支払い、L2ではガスを消費しません。op-gethのgethに対するすべての変更はこちらで確認できます。Arbitrumスタックも、署名のない新しいトランザクションタイプ(実際には複数)を導入しており、そのインクルージョンはオンチェーン強制トランザクションキューによって強制されますが、L2でガスを支払います。gethに対するすべての変更はこちらで確認できます。最も重要なのは、どちらの場合も、強制的にインクルードされるトランザクションリストに入った有効なトランザクションが、ブロックの満杯やベースフィーのために有効に除外されることはないということです。それらのためのスペースは常に予約されており、ベースフィーは考慮されないか、リトライメカニズムが実装されています。
メカニズム
核となる直感は、FOCILのCLとメムプール (Mempool)ロジックがL1上のスマートコントラクトによって完全に置き換えられ、複製できるということです。ユーザーは、従来のL2 メムプール (Mempool)の代わりに(おそらくL1 FOCILを介して!)L1 スマートコントラクトに強制トランザクションを送信します。このスマートコントラクトがインクルージョンリストを構築し、エンジンAPIがELに渡すのと同様の方法で、L2 STF検証者にインプットとして渡します。L2 STFは、EIP-8025によって導入されたステートレスSTF関数とまったく同じであると仮定します。8025はまだヘゴタ (Hegotá)の上に構築されていないため、FOCILを考慮していません。そのため、ステートレスなILインターフェースを自由に想像します。
L1のFOCILチェックに関わるCLおよびELコンポーネントを「L2 FOCIL」がどのように置き換えるかを示す図。
メムプール (Mempool)の動作を複製し、検閲耐性を保証するためには、既存の強制トランザクションメカニズムとは異なり、FOCILは特定のブロックへのインクルージョンを実際に保証しないため、L2 ILに到達したトランザクションが自動的にドロップされないようにする必要があります。さらに、有効なトランザクションに対するDoS攻撃を防ぐため、無効なトランザクションはILを可能な限り汚染しないようにし、プルーバー側の無駄な計算を防ぐ必要があります。
オペレーターが投稿する各L2バッチは1つのL2ブロックに対応すると仮定します。そうでない場合、オペレーターは常に空のブロックを生成してベースフィーを削減し、安価にブロックスタッフィング攻撃を実行できるからです。これは今日のほとんどのロールアップには当てはまりませんが、フラッシュブロックのような技術でより高速なブロックをシミュレートできます。
したがって、強制トランザクションコントラクトを次のように設計します。ユーザーは、maxFeePerGasの降順でソートされたオンチェーンリストに署名済みトランザクションを送信します。送信時に、すべての内部チェック(すなわちステートレスチェック)が実行されます。Validity-Only Partial Statelessness (VOPS)の研究投稿とこちらで説明されているように、健全なメムプール (Mempool)を維持するためには、ステートフルチェックの実行が不可欠です。L2 FOCILでは、送信されたトランザクションはL1でガスを支払いますが、非常に高いmaxFeePerGasを持つものの、ナンスが無効であったり、残高が不足しているトランザクションでリストの先頭を埋め尽くすことは安価であり、ILに無効なトランザクションのみが含まれる原因となります。そのため、VOPSは、ステートレスノードがブロックレベルアクセスリスト (BALs)を介して各アカウントの残高とナンスを維持することを提案しています。EVM L2もBALsを生成および公開できますが、スマートコントラクトで残高とナンスを維持することは非現実的です。L1の場合、推定ストレージはすでに約8.4GBに達しており、L2ではさらに大幅に高くなる可能性があります。したがって、ユーザーはリストに受け入れられるために、最近のL2状態に対するeth_getProofを介して取得されたアカウント証明を提出する必要があります。FOCILは、インクルージョンリスト内のトランザクションが含まれたかどうか、またその理由を教えてくれないため、これらのチェックの後でもILに到達したトランザクションは自動的にドロップできません。2つのメカニズムを使用できます。
-
パーミッションレスな
prune関数が追加されます。これは、新しいブロックに対するアカウント証明が与えられた場合、ナンスが変更されたか、残高が不足になったことを証明します。EIP-7702が、アカウント残高はナンスが増加した場合にのみ減少するという不変条件を破ったため、ナンスチェックだけでは不十分であることに注意することが重要です。インセンティブ整合性条件 (IC条件)を達成するために、強制トランザクションの提出者には、トランザクションが無効になった場合にプルーナーに返金するための少額のボンドを提出するよう求めることができます。 -
オペレーターは、決済中にトランザクションルートに対するマークル証明を提供します。ILがSTFによって検証される(例:ゼロ知識証明を介して)ことを考えると、トランザクションが含まれておらず、ブロックにスペースが残っていた場合、そのトランザクションは無効であったと判断でき、ドロップできます。推定コスト:IL内のトランザクションあたり約275k ガス、32トランザクションは10M ガス以内であり、マルチプルーフを使用すればさらに低くなる可能性があります。IL内のトランザクションが無効であっても、ブロックにスペースが残っていなかった場合、ブロック満杯のケースと無効なケースを区別できないため、トランザクションはドロップされません。EIP-1559のベースフィーメカニズムは、十分な弾力性があることを前提として、十分なスペースを持つブロックが最終的に生成されることを保証します。
L2 オペレーターは、L2ブロックで決済関数を呼び出す際に、事前に定義されたガスバジェットまで、またはトランザクションが現在のベースフィーに対して十分な支払いをしないようになるまで、リストの先頭にあるトランザクションから現在のILをプルします。このようなILはオンチェーン検証者への入力として強制され、その充足は有効性ルールと見なされます。競合状態やグリーフィング攻撃を防ぐため、ILは、プルーバーが強制的に含めるべき正確なトランザクションを事前に知ることができるように、閾値よりも古いトランザクションのみで構築できます。L2の集中型シーケンサーがブロックの生成を完全に拒否した場合、オンチェーンでタイムアウトがトリガーされ、ホワイトリストが削除され、検閲耐性が回復されます。
具体的な実装はこちらで確認できます。送信には約1.3M ガス、プルーニングには約1.1M ガスがかかると推定されており、どちらも1 gwei/ガスで約0.001 ETHに相当します。
この研究投稿の範囲外ですが、強制トランザクションコントラクトは、特定のL2のニーズに基づいて、受け入れられる前に追加のチェックを実行するように自然にカスタマイズできます。
アカウントのみのノード
今日、アカウント証明を取得するにはフルノードに接続する必要があり、ほとんどのユーザー、特にL2ユーザーにとっては法外なコストがかかります。Validity-Only Partial Statelessness (VOPS)の提案は、zkEVM (ゼロ知識イーサリアム仮想マシン)と組み合わせることで、アテスターとインクルーダーのストレージ負荷を約233GiBから約8.4GiBに削減することを目指しています。これは、証明を使って状態を検証し、ブロックレベルアクセスリスト (BALs)を介して取得された残高とナンスのみを保存することで健全なメムプール (Mempool)を維持するものです。L2も証明を投稿し、BALsを投稿できるようになるため、L2ユーザー向けに同様のタイプのノードを想定できます。これにより、ユーザーは完全な状態を維持することなく、検閲が発生した場合に強制トランザクションをより簡単に送信できるようになります。残念ながら、BALsはストレージ差分のみを投稿するため、アカウント証明を提供するために必要なストレージルートを再構築するには、完全な状態を追跡する必要があります。参考までに、BALは次のように定義されます。
BlockAccessList = List[AccountChanges]
AccountChanges = [
Address, # address
List[SlotChanges], # storage_changes (slot -> [block_access_index -> new_value])
List[StorageKey], # storage_reads (read-only storage keys)
List[BalanceChange], # balance_changes ([block_access_index -> post_balance])
List[NonceChange], # nonce_changes ([block_access_index -> new_nonce])
List[CodeChange] # code_changes ([block_access_index -> new_code])
]
一方、アカウントはトライ(trie)内で次のようにシリアライズされます。
def encode_account(raw_account_data: Account, storage_root: Bytes) -> Bytes:
"""
Encode `Account` dataclass.
Storage is not stored in the `Account` dataclass, so `Accounts` cannot be
encoded without providing a storage root.
"""
return rlp.encode(
(
raw_account_data.nonce,
raw_account_data.balance,
storage_root,
raw_account_data.code_hash,
)
)
もしBALsがストレージルートの変更も提供するように変更された場合(ブロックの最後にある最後のものだけで十分です)、ノードは完全な状態を維持することなくアカウント証明を構築できるようになります。EIP-8268(参照を提供してくれたToniに感謝します!)は、まさにこの変更を提案しています。
1投稿 - 1参加者
