mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
feat(nip46): persist serviced request ids so a restart never re-signs replays
The 30s window shrank the restart re-sign problem but couldn't close it: a relay replays stored ephemeral requests on re-subscribe, and the in-memory dedup set is wiped on restart, so anything within the window came back. Persist the recently-serviced kind-24133 event ids (bounded to 128) and seed the service's dedup set from them on start, so a replay after an app restart is dropped by EXACT event id. Chosen over a created_at high-water mark on purpose: a global timestamp floor would wrongly drop a second connected app whose clock lags behind another's, whereas id-matching is immune to client clock skew. The `since` filter still bounds how far back relays replay. - AccountSettings.nip46SeenRequestIds (persisted via putStringSet) + host-side bounded LinkedHashSet, fed to NostrConnectSignerService.initialSeen and advanced through onHandledId. - Tests: a fresh request whose id was serviced last session is not repeated; the serviced id is reported for persistence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FHr2mu5SiHwYNR7evYUuF
This commit is contained in:
+36
@@ -252,6 +252,42 @@ class NostrConnectSignerServiceTest {
|
||||
assertEquals(0, client.published.size, "a minutes-old replayed request is dropped, not re-signed")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aFreshRequestWhoseIdWasServicedLastSessionIsNotRepeated() =
|
||||
runTest {
|
||||
val client = LoopbackClient()
|
||||
val signer = serverSigner()
|
||||
val processor = BunkerRequestProcessor(signer, { setOf(relay) }, AllowAuthorizer())
|
||||
|
||||
// A still-fresh request whose id the previous run already serviced (seeded via initialSeen,
|
||||
// as the host would restore from disk). It must be deduped by exact id — even though its
|
||||
// created_at is within the window — so an app restart doesn't re-sign a relay's replay.
|
||||
val template = EventTemplate<Event>(createdAt = 1L, kind = 1, tags = emptyArray(), content = "again")
|
||||
val replayed = request(BunkerRequestSign(id = "req", event = template))
|
||||
val service = NostrConnectSignerService(client, signer, processor, setOf(relay), initialSeen = setOf(replayed.id))
|
||||
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { service.run() }
|
||||
client.deliver(replayed)
|
||||
|
||||
assertEquals(0, client.published.size, "an id serviced last session is not signed again after restart")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun aHandledIdIsReportedForPersistence() =
|
||||
runTest {
|
||||
val client = LoopbackClient()
|
||||
val signer = serverSigner()
|
||||
val processor = BunkerRequestProcessor(signer, { setOf(relay) }, AllowAuthorizer())
|
||||
val handled = mutableListOf<String>()
|
||||
val service = NostrConnectSignerService(client, signer, processor, setOf(relay), onHandledId = { handled.add(it) })
|
||||
|
||||
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) { service.run() }
|
||||
val event = request(BunkerRequestSign(id = "req", event = EventTemplate<Event>(createdAt = 1L, kind = 1, tags = emptyArray(), content = "x")))
|
||||
client.deliver(event)
|
||||
|
||||
assertEquals(listOf(event.id), handled, "the serviced event id is reported so the host can persist it")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logoutRequestInvokesAuthorizerAndAcks() =
|
||||
runTest {
|
||||
|
||||
Reference in New Issue
Block a user