Add performance baselines; check dedup before verifying Nostr signatures

New PerformanceBaselineTests measure the hot paths (Nostr inbound, BLE
packet pipeline, GCS filters, delivery index, message formatting) with
deterministic fixtures and logged PERF metrics - baselines, not
assertions, so they cannot flake CI.

The suite immediately exposed that every inbound handler ran Schnorr
verification before the dedup lookup, so duplicate events - which
dominate real multi-relay traffic - each paid ~0.5ms of main-actor
crypto for nothing. All five handlers now do cheap rejects (kind, dedup
lookup) first and only record an event as processed AFTER its signature
verifies, so a forged-signature copy can never poison the dedup set and
suppress the genuine event. Gift-wrap verification also moves entirely
off the main actor with an atomic main-actor check-and-record.

Measured: duplicate-event handling 2.2k -> 1.39M events/sec (~640x);
fresh events unchanged (crypto-bound).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-10 21:11:06 +02:00
co-authored by Claude Fable 5
parent 4b287f7490
commit 80bed1f395
3 changed files with 543 additions and 8 deletions
+25 -7
View File
@@ -246,13 +246,17 @@ final class ChatNostrCoordinator {
@MainActor
func subscribeNostrEvent(_ event: NostrEvent) {
guard let context else { return }
guard event.isValidSignature() else { return }
// Cheap rejects (kind, dedup lookup) before Schnorr verification
// duplicates dominate real traffic and must not pay for crypto.
// Only verified events are recorded, so a forged-signature copy can
// never poison the dedup set and suppress the genuine event.
guard (event.kind == NostrProtocol.EventKind.ephemeralEvent.rawValue
|| event.kind == NostrProtocol.EventKind.geohashPresence.rawValue),
!context.hasProcessedNostrEvent(event.id)
else {
return
}
guard event.isValidSignature() else { return }
context.recordProcessedNostrEvent(event.id)
@@ -327,8 +331,9 @@ final class ChatNostrCoordinator {
@MainActor
func subscribeGiftWrap(_ giftWrap: NostrEvent, id: NostrIdentity) {
guard let context else { return }
guard giftWrap.isValidSignature() else { return }
// Dedup lookup before Schnorr verification; record only after it passes.
guard !context.hasProcessedNostrEvent(giftWrap.id) else { return }
guard giftWrap.isValidSignature() else { return }
context.recordProcessedNostrEvent(giftWrap.id)
guard let (content, senderPubkey, rumorTs) = try? NostrProtocol.decryptPrivateMessage(
@@ -441,14 +446,15 @@ final class ChatNostrCoordinator {
@MainActor
func handleNostrEvent(_ event: NostrEvent) {
guard let context else { return }
guard event.isValidSignature() else { return }
// Cheap rejects (kind, dedup lookup) before Schnorr verification
// duplicates dominate real traffic and must not pay for crypto.
guard (event.kind == NostrProtocol.EventKind.ephemeralEvent.rawValue
|| event.kind == NostrProtocol.EventKind.geohashPresence.rawValue)
else {
return
}
if context.hasProcessedNostrEvent(event.id) { return }
guard event.isValidSignature() else { return }
context.recordProcessedNostrEvent(event.id)
// Sampled: fires for every geo event and floods dev logs in busy geohashes.
@@ -559,10 +565,11 @@ final class ChatNostrCoordinator {
@MainActor
func handleGiftWrap(_ giftWrap: NostrEvent, id: NostrIdentity) {
guard let context else { return }
guard giftWrap.isValidSignature() else { return }
// Dedup lookup before Schnorr verification; record only after it passes.
if context.hasProcessedNostrEvent(giftWrap.id) {
return
}
guard giftWrap.isValidSignature() else { return }
context.recordProcessedNostrEvent(giftWrap.id)
guard let (content, senderPubkey, rumorTs) = try? NostrProtocol.decryptPrivateMessage(
@@ -822,9 +829,12 @@ final class ChatNostrCoordinator {
@MainActor
func handleNostrMessage(_ giftWrap: NostrEvent) {
guard let context else { return }
guard giftWrap.isValidSignature() else { return }
// Cheap dedup pre-check only; Schnorr verification runs off-main in
// processNostrMessage, which then does the authoritative
// check-and-record. Recording stays after verification so a
// forged-signature copy can never poison the dedup set and suppress
// the genuine event.
if context.hasProcessedNostrEvent(giftWrap.id) { return }
context.recordProcessedNostrEvent(giftWrap.id)
Task.detached(priority: .userInitiated) { [weak self] in
await self?.processNostrMessage(giftWrap)
@@ -834,6 +844,14 @@ final class ChatNostrCoordinator {
func processNostrMessage(_ giftWrap: NostrEvent) async {
guard giftWrap.isValidSignature() else { return }
guard let context else { return }
// Authoritative check-and-record, atomic on the main actor so two
// concurrent detached tasks can't both process the same event.
let alreadyProcessed: Bool = await MainActor.run {
if context.hasProcessedNostrEvent(giftWrap.id) { return true }
context.recordProcessedNostrEvent(giftWrap.id)
return false
}
if alreadyProcessed { return }
let currentIdentity: NostrIdentity? = await MainActor.run {
context.currentNostrIdentity()
}