[ERC7796] トランザクション実行の事前条件を設定できる仕組みを理解しよう！

# はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、トランザクションを実行する前に事前条件を設定できる、新たなJSON-RPCメソッドを提案している**ERC7796**についてまとめていきます！

:::note info
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-7796

他にも様々なEIPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

# 概要

**ERC7796**は、新しいJSON-RPC APIメソッドである `eth_sendRawTransactionConditional` を提案しています。
これは、ブロックビルダーやシーケンサーがトランザクションを処理する時に、事前条件を指定できるようにするものです。
これにより、トランザクションのシミュレーションを減らし、計算コストを抑えながら効率的なブロック構築を可能にします。

# 動機

現在のプライベートなブロックビルダー向けAPI（例えばFlashbotsのAPI）は、トランザクションの検証をするためにシミュレーションを行います。
このシミュレーションはCPU負荷が高く、ブロックを構築する時に大きな計算コストが発生します。
その結果、効率的なトランザクションの並び替えが難しくなり、不要なコストがかかってしまいます。

さらに、FlashbotsのAPIでは、複数のトランザクションの相互依存関係を判定する仕組みがありません。
つまり、あるトランザクションが他のトランザクションと干渉しないことを保証する方法がないです。
そのため、干渉を避ける唯一の方法は、そのトランザクションをブロック内の最初に配置することになります。
しかし、最初のスロットは非常に経済的価値が高いため、配置のためのコストが異常に高騰しがちです。

また、ブロックの他のスロットに関しては、他のトランザクションがどのように影響するかの保証がないため、ガス代が相対的に低くなりやすいという問題があります。
加えて、トランザクションの依存関係を簡単に検出する方法がないため、最適な順序を見つけるには膨大な計算が必要になります。

**ERC7796**では、トランザクションに「事前条件」を設定できる新しいRPCメソッド `eth_sendRawTransactionConditional` を導入することでこの問題を解決しようとしています。
事前条件とは、「**特定の状態が満たされている場合のみトランザクションを有効にする**」といった条件を指します。
例えば、「**あるコントラクトの残高が一定以上である場合のみ実行可能**」といった条件をトランザクションに付与できるようになります。

この仕組みによって、ブロックビルダーはすべてのトランザクションをシミュレーションすることなく、条件をチェックするだけで検証できます。
その結果、計算コストが削減されてトランザクションの並び替えやブロックの構築がより効率的になります。

また、トランザクションごとに明確な条件が設定されることで、他のトランザクションとの干渉をある程度コントロールできるようになります。
これにより、ブロックの最初のスロットに過度な価値が集中する問題を軽減し、より公平な取引の機会を提供できる可能性があります。

# 仕様

## `eth_sendRawTransactionConditional`

**ERC7796**では、新しいRPCメソッド `eth_sendRawTransactionConditional` を導入し、トランザクションを送信する時に特定の条件を指定できるようにします。
これにより、トランザクションの実行が特定のブロック範囲や状態のもとでのみ許可されるようになります。  

`eth_sendRawTransactionConditional` は、通常の `eth_sendRawTransaction` と似ていますが、追加のオプションパラメータ `options` を指定できる点が異なります。
この `options` に条件を設定することで、トランザクションがブロックに含まれる時のルールを決めることができます。  

### transaction  

通常の `eth_sendRawTransaction` と同じように、**事前に署名されたトランザクションデータ**を指定します。
このトランザクションは、基本的にEthereumネットワークで実行されるものと同じですが、オプションの条件が加わることで特定の状態を満たしたときのみブロックに含めることが可能になります。  

### options

この `options` パラメータを使うことで、トランザクションの実行に必要な条件を細かく指定できます。
以下のような条件を設定できます。  

#### knownAccounts

特定のアカウントのストレージスロットの値を指定し、それが一致している場合のみトランザクションを受け付けるようにできます。
例えば、あるアカウントのETH残高が一定以上であることを条件にすることが可能です。  

