Code review:
rethrow CancellationException; offload NWC verify off WS thread align verification API with Amethyst Android
This commit is contained in:
Vendored
+12
-24
@@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
|||||||
import com.vitorpamplona.amethyst.commons.model.cache.LargeSoftCache
|
import com.vitorpamplona.amethyst.commons.model.cache.LargeSoftCache
|
||||||
import com.vitorpamplona.amethyst.commons.services.nwc.NwcPaymentTracker
|
import com.vitorpamplona.amethyst.commons.services.nwc.NwcPaymentTracker
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.checkSignature
|
import com.vitorpamplona.quartz.nip01Core.crypto.checkSignature
|
||||||
@@ -52,6 +53,7 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
|||||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||||
import com.vitorpamplona.quartz.utils.DualCase
|
import com.vitorpamplona.quartz.utils.DualCase
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
@@ -165,19 +167,12 @@ 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 =
|
fun justVerify(event: Event): Boolean =
|
||||||
if (!event.verify()) {
|
if (!event.verify()) {
|
||||||
try {
|
try {
|
||||||
event.checkSignature()
|
event.checkSignature()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
if (e is CancellationException) throw e
|
||||||
Log.w("Event Verification Failed") {
|
Log.w("Event Verification Failed") {
|
||||||
"Kind: ${event.kind} createdAt=${event.createdAt} id=${event.id} pubkey=${event.pubKey} reason=${e.message}"
|
"Kind: ${event.kind} createdAt=${event.createdAt} id=${event.id} pubkey=${event.pubKey} reason=${e.message}"
|
||||||
}
|
}
|
||||||
@@ -189,25 +184,16 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
|
|
||||||
// ----- Event consumption -----
|
// ----- Event consumption -----
|
||||||
|
|
||||||
/**
|
|
||||||
* Routes an event to the appropriate consume method.
|
|
||||||
* Returns true if the event was consumed (new), false if already seen
|
|
||||||
* or if signature verification failed.
|
|
||||||
*/
|
|
||||||
fun consume(
|
fun consume(
|
||||||
event: Event,
|
event: Event,
|
||||||
relay: NormalizedRelayUrl?,
|
relay: NormalizedRelayUrl?,
|
||||||
|
wasVerified: Boolean = false,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (!justVerify(event)) return false
|
if (!wasVerified && !justVerify(event)) return false
|
||||||
return consumeAssumingVerified(event, relay)
|
return route(event, relay)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun route(
|
||||||
* 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,
|
event: Event,
|
||||||
relay: NormalizedRelayUrl?,
|
relay: NormalizedRelayUrl?,
|
||||||
): Boolean =
|
): Boolean =
|
||||||
@@ -493,8 +479,9 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
zappedNote: Note?,
|
zappedNote: Note?,
|
||||||
relay: NormalizedRelayUrl?,
|
relay: NormalizedRelayUrl?,
|
||||||
onResponse: suspend (LnZapPaymentResponseEvent) -> Unit,
|
onResponse: suspend (LnZapPaymentResponseEvent) -> Unit,
|
||||||
|
wasVerified: Boolean = false,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (!justVerify(event)) return false
|
if (!wasVerified && !justVerify(event)) return false
|
||||||
val note = getOrCreateNote(event.id)
|
val note = getOrCreateNote(event.id)
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
@@ -521,8 +508,9 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
fun consume(
|
fun consume(
|
||||||
event: LnZapPaymentResponseEvent,
|
event: LnZapPaymentResponseEvent,
|
||||||
relay: NormalizedRelayUrl?,
|
relay: NormalizedRelayUrl?,
|
||||||
|
wasVerified: Boolean = false,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (!justVerify(event)) return false
|
if (!wasVerified && !justVerify(event)) return false
|
||||||
val requestId = event.requestId()
|
val requestId = event.requestId()
|
||||||
val pending = paymentTracker.onResponseReceived(requestId) ?: return false
|
val pending = paymentTracker.onResponseReceived(requestId) ?: return false
|
||||||
|
|
||||||
@@ -589,7 +577,7 @@ class DesktopLocalCache : ICacheProvider {
|
|||||||
if (!justVerify(event)) return false
|
if (!justVerify(event)) return false
|
||||||
// For addressable/replaceable events, store in the addressable note cache
|
// For addressable/replaceable events, store in the addressable note cache
|
||||||
// so state holders (Nip65RelayListState, etc.) pick it up via their flows
|
// so state holders (Nip65RelayListState, etc.) pick it up via their flows
|
||||||
if (event is com.vitorpamplona.quartz.nip01Core.core.AddressableEvent) {
|
if (event is AddressableEvent) {
|
||||||
val address = event.address()
|
val address = event.address()
|
||||||
val note = getOrCreateAddressableNote(address)
|
val note = getOrCreateAddressableNote(address)
|
||||||
val author = getOrCreateUser(event.pubKey) ?: return false
|
val author = getOrCreateUser(event.pubKey) ?: return false
|
||||||
|
|||||||
+19
-14
@@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentResponseEv
|
|||||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceErrorResponse
|
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceErrorResponse
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceSuccessResponse
|
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceSuccessResponse
|
||||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.Response
|
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.Response
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@@ -134,27 +135,31 @@ class NwcPaymentHandler(
|
|||||||
filters = listOf(filter),
|
filters = listOf(filter),
|
||||||
onEvent = { event, relay ->
|
onEvent = { event, relay ->
|
||||||
if (event is LnZapPaymentResponseEvent && event.requestId() == requestId) {
|
if (event is LnZapPaymentResponseEvent && event.requestId() == requestId) {
|
||||||
// Unsubscribe
|
// Move verify + cache mutation + decrypt off the relay's
|
||||||
relayManager.closeSubscription(nwcConnection.relayUri, subId)
|
// WebSocket reader thread; Schnorr verify is non-trivial
|
||||||
|
// CPU work and shouldn't block frame parsing.
|
||||||
|
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
||||||
|
kotlinx.coroutines.GlobalScope.launch(kotlinx.coroutines.Dispatchers.IO) {
|
||||||
|
if (!localCache.justVerify(event)) return@launch
|
||||||
|
|
||||||
// Store response note and link to zapped note
|
relayManager.closeSubscription(nwcConnection.relayUri, subId)
|
||||||
val responseNote = localCache.getOrCreateNote(event.id)
|
|
||||||
responseNote.loadEvent(event, localCache.getOrCreateUser(event.pubKey), emptyList())
|
|
||||||
responseNote.addRelay(relay)
|
|
||||||
zappedNote?.addZapPayment(requestNote, responseNote)
|
|
||||||
|
|
||||||
// Decrypt and process response
|
val responseNote = localCache.getOrCreateNote(event.id)
|
||||||
try {
|
responseNote.loadEvent(event, localCache.getOrCreateUser(event.pubKey), emptyList())
|
||||||
kotlinx.coroutines.runBlocking {
|
responseNote.addRelay(relay)
|
||||||
|
zappedNote?.addZapPayment(requestNote, responseNote)
|
||||||
|
|
||||||
|
try {
|
||||||
val response = event.decrypt(nwcSigner)
|
val response = event.decrypt(nwcSigner)
|
||||||
val result = processResponse(response)
|
val result = processResponse(response)
|
||||||
if (continuation.isActive) {
|
if (continuation.isActive) {
|
||||||
continuation.resume(result)
|
continuation.resume(result)
|
||||||
}
|
}
|
||||||
}
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
if (e is kotlinx.coroutines.CancellationException) throw e
|
||||||
if (continuation.isActive) {
|
if (continuation.isActive) {
|
||||||
continuation.resume(PaymentResult.Error("Failed to decrypt response: ${e.message}"))
|
continuation.resume(PaymentResult.Error("Failed to decrypt response: ${e.message}"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-32
@@ -33,6 +33,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
|
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -120,46 +122,25 @@ class DesktopRelaySubscriptionsCoordinator(
|
|||||||
fun consumeEvent(
|
fun consumeEvent(
|
||||||
event: Event,
|
event: Event,
|
||||||
relay: NormalizedRelayUrl?,
|
relay: NormalizedRelayUrl?,
|
||||||
|
wasVerified: Boolean = false,
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
runConsume(localCache.consume(event, relay), event)
|
if (!localCache.consume(event, relay, wasVerified)) return@launch
|
||||||
|
_lastEventAt.value = System.currentTimeMillis()
|
||||||
|
val note = localCache.getNoteIfExists(event.id) ?: return@launch
|
||||||
|
eventBundler.invalidateList(note) { batch ->
|
||||||
|
localCache.eventStream.emitNewNotes(batch)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Coordinator: failed to consume kind=${event.kind} id=${event.id} relay=$relay: ${e.message}")
|
if (e is CancellationException) throw e
|
||||||
|
Log.w("DesktopRelaySubscriptionsCoordinator") {
|
||||||
|
"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.
|
* Request a consolidated interaction subscription for the given note IDs.
|
||||||
* Subscribes to kinds 7 (reactions), 9735 (zaps), 6 (reposts), and 1 (replies)
|
* Subscribes to kinds 7 (reactions), 9735 (zaps), 6 (reposts), and 1 (replies)
|
||||||
|
|||||||
Vendored
+10
-10
@@ -53,7 +53,7 @@ import kotlin.test.assertTrue
|
|||||||
* These tests use a stub INostrClient (no real relay connections) and exercise
|
* These tests use a stub INostrClient (no real relay connections) and exercise
|
||||||
* the full event consumption path through DesktopRelaySubscriptionsCoordinator.
|
* the full event consumption path through DesktopRelaySubscriptionsCoordinator.
|
||||||
*
|
*
|
||||||
* Key invariant being tested: when coordinator.consumeEventAssumingVerified() is called,
|
* Key invariant being tested: when coordinator.consumeEvent() is called,
|
||||||
* the event should flow through cache → eventStream → ViewModel.feedState.
|
* the event should flow through cache → eventStream → ViewModel.feedState.
|
||||||
*/
|
*/
|
||||||
class CoordinatorPipelineTest {
|
class CoordinatorPipelineTest {
|
||||||
@@ -167,14 +167,14 @@ class CoordinatorPipelineTest {
|
|||||||
content = "Hello from relay",
|
content = "Hello from relay",
|
||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
coordinator.consumeEventAssumingVerified(event, relayUrl)
|
coordinator.consumeEvent(event, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
val state = vm.feedState.feedContent.value
|
val state = vm.feedState.feedContent.value
|
||||||
assertIs<FeedState.Loaded>(
|
assertIs<FeedState.Loaded>(
|
||||||
state,
|
state,
|
||||||
"ViewModel should be Loaded after coordinator.consumeEventAssumingVerified()",
|
"ViewModel should be Loaded after coordinator.consumeEvent()",
|
||||||
)
|
)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
vm.feedState.visibleNotes().any { it.idHex == event.id },
|
vm.feedState.visibleNotes().any { it.idHex == event.id },
|
||||||
@@ -203,7 +203,7 @@ class CoordinatorPipelineTest {
|
|||||||
content = "test",
|
content = "test",
|
||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
coordinator.consumeEventAssumingVerified(event, relayUrl)
|
coordinator.consumeEvent(event, relayUrl, wasVerified = true)
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
assertTrue(coordinator.lastEventAt.value != null, "lastEventAt should be set after consumeEvent")
|
assertTrue(coordinator.lastEventAt.value != null, "lastEventAt should be set after consumeEvent")
|
||||||
@@ -227,7 +227,7 @@ class CoordinatorPipelineTest {
|
|||||||
content = "",
|
content = "",
|
||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
coordinator.consumeEventAssumingVerified(contactEvent, relayUrl)
|
coordinator.consumeEvent(contactEvent, relayUrl, wasVerified = true)
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
@@ -255,7 +255,7 @@ class CoordinatorPipelineTest {
|
|||||||
content = "",
|
content = "",
|
||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
coordinator.consumeEventAssumingVerified(contactEvent, relayUrl)
|
coordinator.consumeEvent(contactEvent, relayUrl, wasVerified = true)
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
// Step 2: Create following feed ViewModel
|
// Step 2: Create following feed ViewModel
|
||||||
@@ -274,7 +274,7 @@ class CoordinatorPipelineTest {
|
|||||||
content = "Note from followed user",
|
content = "Note from followed user",
|
||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
coordinator.consumeEventAssumingVerified(textEvent, relayUrl)
|
coordinator.consumeEvent(textEvent, relayUrl, wasVerified = true)
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
val state = vm.feedState.feedContent.value
|
val state = vm.feedState.feedContent.value
|
||||||
@@ -311,7 +311,7 @@ class CoordinatorPipelineTest {
|
|||||||
content = "Note that won't show",
|
content = "Note that won't show",
|
||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
coordinator.consumeEventAssumingVerified(textEvent, relayUrl)
|
coordinator.consumeEvent(textEvent, relayUrl, wasVerified = true)
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
assertIs<FeedState.Empty>(
|
assertIs<FeedState.Empty>(
|
||||||
@@ -349,8 +349,8 @@ class CoordinatorPipelineTest {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Consume same event twice (can happen with multiple relays)
|
// Consume same event twice (can happen with multiple relays)
|
||||||
coordinator.consumeEventAssumingVerified(event, relayUrl)
|
coordinator.consumeEvent(event, relayUrl, wasVerified = true)
|
||||||
coordinator.consumeEventAssumingVerified(event, NormalizedRelayUrl("wss://relay2.test/"))
|
coordinator.consumeEvent(event, NormalizedRelayUrl("wss://relay2.test/"), wasVerified = true)
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
|
|||||||
Vendored
+42
-42
@@ -127,7 +127,7 @@ class DesktopCachePipelineTest {
|
|||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
val event = textNote("note1".padEnd(64, '0'), userPubKey)
|
val event = textNote("note1".padEnd(64, '0'), userPubKey)
|
||||||
|
|
||||||
val consumed = cache.consumeAssumingVerified(event, relayUrl)
|
val consumed = cache.consume(event, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
assertTrue(consumed, "First consume should return true")
|
assertTrue(consumed, "First consume should return true")
|
||||||
val note = cache.getNoteIfExists("note1".padEnd(64, '0'))
|
val note = cache.getNoteIfExists("note1".padEnd(64, '0'))
|
||||||
@@ -140,8 +140,8 @@ class DesktopCachePipelineTest {
|
|||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
val event = textNote("note1".padEnd(64, '0'), userPubKey)
|
val event = textNote("note1".padEnd(64, '0'), userPubKey)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(event, relayUrl)
|
cache.consume(event, relayUrl, wasVerified = true)
|
||||||
val secondConsume = cache.consumeAssumingVerified(event, relayUrl)
|
val secondConsume = cache.consume(event, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
assertTrue(!secondConsume, "Second consume of same event should return false")
|
assertTrue(!secondConsume, "Second consume of same event should return false")
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ class DesktopCachePipelineTest {
|
|||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
val event = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey))
|
val event = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey))
|
||||||
|
|
||||||
cache.consumeAssumingVerified(event, relayUrl)
|
cache.consume(event, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
assertEquals(setOf(followedPubKey), cache.followedUsers.value)
|
assertEquals(setOf(followedPubKey), cache.followedUsers.value)
|
||||||
}
|
}
|
||||||
@@ -168,8 +168,8 @@ class DesktopCachePipelineTest {
|
|||||||
createdAt = 200,
|
createdAt = 200,
|
||||||
)
|
)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(old, relayUrl)
|
cache.consume(old, relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(newer, relayUrl)
|
cache.consume(newer, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
assertEquals(setOf(followedPubKey, unfollowedPubKey), cache.followedUsers.value)
|
assertEquals(setOf(followedPubKey, unfollowedPubKey), cache.followedUsers.value)
|
||||||
}
|
}
|
||||||
@@ -180,8 +180,8 @@ class DesktopCachePipelineTest {
|
|||||||
val newer = contactList("cl2".padEnd(64, '0'), userPubKey, listOf(followedPubKey, unfollowedPubKey), createdAt = 200)
|
val newer = contactList("cl2".padEnd(64, '0'), userPubKey, listOf(followedPubKey, unfollowedPubKey), createdAt = 200)
|
||||||
val old = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey), createdAt = 100)
|
val old = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey), createdAt = 100)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(newer, relayUrl)
|
cache.consume(newer, relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(old, relayUrl)
|
cache.consume(old, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
setOf(followedPubKey, unfollowedPubKey),
|
setOf(followedPubKey, unfollowedPubKey),
|
||||||
@@ -197,8 +197,8 @@ class DesktopCachePipelineTest {
|
|||||||
val note = textNote(noteId, userPubKey)
|
val note = textNote(noteId, userPubKey)
|
||||||
val react = reaction("react1".padEnd(64, '0'), followedPubKey, noteId)
|
val react = reaction("react1".padEnd(64, '0'), followedPubKey, noteId)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(note, relayUrl)
|
cache.consume(note, relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(react, relayUrl)
|
cache.consume(react, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val cachedNote = cache.getNoteIfExists(noteId)!!
|
val cachedNote = cache.getNoteIfExists(noteId)!!
|
||||||
assertTrue(cachedNote.countReactions() > 0, "Note should have reactions after consuming reaction event")
|
assertTrue(cachedNote.countReactions() > 0, "Note should have reactions after consuming reaction event")
|
||||||
@@ -223,7 +223,7 @@ class DesktopCachePipelineTest {
|
|||||||
delay(50)
|
delay(50)
|
||||||
|
|
||||||
val event = textNote("note1".padEnd(64, '0'), userPubKey)
|
val event = textNote("note1".padEnd(64, '0'), userPubKey)
|
||||||
cache.consumeAssumingVerified(event, relayUrl)
|
cache.consume(event, relayUrl, wasVerified = true)
|
||||||
val note = cache.getNoteIfExists(event.id)!!
|
val note = cache.getNoteIfExists(event.id)!!
|
||||||
cache.emitNewNotes(setOf(note))
|
cache.emitNewNotes(setOf(note))
|
||||||
|
|
||||||
@@ -244,9 +244,9 @@ class DesktopCachePipelineTest {
|
|||||||
val filter = DesktopGlobalFeedFilter(cache)
|
val filter = DesktopGlobalFeedFilter(cache)
|
||||||
|
|
||||||
// Add notes from different authors
|
// Add notes from different authors
|
||||||
cache.consumeAssumingVerified(textNote("n1".padEnd(64, '0'), userPubKey, createdAt = 100), relayUrl)
|
cache.consume(textNote("n1".padEnd(64, '0'), userPubKey, createdAt = 100), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote("n2".padEnd(64, '0'), followedPubKey, createdAt = 200), relayUrl)
|
cache.consume(textNote("n2".padEnd(64, '0'), followedPubKey, createdAt = 200), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote("n3".padEnd(64, '0'), unfollowedPubKey, createdAt = 300), relayUrl)
|
cache.consume(textNote("n3".padEnd(64, '0'), unfollowedPubKey, createdAt = 300), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
assertEquals(3, feed.size, "Global feed should contain all text notes")
|
assertEquals(3, feed.size, "Global feed should contain all text notes")
|
||||||
@@ -255,10 +255,10 @@ class DesktopCachePipelineTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `FollowingFeedFilter only includes notes from followed users`() {
|
fun `FollowingFeedFilter only includes notes from followed users`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl)
|
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100), relayUrl)
|
cache.consume(textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200), relayUrl)
|
cache.consume(textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
@@ -270,7 +270,7 @@ class DesktopCachePipelineTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `FollowingFeedFilter returns empty when no follows`() {
|
fun `FollowingFeedFilter returns empty when no follows`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(textNote("n1".padEnd(64, '0'), followedPubKey), relayUrl)
|
cache.consume(textNote("n1".padEnd(64, '0'), followedPubKey), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopFollowingFeedFilter(cache) { emptySet() }
|
val filter = DesktopFollowingFeedFilter(cache) { emptySet() }
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
@@ -281,8 +281,8 @@ class DesktopCachePipelineTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `ProfileFeedFilter only shows notes from target pubkey`() {
|
fun `ProfileFeedFilter only shows notes from target pubkey`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100), relayUrl)
|
cache.consume(textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200), relayUrl)
|
cache.consume(textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopProfileFeedFilter(followedPubKey, cache)
|
val filter = DesktopProfileFeedFilter(followedPubKey, cache)
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
@@ -297,8 +297,8 @@ class DesktopCachePipelineTest {
|
|||||||
val rootId = "root".padEnd(64, '0')
|
val rootId = "root".padEnd(64, '0')
|
||||||
val replyId = "reply".padEnd(64, '0')
|
val replyId = "reply".padEnd(64, '0')
|
||||||
|
|
||||||
cache.consumeAssumingVerified(textNote(rootId, userPubKey, createdAt = 100), relayUrl)
|
cache.consume(textNote(rootId, userPubKey, createdAt = 100), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote(replyId, followedPubKey, createdAt = 200, replyToId = rootId), relayUrl)
|
cache.consume(textNote(replyId, followedPubKey, createdAt = 200, replyToId = rootId), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopThreadFilter(rootId, cache)
|
val filter = DesktopThreadFilter(rootId, cache)
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
@@ -310,11 +310,11 @@ class DesktopCachePipelineTest {
|
|||||||
fun `NotificationFeedFilter shows events tagging user`() {
|
fun `NotificationFeedFilter shows events tagging user`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
val noteId = "note1".padEnd(64, '0')
|
val noteId = "note1".padEnd(64, '0')
|
||||||
cache.consumeAssumingVerified(textNote(noteId, userPubKey, createdAt = 100), relayUrl)
|
cache.consume(textNote(noteId, userPubKey, createdAt = 100), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
// Reaction from someone else targeting user's note
|
// Reaction from someone else targeting user's note
|
||||||
val react = reaction("react1".padEnd(64, '0'), followedPubKey, noteId, createdAt = 200)
|
val react = reaction("react1".padEnd(64, '0'), followedPubKey, noteId, createdAt = 200)
|
||||||
cache.consumeAssumingVerified(react, relayUrl)
|
cache.consume(react, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopNotificationFeedFilter(userPubKey, cache)
|
val filter = DesktopNotificationFeedFilter(userPubKey, cache)
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
@@ -334,7 +334,7 @@ class DesktopCachePipelineTest {
|
|||||||
fun `ViewModel starts in Loading then transitions to Loaded after refresh`() =
|
fun `ViewModel starts in Loading then transitions to Loaded after refresh`() =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(textNote("n1".padEnd(64, '0'), userPubKey), relayUrl)
|
cache.consume(textNote("n1".padEnd(64, '0'), userPubKey), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
|
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
|
||||||
|
|
||||||
@@ -373,7 +373,7 @@ class DesktopCachePipelineTest {
|
|||||||
|
|
||||||
// Simulate relay event arriving
|
// Simulate relay event arriving
|
||||||
val event = textNote("n1".padEnd(64, '0'), userPubKey)
|
val event = textNote("n1".padEnd(64, '0'), userPubKey)
|
||||||
cache.consumeAssumingVerified(event, relayUrl)
|
cache.consume(event, relayUrl, wasVerified = true)
|
||||||
val note = cache.getNoteIfExists(event.id)!!
|
val note = cache.getNoteIfExists(event.id)!!
|
||||||
cache.emitNewNotes(setOf(note))
|
cache.emitNewNotes(setOf(note))
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ class DesktopCachePipelineTest {
|
|||||||
fun `Following ViewModel only shows followed users notes via eventStream`() =
|
fun `Following ViewModel only shows followed users notes via eventStream`() =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl)
|
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
||||||
val vm = DesktopFeedViewModel(filter, cache)
|
val vm = DesktopFeedViewModel(filter, cache)
|
||||||
@@ -397,7 +397,7 @@ class DesktopCachePipelineTest {
|
|||||||
|
|
||||||
// Add followed user's note
|
// Add followed user's note
|
||||||
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100)
|
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100)
|
||||||
cache.consumeAssumingVerified(e1, relayUrl)
|
cache.consume(e1, relayUrl, wasVerified = true)
|
||||||
val note1 = cache.getNoteIfExists(e1.id)!!
|
val note1 = cache.getNoteIfExists(e1.id)!!
|
||||||
cache.emitNewNotes(setOf(note1))
|
cache.emitNewNotes(setOf(note1))
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
@@ -406,7 +406,7 @@ class DesktopCachePipelineTest {
|
|||||||
|
|
||||||
// Add unfollowed user's note
|
// Add unfollowed user's note
|
||||||
val e2 = textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200)
|
val e2 = textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200)
|
||||||
cache.consumeAssumingVerified(e2, relayUrl)
|
cache.consume(e2, relayUrl, wasVerified = true)
|
||||||
val note2 = cache.getNoteIfExists(e2.id)!!
|
val note2 = cache.getNoteIfExists(e2.id)!!
|
||||||
cache.emitNewNotes(setOf(note2))
|
cache.emitNewNotes(setOf(note2))
|
||||||
waitForBundler()
|
waitForBundler()
|
||||||
@@ -422,7 +422,7 @@ class DesktopCachePipelineTest {
|
|||||||
// No contact list consumed — followedUsers remains empty
|
// No contact list consumed — followedUsers remains empty
|
||||||
|
|
||||||
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey)
|
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey)
|
||||||
cache.consumeAssumingVerified(e1, relayUrl)
|
cache.consume(e1, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
||||||
val vm = DesktopFeedViewModel(filter, cache)
|
val vm = DesktopFeedViewModel(filter, cache)
|
||||||
@@ -442,8 +442,8 @@ class DesktopCachePipelineTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `clear resets all cache state`() {
|
fun `clear resets all cache state`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(textNote("n1".padEnd(64, '0'), userPubKey), relayUrl)
|
cache.consume(textNote("n1".padEnd(64, '0'), userPubKey), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl)
|
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
@@ -459,9 +459,9 @@ class DesktopCachePipelineTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `global feed is sorted newest first`() {
|
fun `global feed is sorted newest first`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(textNote("old".padEnd(64, '0'), userPubKey, createdAt = 100), relayUrl)
|
cache.consume(textNote("old".padEnd(64, '0'), userPubKey, createdAt = 100), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote("mid".padEnd(64, '0'), userPubKey, createdAt = 200), relayUrl)
|
cache.consume(textNote("mid".padEnd(64, '0'), userPubKey, createdAt = 200), relayUrl, wasVerified = true)
|
||||||
cache.consumeAssumingVerified(textNote("new".padEnd(64, '0'), userPubKey, createdAt = 300), relayUrl)
|
cache.consume(textNote("new".padEnd(64, '0'), userPubKey, createdAt = 300), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopGlobalFeedFilter(cache)
|
val filter = DesktopGlobalFeedFilter(cache)
|
||||||
val feed = filter.feed()
|
val feed = filter.feed()
|
||||||
@@ -487,7 +487,7 @@ class DesktopCachePipelineTest {
|
|||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(metadata, relayUrl)
|
cache.consume(metadata, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val user = cache.getUserIfExists(userPubKey)
|
val user = cache.getUserIfExists(userPubKey)
|
||||||
assertTrue(user != null, "User should exist after metadata consumption")
|
assertTrue(user != null, "User should exist after metadata consumption")
|
||||||
@@ -506,12 +506,12 @@ class DesktopCachePipelineTest {
|
|||||||
|
|
||||||
// Create a text note
|
// Create a text note
|
||||||
val textEvent = textNote("t1".padEnd(64, '0'), userPubKey)
|
val textEvent = textNote("t1".padEnd(64, '0'), userPubKey)
|
||||||
cache.consumeAssumingVerified(textEvent, relayUrl)
|
cache.consume(textEvent, relayUrl, wasVerified = true)
|
||||||
val textNote = cache.getNoteIfExists(textEvent.id)!!
|
val textNote = cache.getNoteIfExists(textEvent.id)!!
|
||||||
|
|
||||||
// Create a reaction (not a text note)
|
// Create a reaction (not a text note)
|
||||||
val reactEvent = reaction("r1".padEnd(64, '0'), userPubKey, "t1".padEnd(64, '0'))
|
val reactEvent = reaction("r1".padEnd(64, '0'), userPubKey, "t1".padEnd(64, '0'))
|
||||||
cache.consumeAssumingVerified(reactEvent, relayUrl)
|
cache.consume(reactEvent, relayUrl, wasVerified = true)
|
||||||
val reactNote = cache.getNoteIfExists(reactEvent.id)!!
|
val reactNote = cache.getNoteIfExists(reactEvent.id)!!
|
||||||
|
|
||||||
val filtered = filter.applyFilter(setOf(textNote, reactNote))
|
val filtered = filter.applyFilter(setOf(textNote, reactNote))
|
||||||
@@ -523,16 +523,16 @@ class DesktopCachePipelineTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `FollowingFeedFilter applyFilter respects follow set`() {
|
fun `FollowingFeedFilter applyFilter respects follow set`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
cache.consumeAssumingVerified(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl)
|
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
|
||||||
|
|
||||||
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey)
|
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey)
|
||||||
cache.consumeAssumingVerified(e1, relayUrl)
|
cache.consume(e1, relayUrl, wasVerified = true)
|
||||||
val note1 = cache.getNoteIfExists(e1.id)!!
|
val note1 = cache.getNoteIfExists(e1.id)!!
|
||||||
|
|
||||||
val e2 = textNote("n2".padEnd(64, '0'), unfollowedPubKey)
|
val e2 = textNote("n2".padEnd(64, '0'), unfollowedPubKey)
|
||||||
cache.consumeAssumingVerified(e2, relayUrl)
|
cache.consume(e2, relayUrl, wasVerified = true)
|
||||||
val note2 = cache.getNoteIfExists(e2.id)!!
|
val note2 = cache.getNoteIfExists(e2.id)!!
|
||||||
|
|
||||||
val filtered = filter.applyFilter(setOf(note1, note2))
|
val filtered = filter.applyFilter(setOf(note1, note2))
|
||||||
@@ -592,7 +592,7 @@ class DesktopCachePipelineTest {
|
|||||||
sig = dummySig,
|
sig = dummySig,
|
||||||
)
|
)
|
||||||
|
|
||||||
cache.consumeAssumingVerified(metadata, relayUrl)
|
cache.consume(metadata, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
val user = cache.getUserIfExists(userPubKey)!!
|
val user = cache.getUserIfExists(userPubKey)!!
|
||||||
val cached = user.metadataOrNull()
|
val cached = user.metadataOrNull()
|
||||||
|
|||||||
+6
-4
@@ -78,7 +78,9 @@ class DesktopLocalCacheVerifyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `consume rejects an event with an all-zero signature`() {
|
fun `consume rejects an event whose id-hash does not match its content`() {
|
||||||
|
// Synthetic event with arbitrary id and signature — fails the id check
|
||||||
|
// before the Schnorr verify is even attempted.
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
val event =
|
val event =
|
||||||
TextNoteEvent(
|
TextNoteEvent(
|
||||||
@@ -128,7 +130,7 @@ class DesktopLocalCacheVerifyTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `consumeAssumingVerified bypass still routes test events`() {
|
fun `wasVerified bypass routes synthetic test events`() {
|
||||||
val cache = DesktopLocalCache()
|
val cache = DesktopLocalCache()
|
||||||
val event =
|
val event =
|
||||||
TextNoteEvent(
|
TextNoteEvent(
|
||||||
@@ -140,9 +142,9 @@ class DesktopLocalCacheVerifyTest {
|
|||||||
sig = "0".repeat(128),
|
sig = "0".repeat(128),
|
||||||
)
|
)
|
||||||
|
|
||||||
val consumed = cache.consumeAssumingVerified(event, relayUrl)
|
val consumed = cache.consume(event, relayUrl, wasVerified = true)
|
||||||
|
|
||||||
assertTrue(consumed, "consumeAssumingVerified must skip the signature check")
|
assertTrue(consumed, "wasVerified=true must skip the signature check")
|
||||||
assertNotNull(cache.getNoteIfExists(event.id))
|
assertNotNull(cache.getNoteIfExists(event.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user