Refactors GiftWrap caching to delete encrypted text and reload the wrap if necessary.

- Changes host to a host stub to reduce memory use
- Only download GiftWraps form 2 days past the last EOSE
This commit is contained in:
Vitor Pamplona
2024-05-28 11:48:20 -04:00
parent 0c22e66e8f
commit 6a17a8a871
9 changed files with 108 additions and 7 deletions
@@ -39,7 +39,9 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.relays.Constants
import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.TypedFilter
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.ATag
@@ -856,7 +858,22 @@ class Account(
fun broadcast(note: Note) { fun broadcast(note: Note) {
note.event?.let { note.event?.let {
if (it is WrappedEvent && it.host != null) { if (it is WrappedEvent && it.host != null) {
it.host?.let { hostEvent -> Client.send(hostEvent) } it.host?.let {
Client.sendFilterAndStopOnFirstResponse(
filters =
listOf(
TypedFilter(
setOf(FeedType.FOLLOWS, FeedType.PRIVATE_DMS, FeedType.GLOBAL),
JsonFilter(
ids = listOf(it.id),
),
),
),
onResponse = {
Client.send(it)
},
)
}
} else { } else {
Client.send(it) Client.send(it)
} }
@@ -1808,7 +1808,7 @@ object LocalCache {
// Already processed this event. // Already processed this event.
if (note.event != null) return if (note.event != null) return
note.loadEvent(event, author, emptyList()) note.loadEvent(event.copyNoContent(), author, emptyList())
refreshObservers(note) refreshObservers(note)
} }
@@ -1828,7 +1828,7 @@ object LocalCache {
// Already processed this event. // Already processed this event.
if (note.event != null) return if (note.event != null) return
note.loadEvent(event, author, emptyList()) note.loadEvent(event.copyNoContent(), author, emptyList())
refreshObservers(note) refreshObservers(note)
} }
@@ -152,7 +152,7 @@ open class Note(val idHex: String) {
Nip19Bech32.createNEvent( Nip19Bech32.createNEvent(
host.id, host.id,
host.pubKey, host.pubKey,
host.kind(), host.kind,
relays.firstOrNull()?.url, relays.firstOrNull()?.url,
) )
} else { } else {
@@ -239,6 +239,14 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
JsonFilter( JsonFilter(
kinds = listOf(GiftWrapEvent.KIND), kinds = listOf(GiftWrapEvent.KIND),
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)),
since =
latestEOSEs.users[account.userProfile()]
?.followList
?.get("&&((GIFTWRAPS_EOSE))&&")
?.relayList
?.mapValues {
EOSETime(it.value.time - TimeUtils.twoDays())
},
), ),
) )
@@ -251,6 +259,13 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
relayUrl, relayUrl,
time, time,
) )
latestEOSEs.addOrUpdate(
account.userProfile(),
"&&((GIFTWRAPS_EOSE))&&",
relayUrl,
time,
)
} else { } else {
hasLoadedTheBasics[account.userProfile()] = true hasLoadedTheBasics[account.userProfile()] = true
@@ -100,6 +100,34 @@ object Client : RelayPool.Listener {
RelayPool.sendFilter(subscriptionId, filters) RelayPool.sendFilter(subscriptionId, filters)
} }
fun sendFilterAndStopOnFirstResponse(
subscriptionId: String = UUID.randomUUID().toString().substring(0..10),
filters: List<TypedFilter> = listOf(),
onResponse: (Event) -> Unit,
) {
checkNotInMainThread()
subscribe(
object : Listener() {
override fun onEvent(
event: Event,
subId: String,
relay: Relay,
afterEOSE: Boolean,
) {
if (subId == subscriptionId) {
onResponse(event)
unsubscribe(this)
close(subscriptionId)
}
}
},
)
subscriptions = subscriptions + Pair(subscriptionId, filters)
RelayPool.sendFilter(subscriptionId, filters)
}
fun sendFilterOnlyIfDisconnected( fun sendFilterOnlyIfDisconnected(
subscriptionId: String = UUID.randomUUID().toString().substring(0..10), subscriptionId: String = UUID.randomUUID().toString().substring(0..10),
filters: List<TypedFilter> = listOf(), filters: List<TypedFilter> = listOf(),
@@ -526,9 +526,15 @@ open class WrappedEvent(
content: String, content: String,
sig: HexKey, sig: HexKey,
) : Event(id, pubKey, createdAt, kind, tags, content, sig) { ) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
@Transient var host: Event? = null // host event to broadcast when needed @Transient var host: HostStub? = null // host event to broadcast when needed
} }
class HostStub(
val id: HexKey,
val pubKey: HexKey,
val kind: Int,
)
@Immutable @Immutable
interface AddressableEvent { interface AddressableEvent {
fun dTag(): String fun dTag(): String
@@ -38,6 +38,22 @@ class GiftWrapEvent(
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { ) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient private var cachedInnerEvent: Map<HexKey, Event?> = mapOf() @Transient private var cachedInnerEvent: Map<HexKey, Event?> = mapOf()
fun copyNoContent(): GiftWrapEvent {
val copy =
GiftWrapEvent(
id,
pubKey,
createdAt,
tags,
"",
sig,
)
copy.cachedInnerEvent = cachedInnerEvent
return copy
}
override fun isContentEncoded() = true override fun isContentEncoded() = true
fun preCachedGift(signer: NostrSigner): Event? { fun preCachedGift(signer: NostrSigner): Event? {
@@ -61,7 +77,7 @@ class GiftWrapEvent(
} }
unwrap(signer) { gift -> unwrap(signer) { gift ->
if (gift is WrappedEvent) { if (gift is WrappedEvent) {
gift.host = this gift.host = HostStub(this.id, this.pubKey, this.kind)
} }
addToCache(signer.pubKey, gift) addToCache(signer.pubKey, gift)
@@ -39,6 +39,23 @@ class SealedGossipEvent(
) : WrappedEvent(id, pubKey, createdAt, KIND, tags, content, sig) { ) : WrappedEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient private var cachedInnerEvent: Map<HexKey, Event?> = mapOf() @Transient private var cachedInnerEvent: Map<HexKey, Event?> = mapOf()
fun copyNoContent(): SealedGossipEvent {
val copy =
SealedGossipEvent(
id,
pubKey,
createdAt,
tags,
"",
sig,
)
copy.cachedInnerEvent = cachedInnerEvent
copy.host = host
return copy
}
override fun isContentEncoded() = true override fun isContentEncoded() = true
fun preCachedGossip(signer: NostrSigner): Event? { fun preCachedGossip(signer: NostrSigner): Event? {
@@ -64,7 +81,7 @@ class SealedGossipEvent(
unseal(signer) { gossip -> unseal(signer) { gossip ->
val event = gossip.mergeWith(this) val event = gossip.mergeWith(this)
if (event is WrappedEvent) { if (event is WrappedEvent) {
event.host = host ?: this event.host = host ?: HostStub(this.id, this.pubKey, this.kind)
} }
addToCache(signer.pubKey, event) addToCache(signer.pubKey, event)
@@ -51,6 +51,8 @@ object TimeUtils {
fun eightHoursAgo() = now() - EIGHT_HOURS fun eightHoursAgo() = now() - EIGHT_HOURS
fun twoDays() = ONE_DAY * 2
fun oneWeekAgo() = now() - ONE_WEEK fun oneWeekAgo() = now() - ONE_WEEK
fun randomWithinAWeek() = System.currentTimeMillis() / 1000 - CryptoUtils.randomInt(ONE_WEEK) fun randomWithinAWeek() = System.currentTimeMillis() / 1000 - CryptoUtils.randomInt(ONE_WEEK)