- **`key`**
    - アカウントのアドレス（Ethereumアドレス）。  
- **`balance`**
    - 指定したアカウントのETH残高を条件にする場合に使います。  
- **`code`**
    - そのアカウントのコントラクトコードの有無や特定のコードが存在することを条件にできます。  
    - `""`（空文字列）を指定すると、「このアドレスにはコードがない（EOAである）」という条件になります。  
    - `"0xef01..."` のような特定の値を指定すると、そのアドレスが**EIP7702**のデリゲーションコードを持っていることを条件にできます。  
- **`nonce`**
    - アカウントの `nonce`を指定できます。  
- **ストレージの値**  
    - 16進数の文字列を指定すると、そのアカウントのストレージルートハッシュが特定の値であることを条件にできます。  
    - オブジェクトを指定すると、アカウントのストレージスロットごとに、特定のスロットが特定の値を持っているかを条件にできます。
    - スロットと値は16進数の文字列として指定します。  

:::note info
**EIP7702**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/13a828c97786aab953e4

#### `blockNumberMin` / `blockNumberMax`（ブロック番号の範囲）  

トランザクションが含まれるブロックの範囲を指定できます。  
- `blockNumberMin`
    - トランザクションが実行される最低ブロック番号。  
- `blockNumberMax`
    - トランザクションが実行される最大ブロック番号。  

この範囲外のブロックでは、トランザクションは無効になります。  

#### `timestampMin` / `timestampMax`（タイムスタンプの範囲）  

トランザクションが実行されるブロックの**タイムスタンプの範囲**を指定できます。  
- `timestampMin`
    - トランザクションが実行可能になる最小のタイムスタンプ（秒単位）。  
- `timestampMax`
    - トランザクションが実行可能な最大のタイムスタンプ（秒単位）。  

この条件を設定することで、一定の時間内にのみ有効なトランザクションを作ることができます。  

#### `paysCoinbase`（マイナー/ブロックビルダーへの報酬）  

このトランザクションがブロックビルダーやシーケンサーに最低いくらの報酬を支払うかを指定できます。  
この金額には、ガス代と直接的な支払いの両方が含まれます。  
また、これによりマイナーやブロックビルダーがトランザクションを選ぶ際の基準を明確にできます。  

## ブロックビルダーやシーケンサーの処理  
トランザクションを受け取ったブロックビルダーやシーケンサーは、事前に以下の条件を満たしているかどうかをチェックします。  

- ブロック範囲が指定されている場合、現在のブロック番号がその範囲内かを確認する。  
- タイムスタンプ範囲が指定されている場合、現在のブロックのタイムスタンプがその範囲内かを確認する。  
- `knownAccounts` で指定されたアカウントのストレージルートハッシュが変更されていないかを確認する。  
- `knownAccounts` のストレージスロットの値が、指定された値と一致しているかを確認する。**  

もし、これらの条件のいずれかが満たされていない場合、シーケンサーはこのトランザクションを拒否します。  

## 戻り値について  

このメソッドの呼び出しが成功すると、新しく送信されたトランザクションのハッシュが返されます。
この動作は、既存の `eth_sendRawTransaction` メソッドと同じで、トランザクションが適切に受理された場合にその識別子となるハッシュを取得できます。

### 失敗時のエラーハンドリング  

トランザクションが即座に拒否される場合、ブロックビルダーはエラーメッセージを返します。
その時、なぜ拒否されたのかを明確に示すエラーコードと理由が含まれます。

- **条件が満たされずに拒否された場合**  

エラーコードは `-32003` となり、メッセージには具体的な理由が記載されます。  
例えば、以下のようなケースがあります。  
- **ストレージの条件が合わない**（指定したアカウントのストレージ値が異なっている）。  
- **指定したブロック範囲外である**（トランザクションが許可されたブロック番号の範囲を超えている）。  
- **時間条件に合わない**（ブロックのタイムスタンプが指定された時間範囲外である）。  

- **リクエストの負荷が高すぎる場合**  

