Open3

[Nostr] Replaceable イベントの更新方法

雪猫雪猫

Replaceable イベント

Nostr では NIP-01 において kind: 0, 3 および 10000 <= kind < 20000 のイベントは Replaceable イベントとして定義されており kind + pubkey をキーとして最新のイベントだけが保持されます。[1]

更新時の課題

イベントは通常リレーに送信され保存されます。
このときに保存先が 1 ヶ所であれば「最新のイベント」を取得し、なければ新規作成、あれば上書きして保存でいいのですが Nostr では基本的に複数のリレーにイベントを送信します。
送信先はクライアントによって違ったりするため各リレーのイベントの状態が異なるケースが出てきます。

また、ブラウザの WebSocket には接続数の上限があり複数のタブでクライアントを開くと意外と簡単に上限に達します。
そうなると一部または全てのリレーのイベントを取得/送信できなくなります。

更に、環境によっては回線が安定しているとは限りません。
そうなるとイベントを取得/送信できないことがあります。

実際の UI では更新処理中にフォロー等のボタンが追加で押されることがあります。

これらの状態を考慮し「最新のイベント」を探して正しく更新しなければなりません。

脚注
  1. 似たものとして kind + pubkey + d タグをキーとした Addressable イベントもありますが考え方は同様です。 ↩︎

雪猫雪猫

解決方法

ローカルにキャッシュを作成して更新時はリレーにある最新イベントと比較して整合性が取れているかを確認します。
ブラウザの場合キャッシュは localStorage または IndexedDB に保存します。

具体的な更新手順

  1. 更新処理が同時に走らないようにミューテックスまたはキューで管理します。(キューの方が操作をブロックしないのでベター)
  2. 最新のイベントをリレーから取得してローカルのキャッシュと比較し整合性が取れない場合はエラーにします。
  3. 最新のイベントを元に情報を更新してリレーへ送信し UI にも反映します。
  4. キューの場合は残りのキューを同様に処理します。

整合性のチェック

キャッシュあり キャッシュなし
最新イベントあり created_at を比較 問題なし
最新イベントなし エラー 問題なし

created_at を比較してキャッシュの方が新しい場合に問題なしとするかは方針によります。(リレーからの取得が正しく行われていないということなので更に新しいイベントが存在する可能性がある)
後述しますが UI への反映を投機的に行っている場合は最新イベントの方が新しいときにエラーとするか改めて更新処理を行い UI へ反映しなおす必要があります。

UX 向上のヒント

手順 2. で最新のイベントの取得を待つ(=全リレーの EOSE を待つ)と UX がよくないので先に UI に反映してエラーの場合にロールバックすると UX が改善されます。
手順 4. でキューに複数溜まっている場合はまとめて処理するとベター。

それでも

(他のクライアントから移ってきたなど)キャッシュがない状態で最新イベントを取得し損ねると正しく判定できないことがありますし、キャッシュより新しいイベントが複数ある場合に本当に最新のイベントが取得できているか確認する術はありません。
これは Nostr の仕組み上どうしようもないため諦めるほかありません。