# [ERC7774] ブロックチェーンイベントでWeb3ページのキャッシュを更新する仕組みを理解しよう！

> ERC7774は、ERC6860で定義されている web3:// プロトコルにおける名前解決方式であるERC6944 resolve modeを拡張し、HTTPの標準キャッシュ仕様であるRFC9111を実用的に利用できるようにするための仕組

- URL: https://cardene.dev/articles/a5d9e592865a5fd3218b
- 公開日: 2026-01-19
- 更新日: 2026-01-19
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、ブロックチェーンイベントでWeb3ページのキャッシュを更新する仕組みを提案している**ERC7774**についてまとめていきます！

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

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

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

## 概要

**ERC7774**は、**ERC6860**で定義されている `web3://` プロトコルにおける名前解決方式である**ERC6944 resolve mode**を拡張し、HTTPの標準キャッシュ仕様である**RFC9111**を実用的に利用できるようにするための仕組みです。

:::message
**ERC6944**については以下の記事を参考にして下さい。
:::

[\[ERC6944\] コントラクトが特定のリソース要求をどのようの処理するか指定するための仕組みを理解しよう！](/articles/c8f906536dcb7fe7492d)

`web3://` は、ブロックチェーン上のデータをWebコンテンツとして扱うための仕組みで、可能な限り通常のHTTPと同じ振る舞いをすることを目指しています。
その一環として、**ERC6944**ではスマートコントラクトがHTTPレスポンスヘッダ（Cache-ControlやETagなど）を返すことができるようになっています。

しかし、**ERC6944**の仕様では、クライアントから送られるHTTPリクエストヘッダがスマートコントラクトに渡りません。
そのため、本来のHTTPキャッシュで使われる以下の検証用ヘッダを扱うことができません。

* If-None-Match
    ETagと組み合わせて「**内容が変わっていないか**」を確認するためのヘッダです。
* If-Modified-Since
    最終更新日時を使って「**その後に変更があったか**」を確認するためのヘッダです。

この制限により、スマートコントラクト側は「**変更がなければ304 Not Modifiedを返す**」といった通常のHTTPキャッシュ検証を行えず、Cache-Control: max-ageのような単純な有効期限ベースの制御しかできません。

結果として、キャッシュの有効期限が切れるたびに必ずRPC呼び出しが発生し、毎回フルレスポンスを再生成する必要があります。
これは `web3://` をHTTPに近づけるという目的に対して非効率な状態です。

**ERC7774**はこの問題を解決するために、スマートコントラクトのイベントを利用して「**キャッシュが無効になったこと**」を外部に通知する仕組みを導入します。
これにより、HTTPリクエストヘッダを直接扱えない制約を回避しつつ、**RFC9111**に沿ったキャッシュ無効化モデルを実現します。

## 動機

EthereumのRPC呼び出しは、次の理由から非常にコストが高い処理です。

まず、ローカルノードの場合でもCPUやI/Oの負荷が大きくなります。
次に、外部の有料RPCプロバイダを利用している場合、リクエスト数に応じて課金されます。
さらに、多くのプロバイダはレート制限を設けているため、短時間に多数のリクエストを送るとアクセスが遮断される可能性があります。

`web3://` のURLをWebブラウザで読み込む場合、ページの表示だけで複数回のRPC呼び出しが発生します。
キャッシュが適切に効かなければ、以下のような問題が起こります。

* 同じデータを何度も取得するため、RPCコストが無駄に増えます。
* レート制限によりページ表示が失敗する可能性があります。
* 表示速度が遅くなり、通常のWebサイトと比べて体験が悪化します。

このため、HTTPと同じくキャッシュを最大限活用して RPC 呼び出し回数を減らすことが非常に重要です。

**ERC6944**では、スマートコントラクトが以下のようなHTTPキャッシュ関連ヘッダを返せます。

| ヘッダ           | 役割                             |
| ------------- | ------------------------------ |
| Cache-Control | キャッシュの有効期間や再検証方法を指定します。  |
| ETag | レスポンスの内容を識別するためのハッシュのような識別子です。 |

しかし前述の通り、リクエスト側のIf-None-MatchやIf-Modified-Sinceがコントラクトに届かないため、条件付きリクエストによる差分確認ができません。
その結果、キャッシュが有効かどうかの確認だけのために、毎回フルレスポンス生成とRPC実行が必要になります。

