desktop: quartz enhancements
This commit is contained in:
Vendored
+43
-1
@@ -31,6 +31,8 @@ import com.vitorpamplona.amethyst.commons.services.nwc.NwcPaymentTracker
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.checkSignature
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.verify
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.taggedAddresses
|
||||
@@ -49,6 +51,7 @@ import com.vitorpamplona.quartz.nip51Lists.bookmarkList.OldBookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.utils.DualCase
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
@@ -162,15 +165,51 @@ class DesktopLocalCache : ICacheProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Event verification -----
|
||||
|
||||
/**
|
||||
* Verifies an event's id-hash and Schnorr signature. On failure, logs
|
||||
* the offending kind/createdAt and returns false so callers can drop it.
|
||||
* Mirrors Amethyst Android's [com.vitorpamplona.amethyst.model.LocalCache.justVerify]
|
||||
* so unverified relay traffic never reaches the desktop cache.
|
||||
*/
|
||||
fun justVerify(event: Event): Boolean =
|
||||
if (!event.verify()) {
|
||||
try {
|
||||
event.checkSignature()
|
||||
} catch (e: Exception) {
|
||||
Log.w("Event Verification Failed") {
|
||||
"Kind: ${event.kind} createdAt=${event.createdAt} id=${event.id} pubkey=${event.pubKey} reason=${e.message}"
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
// ----- Event consumption -----
|
||||
|
||||
/**
|
||||
* Routes an event to the appropriate consume method.
|
||||
* Returns true if the event was consumed (new), false if already seen.
|
||||
* Returns true if the event was consumed (new), false if already seen
|
||||
* or if signature verification failed.
|
||||
*/
|
||||
fun consume(
|
||||
event: Event,
|
||||
relay: NormalizedRelayUrl?,
|
||||
): Boolean {
|
||||
if (!justVerify(event)) return false
|
||||
return consumeAssumingVerified(event, relay)
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes an already-verified event to its consume method. Internal seam
|
||||
* exposed for tests that exercise routing/cache logic with synthetic
|
||||
* events whose signatures are not real.
|
||||
*/
|
||||
internal fun consumeAssumingVerified(
|
||||
event: Event,
|
||||
relay: NormalizedRelayUrl?,
|
||||
): Boolean =
|
||||
when (event) {
|
||||
is MetadataEvent -> {
|
||||
@@ -455,6 +494,7 @@ class DesktopLocalCache : ICacheProvider {
|
||||
relay: NormalizedRelayUrl?,
|
||||
onResponse: suspend (LnZapPaymentResponseEvent) -> Unit,
|
||||
): Boolean {
|
||||
if (!justVerify(event)) return false
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
@@ -482,6 +522,7 @@ class DesktopLocalCache : ICacheProvider {
|
||||
event: LnZapPaymentResponseEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
): Boolean {
|
||||
if (!justVerify(event)) return false
|
||||
val requestId = event.requestId()
|
||||
val pending = paymentTracker.onResponseReceived(requestId) ?: return false
|
||||
|
||||
@@ -545,6 +586,7 @@ class DesktopLocalCache : ICacheProvider {
|
||||
// ----- Own event consumption -----
|
||||
|
||||
override fun justConsumeMyOwnEvent(event: Event): Boolean {
|
||||
if (!justVerify(event)) return false
|
||||
// For addressable/replaceable events, store in the addressable note cache
|
||||
// so state holders (Nip65RelayListState, etc.) pick it up via their flows
|
||||
if (event is com.vitorpamplona.quartz.nip01Core.core.AddressableEvent) {
|
||||
|
||||
+31
-8
@@ -123,20 +123,43 @@ class DesktopRelaySubscriptionsCoordinator(
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val consumed = localCache.consume(event, relay)
|
||||
if (consumed) {
|
||||
_lastEventAt.value = System.currentTimeMillis()
|
||||
val note = localCache.getNoteIfExists(event.id) ?: return@launch
|
||||
eventBundler.invalidateList(note) { batch ->
|
||||
localCache.eventStream.emitNewNotes(batch)
|
||||
}
|
||||
}
|
||||
runConsume(localCache.consume(event, relay), event)
|
||||
} catch (e: Exception) {
|
||||
println("Coordinator: failed to consume kind=${event.kind} id=${event.id} relay=$relay: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test-only seam — bypasses signature verification in the cache so unit
|
||||
* tests can pump synthetic events through the full coordinator pipeline.
|
||||
* Production code MUST use [consumeEvent].
|
||||
*/
|
||||
internal fun consumeEventAssumingVerified(
|
||||
event: Event,
|
||||
relay: NormalizedRelayUrl?,
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
runConsume(localCache.consumeAssumingVerified(event, relay), event)
|
||||
} catch (e: Exception) {
|
||||
println("Coordinator: failed to consume kind=${event.kind} id=${event.id} relay=$relay: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runConsume(
|
||||
consumed: Boolean,
|
||||
event: Event,
|
||||
) {
|
||||
if (!consumed) return
|
||||
_lastEventAt.value = System.currentTimeMillis()
|
||||
val note = localCache.getNoteIfExists(event.id) ?: return
|
||||
eventBundler.invalidateList(note) { batch ->
|
||||
localCache.eventStream.emitNewNotes(batch)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a consolidated interaction subscription for the given note IDs.
|
||||
* Subscribes to kinds 7 (reactions), 9735 (zaps), 6 (reposts), and 1 (replies)
|
||||
|
||||
Reference in New Issue
Block a user