Skip to content

Commit

Permalink
Improves caching of encrypted DMs:
Browse files Browse the repository at this point in the history
- Migrates caching of decrypted value outside of the Event class
- Removes encrypted parts of NIP-17 from the cache
- Removes old NIP-04 messages from the cache
- Avoids deleting new NIP-17 plain text chats from memory
  • Loading branch information
vitorpamplona committed Aug 22, 2024
1 parent 71b111c commit 448ea79
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 169 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2290,12 +2290,14 @@ class Account(
val mine = signedEvents.wraps.filter { (it.recipientPubKey() == signer.pubKey) }

mine.forEach { giftWrap ->
giftWrap.cachedGift(signer) { gift ->
giftWrap.unwrap(signer) { gift ->
if (gift is SealedGossipEvent) {
gift.cachedGossip(signer) { gossip -> LocalCache.justConsume(gossip, null) }
} else {
LocalCache.justConsume(gift, null)
gift.unseal(signer) { gossip ->
LocalCache.justConsume(gossip, null)
}
}

LocalCache.justConsume(gift, null)
}

LocalCache.consume(giftWrap, null)
Expand Down Expand Up @@ -2842,7 +2844,7 @@ class Account(
) {
if (!isWriteable()) return

return event.cachedGift(signer, onReady)
return event.unwrap(signer, onReady)
}

fun unseal(
Expand All @@ -2851,7 +2853,7 @@ class Account(
) {
if (!isWriteable()) return

return event.cachedGossip(signer, onReady)
return event.unseal(signer, onReady)
}

fun cachedDecryptContent(note: Note): String? = cachedDecryptContent(note.event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ package com.vitorpamplona.amethyst.model

import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.utils.TimeUtils

@Stable
Expand Down Expand Up @@ -75,7 +77,7 @@ class Chatroom {
fun senderIntersects(keySet: Set<HexKey>): Boolean = authors.any { it.pubkeyHex in keySet }

fun pruneMessagesToTheLatestOnly(): Set<Note> {
val sorted = roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
val sorted = roomMessages.sortedWith(DefaultFeedOrder)

val toKeep =
if ((sorted.firstOrNull()?.createdAt() ?: 0) > TimeUtils.oneWeekAgo()) {
Expand All @@ -84,7 +86,7 @@ class Chatroom {
} else {
// Old messages, keep the last one.
sorted.take(1).toSet()
} + sorted.filter { it.liveSet?.isInUse() ?: false }
} + sorted.filter { it.liveSet?.isInUse() ?: false } + sorted.filter { it.event !is PrivateDmEvent }

val toRemove = roomMessages.minus(toKeep)
roomMessages = toKeep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,99 +299,121 @@ object NostrAccountDataSource : AmethystNostrDataSource("AccountData") {
override fun consume(
event: Event,
relay: Relay,
) {
if (LocalCache.justVerify(event)) {
consumeAlreadyVerified(event, relay)
}
}

fun consumeAlreadyVerified(
event: Event,
relay: Relay,
) {
checkNotInMainThread()

if (LocalCache.justVerify(event)) {
when (event) {
is PrivateOutboxRelayListEvent -> {
val note = LocalCache.getAddressableNoteIfExists(event.addressTag())
val noteEvent = note?.event
if (noteEvent == null || event.createdAt > noteEvent.createdAt()) {
event.privateTags(account.signer) {
LocalCache.justConsume(event, relay)
}
when (event) {
is PrivateOutboxRelayListEvent -> {
val note = LocalCache.getAddressableNoteIfExists(event.addressTag())
val noteEvent = note?.event
if (noteEvent == null || event.createdAt > noteEvent.createdAt()) {
event.privateTags(account.signer) {
LocalCache.justConsume(event, relay)
}
}
}

is DraftEvent -> {
// Avoid decrypting over and over again if the event already exist.

if (!event.isDeleted()) {
val note = LocalCache.getAddressableNoteIfExists(event.addressTag())
val noteEvent = note?.event
if (noteEvent != null) {
if (event.createdAt > noteEvent.createdAt() || relay.brief !in note.relays) {
LocalCache.consume(event, relay)
}
} else {
// decrypts
event.cachedDraft(account.signer) {}
is DraftEvent -> {
// Avoid decrypting over and over again if the event already exist.

LocalCache.justConsume(event, relay)
if (!event.isDeleted()) {
val note = LocalCache.getAddressableNoteIfExists(event.addressTag())
val noteEvent = note?.event
if (noteEvent != null) {
if (event.createdAt > noteEvent.createdAt() || relay.brief !in note.relays) {
LocalCache.consume(event, relay)
}
} else {
// decrypts
event.cachedDraft(account.signer) {}

LocalCache.justConsume(event, relay)
}
}
}

is GiftWrapEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
val noteEvent = note?.event as? GiftWrapEvent
if (noteEvent != null) {
if (relay.brief !in note.relays) {
noteEvent.cachedGift(account.signer) {
LocalCache.justConsume(noteEvent, relay)
is GiftWrapEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
val noteEvent = note?.event as? GiftWrapEvent
if (noteEvent != null) {
if (relay.brief !in note.relays) {
LocalCache.justConsume(noteEvent, relay)

noteEvent.innerEventId?.let {
(LocalCache.getNoteIfExists(it)?.event as? Event)?.let {
this.consumeAlreadyVerified(it, relay)
}
} ?: run {
event.unwrap(account.signer) {
this.consume(it, relay)
noteEvent.innerEventId = it.id
}
}
} else {
// new event
event.cachedGift(account.signer) {
LocalCache.justConsume(event, relay)
this.consume(it, relay)
}
}
} else {
// new event
event.unwrap(account.signer) {
LocalCache.justConsume(event, relay)
this.consume(it, relay)
}
}
}

is SealedGossipEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
val noteEvent = note?.event as? SealedGossipEvent
if (noteEvent != null) {
if (relay.brief !in note.relays) {
// adds the relay to seal and inner chat
noteEvent.cachedGossip(account.signer) {
LocalCache.consume(noteEvent, relay)
is SealedGossipEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
val noteEvent = note?.event as? SealedGossipEvent
if (noteEvent != null) {
if (relay.brief !in note.relays) {
LocalCache.justConsume(noteEvent, relay)

noteEvent.innerEventId?.let {
(LocalCache.getNoteIfExists(it)?.event as? Event)?.let {
LocalCache.justConsume(it, relay)
}
} ?: run {
event.unseal(account.signer) {
LocalCache.justConsume(it, relay)
noteEvent.innerEventId = it.id
}
}
} else {
// new event
event.cachedGossip(account.signer) {
LocalCache.justConsume(event, relay)
LocalCache.justConsume(it, relay)
}
}
} else {
// new event
event.unseal(account.signer) {
LocalCache.justConsume(event, relay)
LocalCache.justConsume(it, relay)
}
}
}

is LnZapEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note?.event == null) {
event.zapRequest?.let {
if (it.isPrivateZap()) {
it.decryptPrivateZap(account.signer) {}
}
is LnZapEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note?.event == null) {
event.zapRequest?.let {
if (it.isPrivateZap()) {
it.decryptPrivateZap(account.signer) {}
}

LocalCache.justConsume(event, relay)
}
}

else -> {
LocalCache.justConsume(event, relay)
}
}

else -> {
LocalCache.justConsume(event, relay)
}
}
}

Expand All @@ -409,16 +431,30 @@ object NostrAccountDataSource : AmethystNostrDataSource("AccountData") {
}

private fun markInnerAsSeenOnRelay(
noteEvent: EventInterface,
newNoteEvent: EventInterface,
relay: Relay,
) {
LocalCache.getNoteIfExists(noteEvent.id())?.addRelay(relay)
markInnerAsSeenOnRelay(newNoteEvent.id(), relay)
}

if (noteEvent is GiftWrapEvent) {
noteEvent.cachedGift(account.signer) { gift -> markInnerAsSeenOnRelay(gift, relay) }
} else if (noteEvent is SealedGossipEvent) {
noteEvent.cachedGossip(account.signer) { rumor ->
markInnerAsSeenOnRelay(rumor, relay)
private fun markInnerAsSeenOnRelay(
eventId: HexKey,
relay: Relay,
) {
val note = LocalCache.getNoteIfExists(eventId)

if (note != null) {
note.addRelay(relay)

val noteEvent = note.event
if (noteEvent is GiftWrapEvent) {
noteEvent.innerEventId?.let {
markInnerAsSeenOnRelay(it, relay)
}
} else if (noteEvent is SealedGossipEvent) {
noteEvent.innerEventId?.let {
markInnerAsSeenOnRelay(it, relay)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ class EventNotificationConsumer(
pushWrappedEvent: GiftWrapEvent,
account: Account,
) {
pushWrappedEvent.cachedGift(account.signer) { notificationEvent ->
// no need to cache
pushWrappedEvent.unwrap(account.signer) { notificationEvent ->
val consumed = LocalCache.hasConsumed(notificationEvent)
val verified = LocalCache.justVerify(notificationEvent)
Log.d("EventNotificationConsumer", "New Notification ${notificationEvent.kind} ${notificationEvent.id} Arrived for ${account.userProfile().toBestDisplayName()} consumed= $consumed && verified= $verified")
Expand Down Expand Up @@ -111,15 +112,19 @@ class EventNotificationConsumer(

when (event) {
is GiftWrapEvent -> {
event.cachedGift(account.signer) { unwrapAndConsume(it, account, onReady) }
event.unwrap(account.signer) {
unwrapAndConsume(it, account, onReady)
LocalCache.justConsume(event, null)
}
}
is SealedGossipEvent -> {
event.cachedGossip(account.signer) {
event.unseal(account.signer) {
if (!LocalCache.hasConsumed(it)) {
// this is not verifiable
LocalCache.justConsume(it, null)
onReady(it)
}
LocalCache.justConsume(event, null)
}
}
else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1364,25 +1364,50 @@ class AccountViewModel(
) {
when (event) {
is GiftWrapEvent -> {
event.cachedGift(account.signer) {
val existingNote = LocalCache.getNoteIfExists(it.id)
event.innerEventId?.let {
val existingNote = LocalCache.getNoteIfExists(it)
if (existingNote != null) {
unwrapIfNeeded(existingNote.event, onReady)
} else {
LocalCache.verifyAndConsume(it, null)
unwrapIfNeeded(it, onReady)
event.unwrap(account.signer) {
LocalCache.verifyAndConsume(it, null)
unwrapIfNeeded(it, onReady)
}
}
} ?: run {
event.unwrap(account.signer) {
val existingNote = LocalCache.getNoteIfExists(it.id)
if (existingNote != null) {
unwrapIfNeeded(existingNote.event, onReady)
} else {
LocalCache.verifyAndConsume(it, null)
unwrapIfNeeded(it, onReady)
}
}
}
}
is SealedGossipEvent -> {
event.cachedGossip(account.signer) {
val existingNote = LocalCache.getNoteIfExists(it.id)
event.innerEventId?.let {
val existingNote = LocalCache.getNoteIfExists(it)
if (existingNote != null) {
unwrapIfNeeded(existingNote.event, onReady)
} else {
// this is not verifiable
LocalCache.justConsume(it, null)
unwrapIfNeeded(it, onReady)
event.unseal(account.signer) {
// this is not verifiable
LocalCache.justConsume(it, null)
unwrapIfNeeded(it, onReady)
}
}
} ?: run {
event.unseal(account.signer) {
val existingNote = LocalCache.getNoteIfExists(it.id)
if (existingNote != null) {
unwrapIfNeeded(existingNote.event, onReady)
} else {
// this is not verifiable
LocalCache.justConsume(it, null)
unwrapIfNeeded(it, onReady)
}
}
}
}
Expand Down
Loading

0 comments on commit 448ea79

Please sign in to comment.