例えば、`knownAccounts` に指定したアカウントの数が多すぎるなど、ブロックビルダーが処理しきれない場合は、エラーコード `-32005` となります。  
メッセージには「処理能力を超えた」などの説明が含まれます。  

### 注意点  

このメソッドは、トランザクションの確定を保証するものではありません。`eth_sendRawTransaction` と同様に、検証が通ったからといって確実にブロックに含まれるわけではありません。  

例えば、次のような状況が考えられます。  
- ブロックの並び替えによって、もともと含まれる予定だったトランザクションが削除される可能性がある。  
- 他のトランザクションの影響で、トランザクションが後回しになったり、条件が変わってしまい最終的に失敗することがある。  

そのため、トランザクションを送信した後は、実際にブロックに含まれたかどうかを自分で確認する必要があります。
モニタリングを行い、必要に応じて再送することが求められます。

### サンプルリクエストの説明  

リクエストの例として、以下のJSONが示されています。  

```json
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_sendRawTransactionConditional",
    "params": [
        "0x2815c17b00...",
        {
            "blockNumberMax": 12345,
            "knownAccounts": {
                "0xadd1": "0xfedc....",
                "0xadd2": { 
                    "0x1111": "0x1234...",
                    "0x2222": "0x4567..."
                }
            }     
        } 
    ]
}
```

このリクエストでは、以下の条件が設定されています。  
- **最大ブロック番号**
    - `12345` を指定しており、このブロック番号以下でのみトランザクションが処理される。  
- **`knownAccounts` にアカウントの状態を指定**  
    - `0xadd1` のストレージルートハッシュが `0xfedc...` であることを条件とする。  
    - `0xadd2` のストレージスロット `0x1111` は `0x1234...` の値を持ち、`0x2222` は `0x4567...` の値を持つことを条件とする。  

# 補足

## 設計の意図  

`knownAccounts` では、ストレージスロットの値を「特定の値」に固定して指定することしかできません。
つまり、「このスロットの値がちょうど〇〇である場合に限り、このトランザクションを有効にする」という条件だけを設定できます。  

本来であれば、スロットの値に「最小値 (`minValue`)」や「最大値 (`maxValue`)」といった範囲条件をつけることも考えられます。
例えば、「**あるトークンの残高が100以上ならトランザクションを実行する**」といった指定ができれば便利ですが、それをサポートするのは難しいという理由が述べられています。  

## 範囲指定をサポートしない理由  

- **APIの複雑さが増すため**  

`knownAccounts` に `minValue` や `maxValue` を追加すると、ブロックビルダーが条件を評価するロジックが大幅に複雑化します。  
現在の設計では「**値がピッタリ一致しているかどうか**」だけをチェックすればよいのに対し、範囲条件を追加すると「**値が範囲内かどうか**」という計算が必要になり、処理の負担が増えてしまいます。  

- **送信者側にとっても負担が大きい**  

トランザクションを送信する側（ユーザーやボット）にとっても、どの範囲なら有効かを事前に決めるのが難しいという問題があります。  
例えば、あるスロットの値が「**ある範囲内で変動する**」と分かっていたとしても、その最適な範囲をどのように設定するかを考えるのは容易ではありません。  

このような理由から、`knownAccounts` では「**ピッタリ一致する値だけを条件として設定する**」というシンプルな仕様にしています。  

## 代替の方法  

範囲指定の代わりに、ある種の条件を満たしたときにトランザクションが成立するような方法として `paysCoinbase` を活用できます。  

`paysCoinbase` を使うことで、ブロックビルダーに「**トランザクションがブロックに含まれた場合に、コインベース（ブロックの報酬を受け取るアドレス）へ一定の金額を支払う**」というルールを作ることができます。  

この仕組みをうまく利用すれば、特定の条件下でトランザクションが実行されるような動作を実現できます。
例えば、ブロックビルダーが「**このトランザクションを含めるべきかどうか**」を、自身の利益を考えて判断できるようになります。
つまり、送信者が明示的に「**この条件を満たしたときにだけ支払いを行う**」とすることで、間接的にトランザクションの処理ルールを調整できるのです。  

