fix(recovery): make the restore-anyway override work for dead relay lists - #759
Conversation
…ists A relay list restore is now judged on the write relays the restored version names, as the spec allows, so the override can fix a list whose current relays are dead; the current relays still get it as a best effort. The override's confirmation re-arms after every attempt instead of staying ticked, the retry button says it retries the restore, and a stale comment no longer claims the spec is silent on unreachable relays.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Relay-list cache updates and fallback handling remain unresolved, with a test also needing correction.
Review effort: Lite
Findings: 1
Open (2)
What changed in this PR
Updates Lazarus recovery so relay-list restores can bypass dead write relays and resets restore confirmation between attempts.
Changes:
- Select restored relay-list write relays for publishing.
- Reset override confirmation after each attempt.
- Update retry wording and package version.
| File | Summary |
|---|---|
src/lib/lazarus/source.ts |
Selects publish relays for restored events. |
src/lib/lazarus/source.test.ts |
Tests restored relay selection. |
src/lib/lazarus/lazarusPublish.ts |
Passes the chosen event into relay selection. |
src/lib/lazarus/lazarusPublish.test.ts |
Verifies relay-selection integration. |
src/components/LazarusDeltaPanel.svelte |
Resets confirmation and updates retry text. |
package.json |
Bumps the application version. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| } | ||
|
|
||
| const { write, extra } = await getLazarusPublishRelays(pubkey, respondingRelays); | ||
| const { write, extra } = await getLazarusPublishRelays(pubkey, respondingRelays, chosen); |
There was a problem hiding this comment.
Fixed in 6275e4e. relayListCache gained seedFromEvent(pubkey, event) — it writes the recovered kind-10002 into the memory cache and IndexedDB exactly like a network answer (same freshness/persistence, in-flight fetch for the pubkey dropped) — and refreshLocalCopy now branches on kind 10002 to seed it with the published event. Seeding rather than invalidating on purpose: an invalidate would refetch over relays that may still serve the clobbered copy, while the seed answers every subsequent get() this session with the restored list. Covered by a new test asserting the seed happens with the pubkey and the recovered event (and does not fire for other kinds).
| const restored = restoring?.kind === 10002 ? parseRelayList(restoring).write : []; | ||
| const write = restored.length > 0 ? restored : current; |
There was a problem hiding this comment.
Fixed in 6275e4e. A restored kind-10002 that names no write relays now falls back to getCurrentRelays() instead of current — consistent with getUserRelays's own semantics, where a list naming no write relays is 'missing' and the app's relays stand in as the write set. Falling back to current judged the restore on the dead write relays the override exists to replace. The current relays still receive the recovery as a best-effort extra. New test pins the read-only-restored-list case (write = app defaults, dead relay kept as extra).
…estore, app relays for read-only restores Copilot review on zapcooking#759: - refreshLocalCopy had no kind-10002 branch, so a successful relay-list restore left relayListCache serving the dead cached list — the next scan or edit in the session kept targeting the dead relays and could clobber the restore. relayListCache gains seedFromEvent(pubkey, event), written exactly like a network answer, and the publish path seeds it with the recovered event (the spec's update-the-local-copy duty). - getLazarusPublishRelays fell back to the CURRENT write relays when the restored kind-10002 named no write relays — the dead relays the override exists to replace. A restored list with no write relays is 'missing' the same way getUserRelays treats one, so the app's relays stand in as the write set; the current relays still get the event as a best effort.
# Conflicts: # package.json
The kind 10044 registry entry counted p tags, but NIP-4e lists encryption pubkeys in n tags, so every key list scanned as empty.
The scan asked relays through nostr-tools' subscribeMany, which reports a relay that refused the connection or sent CLOSED as an EOSE before the close, and invents an EOSE for a silent relay after 4.4 seconds. Dead relays counted as answered with nothing, so a refused write relay could confirm current and let the re-read before a restore pass. Each relay is now asked directly: only EOSE is an answer, a refused connection or a CLOSED is a failure, and silence times out. Items are counted once each, a tag without a value isn't one, a profile's items are its fields, and relay URLs compare normalized. Versions from the same second order as NIP-01 keeps them, including in the re-read. The review says which side's private items couldn't be decrypted, and uncounted ones on current take the removal confirmation. Relay lists get added and removed counts too, and the encryption key list states what both versions mean in the restore's direction. After a restore, the local event cache takes the recovered version instead of only being invalidated.


Follow-up to #753, bringing data recovery up to the Lazarus spec's 0.6.2-draft (https://github.com/dmnyc/lazarus/blob/main/SPEC.md).
subscribeMany, which reports a relay that refused the connection or sentCLOSEDas an EOSE, and invents one for a relay that stays silent past 4.4 seconds. A dead write relay could confirm the current version, and the check before signing could pass with no write relay reachable. This affects the recovery that shipped in Lazarus data recovery replaces NIP-78 account backup #753. Each relay is now asked directly, and only a real EOSE is an answer.ntags NIP-4e puts keys in; the registry countedptags, so every key list scanned as empty. The review's description of the two versions had the directions reversed; it now states what both mean, in the direction of the restore.