ここで**ERC7774**は別のアプローチを取ります。
HTTPリクエストで検証する代わりに、以下の考え方を採用します。

* データが更新されたときだけ、スマートコントラクトがイベントを発行します。
* クライアントやゲートウェイは、そのイベントを監視し、該当するキャッシュを無効化します。
* 更新がなければ、RPCを呼ばずにローカルキャッシュをそのまま使用します。

この方式には2つの利点があります。

1つ目は、HTTPヘッダをスマートコントラクトに渡せないという**ERC6944**の構造的制約を回避できることです。
2つ目は、キャッシュの無効化判定を「**コントラクトの実行**」ではなく「**イベント購読**」に移すことで、計算コストとRPC負荷を大幅に削減できることです。

そのため、**ERC7774**は**ERC6944 resolve mode**を変更せずに拡張でき、将来的にHTTPリクエストヘッダを扱える新しい resolve mode が登場した場合でも、同じ仕組みをそのまま利用できる設計になっています。

## 仕様

#### Cache-Control 拡張ディレクティブ evm-events

**ERC7774**は、`web3://` で配信されるページのキャッシュを、ブロックチェーンのイベントで無効化できるようにするための仕組みです。
そのために、HTTP標準のCache-Controlヘッダに `evm-events` という拡張ディレクティブを追加します。

:::message
ディレクティブとは、「**処理の振る舞いを指示するための制御用の指定子**」です。

HTTPでいうとCache-Controlの中の `max-age` や `no-cache` と同じ種類のもので、「**このレスポンスはどう扱うべきか**」をクライアントや中継サーバーに伝えるための指示です。

その中で **ERC7774**の `evm-events` は、「**このレスポンスのキャッシュは、通常の再検証ではなくブロックチェーンイベントで無効化されます**」という扱い方を指示するディレクティブです。
:::

これは **ERC6944**のresolve modeにおいて、「**このレスポンスは、通常のHTTPの再検証（If-None-Match など）ではなく、スマートコントラクトが発行するイベントによってキャッシュを無効化します**」という意思表示をクライアントに伝えるためのフラグです。

`web3://` のレスポンスでイベントベースのキャッシュを使う場合、スマートコントラクトは必ず以下を行います。

1. Cache-Controlヘッダに `evm-events` を含めます。
2. **RFC9111**に従い、ETagやmax-ageなど通常のHTTPキャッシュヘッダも付与します。
3. レスポンス内容が変わったとき、対応するキャッシュ無効化イベントを発行します。

ここで重要なのは、`evm-events` が付いていることで、以下の動作にクライアントが切り替わる点です。

- このページはイベントが来るまでキャッシュを保持してよい
- イベントが来たら、その瞬間にキャッシュを捨てる

#### `evm-events` の値の意味

`evm-events` には任意で値を指定できます。
値を指定すると、どのコントラクトのどのパスの無効化イベントを監視するかを明示できます。

構文は以下のように定義されています。

```abnf
cache-directive-value = [ address-path-absolute *( " " address-path-absolute ) ]
address-path-absolute = [ address ] path-absolute [ "?" query ]
address               = "0x" 20( HEXDIG HEXDIG )
path-absolute         = <RFC 3986 のパス>
query                 = <RFC 3986 のクエリ>
```

意味としては、以下を1セットとして空白区切りで複数並べられる形式です。

* コントラクトアドレス（省略可）
* パス
* クエリ

アドレスを省略した場合は、「**このレスポンスを返したコントラクト自身**」を指します。

代表的な例と意味は以下です。

| 指定例      | 意味          |
| ---------- | ----------------------- |
| `Cache-Control: evm-events`     | このページ自身のパスに対する無効化イベントが出たらキャッシュを削除します。        |
| `Cache-Control: evm-events="/path/path2"`   | 自分のパスに加えて、同じコントラクトの `/path/path2` が更新されたときもキャッシュを削除します。 |
| `Cache-Control: evm-events="0x.../path/path2"` | 別コントラクトの `/path/path2` が更新されたときもキャッシュを削除します。  |
| `Cache-Control: evm-events="0x..."` | 別コントラクトの「同じパス」が更新されたらキャッシュを削除します。 |
| `Cache-Control: evm-events="/path/path2 /path/path3"` | 複数のパスの更新をまとめて監視します。    |

これは、一覧ページが個別ページの更新に連動して無効化されるようなケースを想定しています。

#### キャッシュ無効化イベント

