refactor(quartz): NIP-86 bans purge matching events via onBan(filter) hook
Nip86Server used to take an optional IEventStore and call store.delete()
only on banevent — banpubkey and disallowkind would update the BanStore
but leave existing matching events served by REQ. That's inconsistent
(an operator who bans a spam pubkey expects the spam to be gone) and
a real safety issue (banning a CSAM event wouldn't actually remove
existing copies if the store was null).
Replace the IEventStore param with onBan: suspend (Filter) -> Unit. The
dispatcher fires it after each ban method with the Filter that selects
the now-banned events:
banpubkey → Filter(authors = [pk])
banevent → Filter(ids = [id])
disallowkind→ Filter(kinds = [k])
KtorRelay wires onBan = { f -> relay.store.delete(f) }, so the existing
SQLite delete-by-filter path handles every shape without the caller
having to know which field to populate. The default no-op keeps the
unit test for the dispatcher dependency-free.
withInt is now suspend so disallowKind's handler can call the suspending
onBan inline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -113,7 +113,12 @@ class KtorRelay(
|
||||
}
|
||||
}
|
||||
|
||||
private val nip86Server = Nip86Server(banStore = relay.banStore, infoHolder = infoHolder, store = relay.store)
|
||||
private val nip86Server =
|
||||
Nip86Server(
|
||||
banStore = relay.banStore,
|
||||
infoHolder = infoHolder,
|
||||
onBan = { filter -> relay.store.delete(filter) },
|
||||
)
|
||||
private val nip86Route =
|
||||
Nip86HttpRoute(
|
||||
server = nip86Server,
|
||||
|
||||
+35
-10
@@ -21,7 +21,6 @@
|
||||
package com.vitorpamplona.quartz.nip86RelayManagement.server
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.store.IEventStore
|
||||
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
||||
import com.vitorpamplona.quartz.nip86RelayManagement.rpc.AllowedPubkey
|
||||
import com.vitorpamplona.quartz.nip86RelayManagement.rpc.BannedEvent
|
||||
@@ -46,8 +45,8 @@ import kotlinx.serialization.json.int
|
||||
*
|
||||
* Holds the [BanStore] (mutated by ban/allow methods), an [InfoHolder]
|
||||
* for the live NIP-11 doc (mutated by `changerelay*` methods, which
|
||||
* atomically swap it), and an optional [IEventStore] so `banevent`
|
||||
* can also delete the offending event from the store.
|
||||
* atomically swap it), and an [onBan] hook so each ban method can
|
||||
* retroactively purge matching events from the relay's event store.
|
||||
*
|
||||
* Transport-agnostic — relay implementations call [dispatch] from
|
||||
* whatever HTTP route they expose (e.g. POST `application/nostr+json+rpc`),
|
||||
@@ -67,7 +66,23 @@ class Nip86Server(
|
||||
* every request, not cache it.
|
||||
*/
|
||||
private val infoHolder: InfoHolder,
|
||||
private val store: IEventStore? = null,
|
||||
/**
|
||||
* Called from `banpubkey` / `banevent` / `disallowkind` AFTER the
|
||||
* [BanStore] has been updated, with a [Filter] selecting all events
|
||||
* the operator just banned: respectively `Filter(authors=[pk])`,
|
||||
* `Filter(ids=[id])`, `Filter(kinds=[k])`. Production callers wire
|
||||
* this to `IEventStore.delete(filter)` so the existing offending
|
||||
* events are removed from storage, not just blocked from re-ingest.
|
||||
* Without it, a ban would only stop *future* matches while clients
|
||||
* kept seeing whatever was already there — important for spam
|
||||
* cleanup, and a real safety issue for `banevent` on illegal /
|
||||
* leaked content.
|
||||
*
|
||||
* Defaults to a no-op so unit tests for the dispatcher don't need
|
||||
* to stand up an event store. Production callers (e.g.
|
||||
* `geode/KtorRelay`) wire it to `store.delete(filter)`.
|
||||
*/
|
||||
private val onBan: suspend (Filter) -> Unit = {},
|
||||
) {
|
||||
/** Pluggable container so the relay's NIP-11 doc can be swapped at runtime. */
|
||||
interface InfoHolder {
|
||||
@@ -108,7 +123,12 @@ class Nip86Server(
|
||||
}
|
||||
|
||||
Nip86Method.BAN_PUBKEY -> {
|
||||
withHexAndReason(req, "pubkey") { pk, reason -> banStore.banPubkey(pk, reason) }
|
||||
withHexAndReason(req, "pubkey") { pk, reason ->
|
||||
banStore.banPubkey(pk, reason)
|
||||
// Purge everything the banned author had posted
|
||||
// so REQ stops serving their history.
|
||||
onBan(Filter(authors = listOf(pk)))
|
||||
}
|
||||
}
|
||||
|
||||
Nip86Method.UNBAN_PUBKEY -> {
|
||||
@@ -134,8 +154,9 @@ class Nip86Server(
|
||||
Nip86Method.BAN_EVENT -> {
|
||||
withHexAndReason(req, "event_id") { id, reason ->
|
||||
banStore.banEvent(id, reason)
|
||||
// Also remove the event from the store if present.
|
||||
store?.delete(Filter(ids = listOf(id)))
|
||||
// Also remove the event from the relay's event
|
||||
// store so REQ stops serving the offending copy.
|
||||
onBan(Filter(ids = listOf(id)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +173,11 @@ class Nip86Server(
|
||||
}
|
||||
|
||||
Nip86Method.DISALLOW_KIND -> {
|
||||
withInt(req, "kind") { k -> banStore.disallowKind(k) }
|
||||
withInt(req, "kind") { k ->
|
||||
banStore.disallowKind(k)
|
||||
// Purge every event of the now-disallowed kind.
|
||||
onBan(Filter(kinds = listOf(k)))
|
||||
}
|
||||
}
|
||||
|
||||
Nip86Method.LIST_ALLOWED_KINDS -> {
|
||||
@@ -205,10 +230,10 @@ class Nip86Server(
|
||||
return okTrue
|
||||
}
|
||||
|
||||
private inline fun withInt(
|
||||
private suspend inline fun withInt(
|
||||
req: Nip86Request,
|
||||
label: String,
|
||||
action: (Int) -> Unit,
|
||||
action: suspend (Int) -> Unit,
|
||||
): Nip86Response {
|
||||
val v = req.params.firstInt() ?: return malformed("expected [$label]")
|
||||
action(v)
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ class Nip86ServerTest {
|
||||
private fun fixture(): Triple<Nip86Server, BanStore, Holder> {
|
||||
val store = BanStore()
|
||||
val holder = Holder(Nip11RelayInformation(name = "before", description = "before-desc"))
|
||||
val server = Nip86Server(banStore = store, infoHolder = holder, store = null)
|
||||
val server = Nip86Server(banStore = store, infoHolder = holder)
|
||||
return Triple(server, store, holder)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user