mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 03:05:19 +00:00
Inject notification/favorites singletons through coordinator contexts
NotificationService.shared and FavoritesPersistenceService.shared accesses across nine coordinators/components consolidate into ChatViewModel witnesses behind intent-named context members (notifyPrivateMessage, notifyMention, favoriteRelationship(forNoiseKey:), allFavoriteRelationships, postLocalNotification, ...). Singleton reach from coordinators is now zero; nine new tests cover previously untestable notification/favorites flows. Also root-causes the long-flaky gift-wrap dedup test: parallel tests share LocationChannelManager.shared, so channel-switch tests trigger clearProcessedNostrEvents() on every live ChatViewModel, wiping the dedup record mid-test. The invariant (forged-signature copies never poison dedup) now lives as a deterministic NostrInboundPipeline mock-context test; two Schnorr concurrency probes added along the way stay as regression guards for the P256K shared-context assumptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import bitchat
|
||||
|
||||
struct SchnorrConcurrencyRepro {
|
||||
@Test func concurrentVerificationOfValidEventAlwaysSucceeds() async throws {
|
||||
let identity = try NostrIdentity.generate()
|
||||
let event = NostrEvent(
|
||||
pubkey: identity.publicKeyHex,
|
||||
createdAt: Date(),
|
||||
kind: .ephemeralEvent,
|
||||
tags: [["g", "sn"]],
|
||||
content: "concurrency probe"
|
||||
)
|
||||
let signed = try event.sign(with: identity.schnorrSigningKey())
|
||||
#expect(signed.isValidSignature())
|
||||
|
||||
let failures = await withTaskGroup(of: Int.self) { group in
|
||||
for _ in 0..<8 {
|
||||
group.addTask {
|
||||
var localFailures = 0
|
||||
for _ in 0..<250 {
|
||||
if !signed.isValidSignature() { localFailures += 1 }
|
||||
}
|
||||
return localFailures
|
||||
}
|
||||
}
|
||||
var total = 0
|
||||
for await f in group { total += f }
|
||||
return total
|
||||
}
|
||||
#expect(failures == 0, "Schnorr verification returned false \(failures)/2000 times under concurrency")
|
||||
}
|
||||
|
||||
@Test func verificationSurvivesConcurrentSigningAndKeyGeneration() async throws {
|
||||
let identity = try NostrIdentity.generate()
|
||||
let event = NostrEvent(
|
||||
pubkey: identity.publicKeyHex,
|
||||
createdAt: Date(),
|
||||
kind: .ephemeralEvent,
|
||||
tags: [["g", "sn"]],
|
||||
content: "concurrency probe"
|
||||
)
|
||||
let signed = try event.sign(with: identity.schnorrSigningKey())
|
||||
#expect(signed.isValidSignature())
|
||||
|
||||
// Signing and key generation may mutate shared library state
|
||||
// (secp256k1 context randomization); verification must stay correct
|
||||
// while they run on other tasks.
|
||||
let failures = await withTaskGroup(of: Int.self) { group in
|
||||
for worker in 0..<8 {
|
||||
group.addTask {
|
||||
if worker < 4 {
|
||||
var localFailures = 0
|
||||
for _ in 0..<250 {
|
||||
if !signed.isValidSignature() { localFailures += 1 }
|
||||
}
|
||||
return localFailures
|
||||
} else {
|
||||
for i in 0..<100 {
|
||||
if let id = try? NostrIdentity.generate() {
|
||||
let e = NostrEvent(
|
||||
pubkey: id.publicKeyHex,
|
||||
createdAt: Date(),
|
||||
kind: .ephemeralEvent,
|
||||
tags: [],
|
||||
content: "signer \(worker)-\(i)"
|
||||
)
|
||||
_ = try? e.sign(with: id.schnorrSigningKey())
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
var total = 0
|
||||
for await f in group { total += f }
|
||||
return total
|
||||
}
|
||||
#expect(failures == 0, "Schnorr verification returned false \(failures)/1000 times while signing ran concurrently")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user