キャッシュを無効化するために、スマートコントラクトは以下のイベントを発行します。

```js
event ClearPathCache(string[] paths);
```

ここで指定する `paths` は、**ERC6860**の `pathQuery`、つまり以下のような「**パス + クエリ**」の組です。

```
/path
/path?key=value
```

パスの同一性は以下のルールで判定されます。

* 末尾の `/` は付けません（ルート `/` だけは例外です）。
* クエリパラメータの順序は無視されます。

そのため、以下は同一のキャッシュ対象として扱われます。

```
/test?a=1&b=2
/test?b=2&a=1
```

#### ワイルドカードの使用

大量のページをまとめて無効化するため、`paths` には `*` ワイルドカードを使えます。
ただし、ガス効率と実装の単純さのため、非常に限定された形のみ許可されています。

##### パス部分のワイルドカード

* `*` は1セグメント全体としてのみ使用できます。
* 他の文字と組み合わせると無効です。
* 空文字にはマッチせず、最低1文字が必要です。

| パターン      | マッチする       | マッチしない    |
| --------- | ----------- | ------------------------- |
| `/*`      | `/test`     | `/`, `/test/abc`          |
| `/test/*` | `/test/abc` | `/test/`, `/test/abc/def` |
| `/*/abc`  | `/test/abc` | `//abc`                   |
| `/t*t`    | 無効          | 常に無視                      |

##### クエリ値のワイルドカード

* 値として `*` 単体のみ使用可能です。
* 他の文字と組み合わせると無効です。

| パターン           | マッチする          | マッチしない                      |
| -------------- | -------------- | --------------------------- |
| `/abc?a=*`     | `/abc?a=zz`    | `/abc?a=`, `/abc?a=zz&b=cc` |
| `/abc?a=*&b=*` | `/abc?a=1&b=2` | なし                          |
| `/abc?a=z*`    | 無効             | 常に無視                        |

##### 全体ワイルドカード

`*` だけを指定した場合、そのコントラクト配下のすべてのパスが対象になります。

#### キャッシュ動作

##### `web3://` クライアントの状態

クライアントは、チェーンとコントラクトごとに以下の2状態を持ちます。

| 状態      | 内容    |
| ------- | -------------------- |
| イベント監視中 | `ClearPathCache` イベントを購読し、該当パスのキャッシュを即座に削除します。 |
| イベント非監視 | イベントを使わず、従来どおり毎回 RPC を実行します。 |

イベント監視中のときだけ、**ERC7774**の仕組みが有効になります。

#### キャッシュキーとメタ情報

クライアントは以下のキーでキャッシュ状態を管理します。

```text
(<chain id>, <contract address>, <ERC-6860 pathQuery>)
  =>
(<最終更新時刻>, <ETag>)
```

圧縮方式などでレスポンスが変わる場合（例: **ERC7618**）は、`Accept-Encoding` などのヘッダもキーに含めます。

イベント監視をやめた場合、このマッピングはすべて破棄されます。

#### イベント監視中のリクエスト処理

イベント監視中は、以下の流れで動作します。

1. まだキャッシュ情報が無い場合。
    RPCでコントラクトを呼び、`evm-events` とETag、または `max-age` 付きの日時情報を保存します。
2. すでにキャッシュがある場合。
   ブラウザからIf-None-MatchやIf-Modified-Sinceが送られてきたら、
   ローカルに保存しているETagや更新時刻と比較し、一致すればRPCを呼ばずに 304 を返します。
    一致しなければキャッシュを捨てて再取得します。

#### ブロックチェーンイベントによる無効化

イベント監視中のクライアントは、チェーン上の `ClearPathCache` を常に監視します。
指定されたパスに一致するキャッシュエントリが見つかった場合、それを即座に削除します。

これにより、以下の構造が成立します。

* スマートコントラクトはリクエストヘッダを読めなくてもよい。
* 変更が起きた瞬間だけイベントを出せばよい。
* クライアントは HTTP と同等のキャッシュ整合性を維持できる。

## 補足

**ERC7774**は、できる限り通常のHTTPと同じ振る舞いを維持するために、**RFC9111**で定義されている既存のHTTPキャッシュ関連ヘッダ（Cache-Control、ETag、Last-Modified など）をそのまま再利用します。
新しい独自仕様でキャッシュ制御を作るのではなく、Webの世界で長年使われてきた仕組みをそのまま `web3://` に持ち込む方が、実装者にも利用者にも理解しやすく、互換性も高いためです。

