Move to a cache model where we defer the event's verification until the last moment before adding to cache

This commit is contained in:
Vitor Pamplona
2025-05-06 09:45:01 -04:00
parent a05a465f69
commit fbe20029f7
12 changed files with 565 additions and 490 deletions
@@ -126,7 +126,7 @@ class ThreadDualAxisChartAssemblerTest {
var counter = 0
eventArray.forEach {
TestCase.assertTrue("${it.id} failed signature check", it.verify())
LocalCache.verifyAndConsume(it, null)
LocalCache.justConsume(it, null)
counter++
}
@@ -2000,10 +2000,10 @@ class Account(
if (!isWriteable()) return null
Amethyst.instance.client.send(data, relayList = relayList)
LocalCache.consume(data, null)
LocalCache.justConsume(data, null)
Amethyst.instance.client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent, null)
LocalCache.justConsume(signedEvent, null)
return LocalCache.getNoteIfExists(signedEvent.id)
}
@@ -2012,8 +2012,8 @@ class Account(
data: FileStorageEvent,
signedEvent: FileStorageHeaderEvent,
): Note? {
LocalCache.consume(data, null)
LocalCache.consume(signedEvent, null)
LocalCache.justConsume(data, null)
LocalCache.justConsume(signedEvent, null)
return LocalCache.getNoteIfExists(signedEvent.id)
}
@@ -2695,7 +2695,7 @@ class Account(
LocalCache.justConsume(gift, null)
}
LocalCache.consume(giftWrap, null)
LocalCache.justConsume(giftWrap, null)
}
val id = mine.firstOrNull()?.id
@@ -2839,7 +2839,7 @@ class Account(
},
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
@@ -2996,7 +2996,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
} else {
MuteListEvent.createListWithWord(
@@ -3005,7 +3005,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
}
@@ -3020,7 +3020,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
@@ -3033,7 +3033,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
}
@@ -3049,7 +3049,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
} else {
MuteListEvent.createListWithUser(
@@ -3058,7 +3058,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
}
@@ -3073,7 +3073,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
@@ -3086,7 +3086,7 @@ class Account(
signer = signer,
) {
Amethyst.instance.client.send(it)
LocalCache.consume(it, null)
LocalCache.justConsume(it, null)
}
}
@@ -3709,25 +3709,25 @@ class Account(
Log.d("AccountRegisterObservers", "Loading saved user metadata ${it.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.consume(it, null) }
GlobalScope.launch(Dispatchers.IO) { LocalCache.justConsume(it, null) }
}
settings.backupDMRelayList?.let {
Log.d("AccountRegisterObservers", "Loading saved DM Relay List ${it.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
GlobalScope.launch(Dispatchers.IO) { LocalCache.justConsume(it, null) }
}
settings.backupNIP65RelayList?.let {
Log.d("AccountRegisterObservers", "Loading saved nip65 relay list ${it.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
GlobalScope.launch(Dispatchers.IO) { LocalCache.justConsume(it, null) }
}
settings.backupSearchRelayList?.let {
Log.d("AccountRegisterObservers", "Loading saved search relay list ${it.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
GlobalScope.launch(Dispatchers.IO) { LocalCache.justConsume(it, null) }
}
settings.backupPrivateHomeRelayList?.let { event ->
@@ -3735,7 +3735,7 @@ class Account(
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) {
event.privateTags(signer) {
LocalCache.verifyAndConsume(event, null)
LocalCache.justConsume(event, null)
}
}
}
@@ -3744,7 +3744,7 @@ class Account(
Log.d("AccountRegisterObservers", "Loading saved app specific data ${event.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) {
LocalCache.verifyAndConsume(event, null)
LocalCache.justConsume(event, null)
signer.decrypt(event.content, event.pubKey) { decrypted ->
try {
val syncedSettings = EventMapper.mapper.readValue<AccountSyncedSettingsInternal>(decrypted)
@@ -3759,22 +3759,34 @@ class Account(
}
}
settings.backupMuteList?.let {
Log.d("AccountRegisterObservers", "Loading saved mute list ${it.toJson()}")
settings.backupMuteList?.let { event ->
Log.d("AccountRegisterObservers", "Loading saved mute list ${event.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
GlobalScope.launch(Dispatchers.IO) {
event.privateTags(signer) {
LocalCache.justConsume(event, null)
}
}
}
settings.backupEphemeralChatList?.let {
Log.d("AccountRegisterObservers", "Loading saved ephemeral chat list ${it.toJson()}")
settings.backupEphemeralChatList?.let { event ->
Log.d("AccountRegisterObservers", "Loading saved ephemeral chat list ${event.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
GlobalScope.launch(Dispatchers.IO) {
event.privateTags(signer) {
LocalCache.justConsume(event, null)
}
}
}
settings.backupChannelList?.let {
Log.d("AccountRegisterObservers", "Loading saved channel list ${it.toJson()}")
settings.backupChannelList?.let { event ->
Log.d("AccountRegisterObservers", "Loading saved channel list ${event.toJson()}")
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launch(Dispatchers.IO) { LocalCache.verifyAndConsume(it, null) }
GlobalScope.launch(Dispatchers.IO) {
event.privateTags(signer) {
LocalCache.justConsume(event, null)
}
}
}
// saves contact list for the next time.
File diff suppressed because it is too large Load Diff
@@ -29,7 +29,6 @@ import com.vitorpamplona.amethyst.service.replace
import com.vitorpamplona.amethyst.tryAndWait
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.ammolite.relays.BundledUpdate
import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
import com.vitorpamplona.ammolite.relays.filters.EOSETime
import com.vitorpamplona.quartz.experimental.bounties.addedRewardValue
@@ -448,7 +447,7 @@ open class Note(
}
}
fun hasRelay(relay: Relay) = relay.brief !in relays
fun hasRelay(relay: RelayBriefInfoCache.RelayBriefInfo) = relay !in relays
fun addRelay(brief: RelayBriefInfoCache.RelayBriefInfo) {
if (brief !in relays) {
@@ -62,7 +62,6 @@ class EventNotificationConsumer(
suspend fun consume(event: GiftWrapEvent) {
Log.d(TAG, "New Notification Arrived")
if (!LocalCache.justVerify(event)) return
// PushNotification Wraps don't include a receiver.
// Test with all logged in accounts
@@ -108,9 +107,8 @@ class EventNotificationConsumer(
account: AccountSettings,
) {
val consumed = LocalCache.hasConsumed(notificationEvent)
val verified = LocalCache.justVerify(notificationEvent)
Log.d(TAG, "New Notification ${notificationEvent.kind} ${notificationEvent.id} Arrived for ${signer.pubKey} consumed= $consumed && verified= $verified")
if (!consumed && verified) {
Log.d(TAG, "New Notification ${notificationEvent.kind} ${notificationEvent.id} Arrived for ${signer.pubKey} consumed= $consumed")
if (!consumed) {
Log.d(TAG, "New Notification was verified")
unwrapAndConsume(notificationEvent, signer) { innerEvent ->
if (!notificationManager().areNotificationsEnabled()) return@unwrapAndConsume
@@ -135,8 +133,6 @@ class EventNotificationConsumer(
suspend fun findAccountAndConsume(event: Event) {
Log.d(TAG, "New Notification Arrived")
if (!LocalCache.justVerify(event)) return
val users = event.taggedUserIds().map { LocalCache.getOrCreateUser(it) }
val npubs = users.map { it.pubkeyNpub() }.toSet()
@@ -174,24 +170,27 @@ class EventNotificationConsumer(
signer: NostrSigner,
onReady: (Event) -> Unit,
) {
if (!LocalCache.justVerify(event)) return
if (LocalCache.hasConsumed(event)) return
when (event) {
is GiftWrapEvent -> {
event.unwrap(signer) {
unwrapAndConsume(it, signer, onReady)
LocalCache.justConsume(event, null)
if (LocalCache.justConsume(event, null)) {
// new event
event.unwrap(signer) {
unwrapAndConsume(it, signer, onReady)
}
}
}
is SealedRumorEvent -> {
event.unseal(signer) {
if (!LocalCache.hasConsumed(it)) {
if (LocalCache.justConsume(event, null)) {
// new event
event.unseal(signer) {
// this is not verifiable
LocalCache.justConsume(it, null)
onReady(it)
if (!LocalCache.hasConsumed(it)) {
LocalCache.justConsume(it, null)
onReady(it)
}
}
LocalCache.justConsume(event, null)
}
}
else -> {
@@ -33,7 +33,7 @@ class CacheClientConnector(
) {
val receiver =
EventCollector(client) { event, relay ->
cache.verifyAndConsume(event, relay)
cache.justConsume(event, relay)
}
val confirmationWatcher =
@@ -155,7 +155,7 @@ class SearchFilterAssembler(
is Note -> filterByEvent(parsed.hex)
is NEvent -> filterByEvent(parsed.hex)
is NEmbed -> {
cache.verifyAndConsume(parsed.event, null)
cache.justConsume(parsed.event, null)
emptyList()
}
@@ -183,7 +183,7 @@ fun uriToRoute(uri: String?): Route? =
is NEmbed -> {
if (LocalCache.getNoteIfExists(nip19.event.id) == null) {
LocalCache.verifyAndConsume(nip19.event, null)
LocalCache.justConsume(nip19.event, null)
}
Route.EventRedirect(nip19.event.id)
}
@@ -155,7 +155,7 @@ fun PollNotePreview() {
withContext(Dispatchers.IO) {
LocalCache.justConsume(event, null)
LocalCache.consume(zapVote.zapRequest!!)
LocalCache.consume(zapVote, null)
LocalCache.justConsume(zapVote, null)
baseNote = LocalCache.getOrCreateNote("6ff9bc13d27490f6e3953325260bd996901a143de89886a0608c39e7d0160a72")
}
}
@@ -1091,7 +1091,7 @@ class AccountViewModel(
var note = checkGetOrCreateNote(event.id)
if (note == null) {
LocalCache.verifyAndConsume(event, null)
LocalCache.justConsume(event, null)
note = checkGetOrCreateNote(event.id)
}
@@ -1471,8 +1471,9 @@ class AccountViewModel(
unwrapIfNeeded(existingNote.event, onReady)
} else {
event.unwrap(account.signer) {
LocalCache.verifyAndConsume(it, null)
unwrapIfNeeded(it, onReady)
if (LocalCache.justConsume(it, null)) {
unwrapIfNeeded(it, onReady)
}
}
}
} ?: run {
@@ -1481,8 +1482,9 @@ class AccountViewModel(
if (existingNote != null) {
unwrapIfNeeded(existingNote.event, onReady)
} else {
LocalCache.verifyAndConsume(it, null)
unwrapIfNeeded(it, onReady)
if (LocalCache.justConsume(it, null)) {
unwrapIfNeeded(it, onReady)
}
}
}
}
@@ -1495,8 +1497,9 @@ class AccountViewModel(
} else {
event.unseal(account.signer) {
// this is not verifiable
LocalCache.justConsume(it, null)
unwrapIfNeeded(it, onReady)
if (LocalCache.justConsume(it, null)) {
unwrapIfNeeded(it, onReady)
}
}
}
} ?: run {
@@ -1506,8 +1509,9 @@ class AccountViewModel(
unwrapIfNeeded(existingNote.event, onReady)
} else {
// this is not verifiable
LocalCache.justConsume(it, null)
unwrapIfNeeded(it, onReady)
if (LocalCache.justConsume(it, null)) {
unwrapIfNeeded(it, onReady)
}
}
}
}
@@ -1755,7 +1759,7 @@ class AccountViewModel(
val baseNote = LocalCache.getOrCreateNote(parsed.event)
if (baseNote.event == null) {
launch(Dispatchers.Default) {
LocalCache.verifyAndConsume(parsed.event, null)
LocalCache.justConsume(parsed.event, null)
}
}
@@ -78,8 +78,7 @@ class PrecacheNewNotesProcessor(
val inner = event.innerEventId
if (inner == null) {
event.unwrap(account.signer) {
if (cache.justVerify(it)) {
cache.justConsume(it, null)
if (cache.justConsume(it, null)) {
cache.copyRelaysFromTo(note, it)
consumeAlreadyVerified(it, note)
}
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip09Deletions
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.verify
import com.vitorpamplona.quartz.utils.LargeCache
class DeletionIndex {
@@ -45,14 +46,24 @@ class DeletionIndex {
// Anything newer than the date should not be deleted.
private val deletedReferencesBefore = LargeCache<DeletionRequest, DeletionEvent>()
fun add(event: DeletionEvent): Boolean {
fun add(
event: DeletionEvent,
wasVerified: Boolean,
): Boolean {
var atLeastOne = false
var myWasVerified = wasVerified
event.tags.forEach {
if (it.size > 1 && (it[0] == "a" || it[0] == "e")) {
if (add(it[1], event.pubKey, event)) {
atLeastOne = true
}
event.deleteEventIds().forEach { toDelete ->
if (add(toDelete, event.pubKey, event, myWasVerified)) {
myWasVerified = true
atLeastOne = true
}
}
event.deleteAddressIds().forEach { toDelete ->
if (add(toDelete, event.pubKey, event, myWasVerified)) {
myWasVerified = true
atLeastOne = true
}
}
@@ -63,13 +74,16 @@ class DeletionIndex {
ref: String,
byPubKey: HexKey,
deletionEvent: DeletionEvent,
wasVerified: Boolean,
): Boolean {
val key = DeletionRequest(ref, byPubKey)
val previousDeletionEvent = deletedReferencesBefore.get(key)
if (previousDeletionEvent == null || deletionEvent.createdAt > previousDeletionEvent.createdAt) {
deletedReferencesBefore.put(key, deletionEvent)
return true
if (wasVerified || deletionEvent.verify()) {
deletedReferencesBefore.put(key, deletionEvent)
return true
}
}
return false
}