diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 1c8a45196..bd167df80 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -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) @@ -2842,7 +2844,7 @@ class Account( ) { if (!isWriteable()) return - return event.cachedGift(signer, onReady) + return event.unwrap(signer, onReady) } fun unseal( @@ -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) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Chatroom.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Chatroom.kt index 63e3094ba..d85571003 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Chatroom.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Chatroom.kt @@ -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 @@ -75,7 +77,7 @@ class Chatroom { fun senderIntersects(keySet: Set): Boolean = authors.any { it.pubkeyHex in keySet } fun pruneMessagesToTheLatestOnly(): Set { - val sorted = roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() + val sorted = roomMessages.sortedWith(DefaultFeedOrder) val toKeep = if ((sorted.firstOrNull()?.createdAt() ?: 0) > TimeUtils.oneWeekAgo()) { @@ -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 diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index a97351b5c..cfcdc847c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -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) + } } } @@ -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) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index 5231262ce..57a728fa7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -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") @@ -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 -> { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index f56a0e059..06456d731 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -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) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt index 3ef9c1650..3fd596ada 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt @@ -43,10 +43,15 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.events.ChannelCreateEvent +import com.vitorpamplona.quartz.events.ChannelMessageEvent +import com.vitorpamplona.quartz.events.ChannelMetadataEvent import com.vitorpamplona.quartz.events.ChatroomKeyable import com.vitorpamplona.quartz.events.EventInterface import com.vitorpamplona.quartz.events.GiftWrapEvent +import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.events.LiveActivitiesEvent import com.vitorpamplona.quartz.events.SealedGossipEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -104,7 +109,7 @@ fun LoadRedirectScreen( val event = note.event if (event != null) { - withContext(Dispatchers.IO) { redirect(event, note, accountViewModel, nav) } + withContext(Dispatchers.IO) { redirect(event, accountViewModel, nav) } } } @@ -117,36 +122,61 @@ fun LoadRedirectScreen( } } +fun redirect( + eventId: HexKey, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LocalCache.getNoteIfExists(eventId)?.event?.let { + redirect(it, accountViewModel, nav) + } +} + fun redirect( event: EventInterface, - note: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val channelHex = note.channelHex() + val channelHex = + if ( + event is ChannelMessageEvent || + event is ChannelMetadataEvent || + event is ChannelCreateEvent || + event is LiveActivitiesChatMessageEvent || + event is LiveActivitiesEvent + ) { + (event as? ChannelMessageEvent)?.channel() + ?: (event as? ChannelMetadataEvent)?.channel() + ?: (event as? ChannelCreateEvent)?.id + ?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag() + ?: (event as? LiveActivitiesEvent)?.address()?.toTag() + } else { + null + } if (event is GiftWrapEvent) { - accountViewModel.unwrap(event) { redirect(it, note, accountViewModel, nav) } + event.innerEventId?.let { + redirect(it, accountViewModel, nav) + } ?: run { + accountViewModel.unwrap(event) { redirect(it, accountViewModel, nav) } + } } else if (event is SealedGossipEvent) { - accountViewModel.unseal(event) { redirect(it, note, accountViewModel, nav) } + event.innerEventId?.let { + redirect(it, accountViewModel, nav) + } ?: run { + accountViewModel.unseal(event) { redirect(it, accountViewModel, nav) } + } } else { - if (event == null) { - // stay here, loading - } else if (event is ChannelCreateEvent) { - nav("Channel/${note.idHex}") + if (event is ChannelCreateEvent) { + nav("Channel/${event.id()}") } else if (event is ChatroomKeyable) { - note.author?.let { - val withKey = - (event as ChatroomKeyable).chatroomKey(accountViewModel.userProfile().pubkeyHex) - - accountViewModel.userProfile().createChatroom(withKey) - - nav("Room/${withKey.hashCode()}") - } + val withKey = event.chatroomKey(accountViewModel.userProfile().pubkeyHex) + accountViewModel.userProfile().createChatroom(withKey) + nav("Room/${withKey.hashCode()}") } else if (channelHex != null) { nav("Channel/$channelHex") } else { - nav("Note/${note.idHex}") + nav("Note/${event.id()}") } } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt index b0e2e0ab8..11500e797 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/GiftWrapEvent.kt @@ -20,12 +20,14 @@ */ package com.vitorpamplona.quartz.events +import android.util.Log import androidx.compose.runtime.Immutable import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSignerInternal import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.utils.bytesUsedInMemory import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -37,11 +39,11 @@ class GiftWrapEvent( content: String, sig: HexKey, ) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { - @Transient private var cachedInnerEvent: Map = mapOf() + @Transient var innerEventId: HexKey? = null override fun countMemory(): Long = super.countMemory() + - 32 + (cachedInnerEvent.values.sumOf { pointerSizeInBytes + (it?.countMemory() ?: 0) }) // rough calculation + pointerSizeInBytes + (innerEventId?.bytesUsedInMemory() ?: 0) fun copyNoContent(): GiftWrapEvent { val copy = @@ -54,48 +56,38 @@ class GiftWrapEvent( sig, ) - copy.cachedInnerEvent = cachedInnerEvent + copy.innerEventId = innerEventId return copy } override fun isContentEncoded() = true - fun preCachedGift(signer: NostrSigner): Event? = cachedInnerEvent[signer.pubKey] - - fun addToCache( - pubKey: HexKey, - gift: Event, - ) { - cachedInnerEvent = cachedInnerEvent + Pair(pubKey, gift) - } - + @Deprecated( + message = "Heavy caching was removed from this class due to high memory use. Cache it separatedly", + replaceWith = ReplaceWith("unwrap"), + ) fun cachedGift( signer: NostrSigner, onReady: (Event) -> Unit, - ) { - cachedInnerEvent[signer.pubKey]?.let { - onReady(it) - return - } - unwrap(signer) { gift -> - if (gift is WrappedEvent) { - gift.host = HostStub(this.id, this.pubKey, this.kind) - } - addToCache(signer.pubKey, gift) + ) = unwrap(signer, onReady) - onReady(gift) - } - } - - private fun unwrap( + fun unwrap( signer: NostrSigner, onReady: (Event) -> Unit, ) { try { - plainContent(signer) { onReady(fromJson(it)) } + plainContent(signer) { giftStr -> + val gift = fromJson(giftStr) + if (gift is WrappedEvent) { + gift.host = HostStub(this.id, this.pubKey, this.kind) + } + innerEventId = gift.id + + onReady(gift) + } } catch (e: Exception) { - // Log.e("UnwrapError", "Couldn't Decrypt the content", e) + Log.w("GiftWrapEvent", "Couldn't Decrypt the content", e) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt index d6b92a8e1..135bc12cf 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/SealedGossipEvent.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.utils.bytesUsedInMemory import com.vitorpamplona.quartz.utils.pointerSizeInBytes @Immutable @@ -38,11 +39,11 @@ class SealedGossipEvent( content: String, sig: HexKey, ) : WrappedEvent(id, pubKey, createdAt, KIND, tags, content, sig) { - @Transient private var cachedInnerEvent: Map = mapOf() + @Transient var innerEventId: HexKey? = null override fun countMemory(): Long = super.countMemory() + - pointerSizeInBytes + cachedInnerEvent.values.sumOf { pointerSizeInBytes + (it?.countMemory() ?: 0) } + pointerSizeInBytes + (innerEventId?.bytesUsedInMemory() ?: 0) fun copyNoContent(): SealedGossipEvent { val copy = @@ -55,51 +56,38 @@ class SealedGossipEvent( sig, ) - copy.cachedInnerEvent = cachedInnerEvent copy.host = host + copy.innerEventId = innerEventId return copy } override fun isContentEncoded() = true - fun preCachedGossip(signer: NostrSigner): Event? = cachedInnerEvent[signer.pubKey] - - fun addToCache( - pubKey: HexKey, - gift: Event, - ) { - cachedInnerEvent = cachedInnerEvent + Pair(pubKey, gift) - } - + @Deprecated( + message = "Heavy caching was removed from this class due to high memory use. Cache it separatedly", + replaceWith = ReplaceWith("unseal"), + ) fun cachedGossip( signer: NostrSigner, onReady: (Event) -> Unit, - ) { - cachedInnerEvent[signer.pubKey]?.let { - onReady(it) - return - } + ) = unseal(signer, onReady) - unseal(signer) { gossip -> - val event = gossip.mergeWith(this) - if (event is WrappedEvent) { - event.host = host ?: HostStub(this.id, this.pubKey, this.kind) - } - addToCache(signer.pubKey, event) - - onReady(event) - } - } - - private fun unseal( + fun unseal( signer: NostrSigner, - onReady: (Gossip) -> Unit, + onReady: (Event) -> Unit, ) { try { plainContent(signer) { try { - onReady(Gossip.fromJson(it)) + val gossip = Gossip.fromJson(it) + val event = gossip.mergeWith(this) + if (event is WrappedEvent) { + event.host = host ?: HostStub(this.id, this.pubKey, this.kind) + } + innerEventId = event.id + + onReady(event) } catch (e: Exception) { Log.w("GossipEvent", "Fail to decrypt or parse Gossip", e) }