From c4bfdc72dc0a4fcff1648c87eef7a58888935fa4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 4 Jun 2026 16:17:14 +0000 Subject: [PATCH] fix: detach channel in removeFromCache to mirror deleteNote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit removeFromCache() relied solely on note.inGatherers to detach a pruned note from its channels, while deleteNote() additionally resolved the channel via getAnyChannel() and removed the note there too. inGatherers is normally authoritative (Channel.addNote always calls addGatherer), so this is defensive rather than a confirmed live leak — but it closes the divergence so both removal paths detach channels identically. Guards against any future consume path that adds a note to a getAnyChannel- resolvable channel without the gatherer link: otherwise the note would linger in the channel's notes map after leaving the cache, leaking it and letting a relay echo mint a duplicate Note with the same id. https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s --- .../java/com/vitorpamplona/amethyst/model/LocalCache.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index f7847f225a..c9372f62d4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -2785,6 +2785,13 @@ object LocalCache : ILocalCache, ICacheProvider { note.inGatherers?.forEach { it.removeNote(note) } + // Mirror deleteNote(): inGatherers is normally authoritative for channel + // membership (Channel.addNote always calls note.addGatherer), but resolve + // the channel from the event as a belt-and-suspenders detach so a note can + // never linger in a channel's notes map after it leaves the cache — that + // would leak the note and let a relay echo mint a duplicate with the same id. + getAnyChannel(note)?.removeNote(note) + val noteEvent = note.event if (noteEvent is ReportEvent) {