# 互換性

新しいAPIメソッドの提案であるため、互換性の問題は発生しない。

# セキュリティ

このAPIを運用するブロックビルダーは、不正利用を防ぐための対策を講じる必要があります。
特に、悪意のあるユーザーが「**必ず失敗すると分かっているリクエスト**」を大量に送信した場合、ブロックビルダーのリソースが無駄に消費されてサービスが正常に動作しなくなる可能性があります。
このような攻撃はサービス拒否攻撃（DoS攻撃）と呼ばれ、処理能力の限界を超える負荷をかけることで、他の正当なユーザーがAPIを利用できなくすることを目的としています。  

このような問題を防ぐために、以下のような対策が推奨されています。  

### スロットリング（Throttling）の導入  

ブロックビルダーは、1つの送信元（アカウント）からのリクエスト回数を制限する仕組みを導入すべきです。
具体的には、一定の時間内に送信できるリクエストの回数に上限を設けることが考えられます。  

また、単純に回数を制限するだけではなく、リクエストが成功した場合はその送信者の許容回数を増やし、失敗が続く場合は許容回数を減らすといった調整を行うことで悪意のある利用者を自然に排除できます。  

例えば、次のようなルールが考えられます。  
- **リクエストが成功（トランザクションがブロックに含まれた）** → そのアカウントのリクエスト許容量を増やす。  
- **リクエストが失敗（条件を満たしていない）** → そのアカウントのリクエスト許容量を減らす。  

この仕組みによって、正当なユーザーはスムーズに利用できる一方で、不正利用者は自動的に制限されるようになります。  

### Arbitrum による対策  

Arbitrumでは、同様のAPIを導入していますがストレージの検証を現在のブロックだけでなく、過去2秒間のブロックも対象にしています。  

この方法を取ることで、次のようなメリットがあります。  

- **MEV目的の悪用を防ぐ**  

MEVを狙うトレーダーが、特定のストレージの状態が変わった瞬間に有利なトランザクションを送信することを防げます。  
例えば、価格操作やフロントランニング（他の取引を先回りして利益を得る行為）を防ぐことができます。  

- **ERC4337との互換性を保つ**  

**ERC4337**では、ユーザーのウォレットがトランザクションの実行条件を満たしているかどうかを検証する必要がありますが、過去2秒間のデータも考慮することでより安定した検証が可能になります。  

:::note info
**ERC4337**については以下の記事を参考にしてください。
:::

https://qiita.com/cardene/items/8c97ca7c93c4557ebfc2

このように、過去の一定期間のデータを考慮することで、リアルタイムでの悪用を抑えつつ、正当なトランザクションをスムーズに処理できるようになります。  

### Polygon Fastlane による対策  

Polygonの「`Fastlane`」という仕組みでは、このAPIを**ERC4337**のために利用しており、トランザクションが受け入れられる条件として「**パブリックメンプールに同じトランザクションが存在するかどうか**」をチェックしています。  

この仕組みによって、以下のような対策が可能になります。  
- **隠れた不正取引を防ぐ**  

もしパブリックメンプールに存在しないトランザクションが急に送信されると、それが悪用目的の可能性があるため、拒否することができます。  

- **正規のERC4337トランザクションを確実に処理する**  

**ERC4337**を利用するトランザクションは、基本的にメンプールに公開されるはずなので、それを基準に判断すれば不正なリクエストを弾きながら正当なトランザクションを処理できます。 

# 引用

Dror Tirosh (@drortirosh), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Shahaf Nacson (@shahafn), "ERC-7796: Conditional send transaction RPC [DRAFT]," Ethereum Improvement Proposals, no. 7796, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7796.

# 最後に

今回は「トランザクションを実行する前に事前条件を設定できる、新たなJSON-RPCメソッドを提案している**ERC7796**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4