ここで重要になるのが、Cache-Controlに追加される `evm-events` ディレクティブです。
これは単なる最適化ではなく、「**このレスポンスはイベントベースのキャッシュ無効化を前提にしています**」という明示的な宣言の役割を持ちます。

もし `evm-events` が存在しないまま、通常の**RFC9111**のヘッダだけが返された場合を考えます。

* Webサイト側は「**HTTPキャッシュとしては正しく設定している**」つもりです。
* しかしスマートコントラクトは**ERC7774**を実装しておらず、`ClearPathCache` イベントも発行しません。
* 一方、`web3://` クライアントが**ERC7774**対応で、イベント駆動キャッシュを有効にしている場合、
    「**無効化イベントが来ない＝ずっと変更されていない**」と解釈してしまいます。

この状態では、実際にはコンテンツが変わっているのに、クライアントは永久にキャッシュを更新せず、古い内容を配信し続けることになります。

そのため**ERC7774**では、以下を安全装置としてこのディレクティブの明示を必須にしています。

* イベントによる無効化を前提にする場合は、必ず `evm-events` を Cache-Control に含める。
* これが無い場合は、「**このレスポンスはイベント無効化に対応していない**」と判断できる。

## セキュリティ

#### イベント反映までの遅延による一時的な古いデータ配信

**ERC7774**では、キャッシュの無効化はスマートコントラクトのイベントによって通知されます。
ユーザーのトランザクションが実行され、`ClearPathCache` イベントがブロックに含まれてから、`web3://` クライアントがそのイベントを取得し、処理するまでには必ず時間差があります。

この間の流れは以下です。

1. ユーザーが状態を変更するトランザクションを送信します。
2. コントラクトが新しい状態に基づいて `ClearPathCache` を発行します。
3. ブロックが確定します。
4. `web3://` クライアントがそのブロックを取得し、イベントを検出します。
5. 対応するキャッシュエントリを削除します。

4までの間、クライアントはまだ古いキャッシュを保持しているため、短時間ではありますが、変更前の内容を返す可能性があります。
これはHTTPキャッシュにおける再検証の遅延と本質的に同じ問題で、リアルタイム性と分散システムの性質上、完全には避けられません。

#### 無効化イベント未実装による恒久的な古いデータ配信

各ページについて、スマートコントラクトが正しく `ClearPathCache` イベントを発行しなければ、キャッシュは永遠に残り続けます。

つまり、以下の責務が明確にサイト側にあります。

* レスポンス内容が変わる可能性のあるすべてのパスについて、
* その変更タイミングで必ず対応する無効化イベントを発行する。

これを怠ると、`web3://` クライアントは「**変更が起きていない**」と判断し続け、結果として永久に古いコンテンツを配信します。
これは単なる性能問題ではなく、情報の整合性という意味でのセキュリティリスクになります。

#### チェーンリオーグ（再編成）時のキャッシュ巻き戻し

ブロックチェーンでは、まれにチェーンリオーグ（確定したと思われたブロックが取り消され、別のブロック列に置き換わる現象）が発生します。

この場合、以下の問題が起こり得ます。

* あるブロックで `ClearPathCache` イベントが発行され、クライアントがキャッシュを削除します。
* そのブロックがリオーグで取り消されます。
* 実際にはそのイベントは「**存在しなかったこと**」になります。
* しかしクライアント側のキャッシュ状態はすでに変更されています。

逆のケースもあります。

* 変更を含むブロックが取り消されます。
* しかしクライアントは、すでに新しい内容を正としてキャッシュしています。
* その結果、実際のチェーン状態と食い違った内容を配信し続けます。

このため **ERC7774** では、`web3://` クライアントはチェーンリオーグを検知した場合、
キャッシュの状態もブロックチェーンの状態に合わせて巻き戻す必要があるとしています。
これを行わないと、取り消されたはずの状態に基づくキャッシュが、次の無効化イベントが来るまで配信され続けることになります。

## 引用

Nicolas Deschildre (@nand2), Sam Wilson (@SamWilsn), "ERC-7774: Cache invalidation in ERC-5219 mode Web3 URL [DRAFT]," Ethereum Improvement Proposals, no. 7774, September 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7774.

## 最後に

今回は「ブロックチェーンイベントでWeb3ページのキャッシュを更新する仕組みを提案している**ERC7774**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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