mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 16:14:40 +00:00
Merge pull request #3702 from vitorpamplona/claude/ots-notes-lifecycle-ok3mz3
Anchor OTS attestations to target notes, replace verification cache
This commit is contained in:
@@ -137,7 +137,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CachingEventDe
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.SurgeDns
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.SurgeDnsStore
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpBitcoinExplorer
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
||||
@@ -577,12 +576,6 @@ class AppModules(
|
||||
)
|
||||
}
|
||||
|
||||
// Application-wide ots verification cache
|
||||
val otsVerifCache by lazy {
|
||||
Log.d("AppModules", "OtsCache Init")
|
||||
VerificationStateCache(otsResolverBuilder)
|
||||
}
|
||||
|
||||
val torEvaluatorFlow =
|
||||
TorRelayState(
|
||||
okHttpClients,
|
||||
@@ -747,7 +740,7 @@ class AppModules(
|
||||
// Tries to verify new OTS events when they arrive.
|
||||
val otsEventVerifier =
|
||||
IncomingOtsEventVerifier(
|
||||
otsVerifCache = { otsVerifCache },
|
||||
otsResolverBuilder = otsResolverBuilder,
|
||||
cache = cache,
|
||||
scope = applicationIOScope,
|
||||
)
|
||||
|
||||
@@ -197,14 +197,11 @@ import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.taggedAddresses
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.GenericETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUsers
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionIndex
|
||||
@@ -1451,12 +1448,17 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
// Already processed this event.
|
||||
if (version.event?.id == event.id) return false
|
||||
if (version.event != null) return false
|
||||
|
||||
if (wasVerified || justVerify(event)) {
|
||||
if (version.event == null) {
|
||||
version.loadEvent(event, author, emptyList())
|
||||
version.flowSet?.ots?.invalidateData()
|
||||
version.loadEvent(event, author, emptyList())
|
||||
|
||||
// Anchor the attestation to the note it timestamps (like an edit to its message), so it
|
||||
// survives exactly as long as that note and is dropped when the note is deleted or pruned.
|
||||
// addTimestamp invalidates the target's `ots` flow so the OTS pill re-derives — the old
|
||||
// code invalidated the attestation's OWN (observer-less) flow, so the target never updated.
|
||||
event.digestEventId()?.let { targetId ->
|
||||
getOrCreateNote(targetId).addTimestamp(version)
|
||||
}
|
||||
|
||||
refreshNewNoteObservers(version)
|
||||
@@ -3201,45 +3203,6 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
|
||||
fun getPeopleListNotesFor(user: User): List<AddressableNote> = addressables.filter(PeopleListEvent.KIND, user.pubkeyHex)
|
||||
|
||||
suspend fun findEarliestOtsForNote(
|
||||
note: Note,
|
||||
otsVerifCacheBuilder: () -> VerificationStateCache,
|
||||
): Long? {
|
||||
checkNotInMainThread()
|
||||
|
||||
var minTime: Long? = null
|
||||
val time = TimeUtils.now()
|
||||
|
||||
val candidates =
|
||||
notes.mapNotNull { _, item ->
|
||||
val noteEvent = item.event
|
||||
if ((noteEvent is OtsEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time))) {
|
||||
val cachedTime = (otsVerifCacheBuilder().justCache(noteEvent) as? VerificationState.Verified)?.verifiedTime
|
||||
if (cachedTime != null) {
|
||||
if (minTime == null || cachedTime < (minTime ?: Long.MAX_VALUE)) {
|
||||
minTime = cachedTime
|
||||
}
|
||||
null
|
||||
} else {
|
||||
// tries to verify again
|
||||
noteEvent
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
candidates.forEach { noteEvent ->
|
||||
(otsVerifCacheBuilder().cacheVerify(noteEvent) as? VerificationState.Verified)?.verifiedTime?.let { stampedTime ->
|
||||
if (minTime == null || stampedTime < (minTime ?: Long.MAX_VALUE)) {
|
||||
minTime = stampedTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minTime
|
||||
}
|
||||
|
||||
fun cleanMemory() {
|
||||
Log.d("LargeCache") { "Notes cleanup started. Current size: ${notes.size()}" }
|
||||
notes.cleanUp()
|
||||
@@ -3561,6 +3524,12 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
// and drop it there, or a deleted edit would keep overlaying its message.
|
||||
editedTargetIdOf(noteEvent)?.let { getNoteIfExists(it)?.removeEdit(note) }
|
||||
|
||||
// OTS attestations (kind 1040) are likewise anchored on their target's Note.timestamps with
|
||||
// no `replyTo` back-link — resolve the target by the `e` tag and drop the proof there.
|
||||
if (noteEvent is OtsEvent) {
|
||||
noteEvent.digestEventId()?.let { getNoteIfExists(it)?.removeTimestamp(note) }
|
||||
}
|
||||
|
||||
if (noteEvent is ReportEvent) {
|
||||
noteEvent.reportedAuthor().forEach {
|
||||
getUserIfExists(it.pubkey)?.reportsOrNull()?.let { reports ->
|
||||
|
||||
+7
-6
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.model.nip03Timestamp
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -32,7 +32,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class IncomingOtsEventVerifier(
|
||||
private val otsVerifCache: () -> VerificationStateCache,
|
||||
private val otsResolverBuilder: OtsResolverBuilder,
|
||||
private val cache: LocalCache,
|
||||
private val scope: CoroutineScope,
|
||||
) {
|
||||
@@ -49,11 +49,12 @@ class IncomingOtsEventVerifier(
|
||||
null,
|
||||
)
|
||||
|
||||
// Verifies each newly-arrived attestation once and memoizes the verdict on the note itself
|
||||
// (Note.otsVerification), so the OTS pill can render off a cached result without re-hitting
|
||||
// the blockchain. The verdict is evicted with the note — no separate cache to keep in sync.
|
||||
suspend fun consume(note: Note) {
|
||||
note.event?.let { event ->
|
||||
if (event is OtsEvent) {
|
||||
otsVerifCache().cacheVerify(event)
|
||||
}
|
||||
if (note.event is OtsEvent) {
|
||||
note.cacheVerifyOts(otsResolverBuilder.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip03Timestamp
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpirationBefore
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
/*
|
||||
* OTS (NIP-03) attestation resolution off a note's own [Note.timestamps] list. Every kind-1040
|
||||
* attestation targeting a note is anchored there as a hard-referenced child (like a reaction), so
|
||||
* finding a note's proofs is an in-memory fold — no LocalCache scan. Each attestation memoizes its
|
||||
* blockchain verdict in [Note.otsVerification], which shares the attestation note's lifecycle: it is
|
||||
* evicted when the target note is (a NIP-09 delete or a cache prune), so there is no separate,
|
||||
* id-keyed verification cache to keep in sync. This is the read side that replaces the old
|
||||
* VerificationStateCache + full-cache scan.
|
||||
*/
|
||||
|
||||
/** The memoized OTS verdict for this attestation note, or null when it has never been verified. */
|
||||
fun Note.justOtsVerification(): VerificationState? = otsVerification
|
||||
|
||||
/**
|
||||
* Returns the OTS verdict for this attestation note, verifying against the blockchain only when no
|
||||
* usable verdict exists yet (or a stale [VerificationState.NetworkError] is due for a retry). The
|
||||
* result is stored on the note so later reads are free. Must run off the main thread — verification
|
||||
* hits the network. No-op verdict ([VerificationState.Error]) when the note is not an OTS event.
|
||||
*/
|
||||
suspend fun Note.cacheVerifyOts(resolver: OtsResolver): VerificationState {
|
||||
val event = event as? OtsEvent ?: return VerificationState.Error("Not an OTS event")
|
||||
return when (val current = otsVerification) {
|
||||
is VerificationState.Verified -> current
|
||||
is VerificationState.Error -> current
|
||||
is VerificationState.NetworkError ->
|
||||
if (current.time < TimeUtils.fiveMinutesAgo()) verifyOts(event, resolver) else current
|
||||
// null, or a leftover non-terminal state from an interrupted run: (re)verify.
|
||||
else -> verifyOts(event, resolver)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the attestation and stores ONLY the terminal verdict. We deliberately never persist a
|
||||
* `Verifying` sentinel: this runs inside a cancellable `LoadOts` LaunchedEffect, so if the coroutine
|
||||
* were cancelled at the network suspension point after writing `Verifying` but before the verdict,
|
||||
* that sentinel would stick on the (long-lived) note forever — there is no LRU eviction to recover
|
||||
* it, unlike the old VerificationStateCache — and the OTS pill would silently vanish. Leaving the
|
||||
* field untouched until a real verdict lands means a cancelled run simply retries on the next read;
|
||||
* the cost is at worst two concurrent first-time verifications, which is harmless.
|
||||
*/
|
||||
private suspend fun Note.verifyOts(
|
||||
event: OtsEvent,
|
||||
resolver: OtsResolver,
|
||||
): VerificationState = event.verifyState(resolver).also { otsVerification = it }
|
||||
|
||||
/**
|
||||
* The earliest blockchain-verified time (unix seconds) among this note's non-expired OTS
|
||||
* attestations, or null when none verify. Reads already-cached verdicts first — so a proof that was
|
||||
* verified on arrival shows without waiting on the network — then verifies any still-unresolved
|
||||
* proofs. Must run off the main thread.
|
||||
*/
|
||||
suspend fun Note.earliestOtsVerifiedTime(resolver: OtsResolver): Long? {
|
||||
val now = TimeUtils.now()
|
||||
var minTime: Long? = null
|
||||
|
||||
fun consider(time: Long) {
|
||||
if (minTime.let { it == null || time < it }) minTime = time
|
||||
}
|
||||
|
||||
val live =
|
||||
timestamps.filter {
|
||||
val e = it.event
|
||||
e is OtsEvent && !e.isExpirationBefore(now)
|
||||
}
|
||||
|
||||
val unresolved =
|
||||
live.filter { proof ->
|
||||
val verified = (proof.justOtsVerification() as? VerificationState.Verified)?.verifiedTime
|
||||
if (verified != null) {
|
||||
consider(verified)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
unresolved.forEach { proof ->
|
||||
(proof.cacheVerifyOts(resolver) as? VerificationState.Verified)?.verifiedTime?.let(::consider)
|
||||
}
|
||||
|
||||
return minTime
|
||||
}
|
||||
@@ -37,8 +37,8 @@ import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChann
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.nip03Timestamp.earliestOtsVerifiedTime
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteOts
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||
@@ -119,12 +119,10 @@ fun LoadOts(
|
||||
val noteStatus by observeNoteOts(note, accountViewModel)
|
||||
|
||||
LaunchedEffect(key1 = noteStatus) {
|
||||
val target = noteStatus?.note ?: note
|
||||
val newOts =
|
||||
withContext(Dispatchers.IO) {
|
||||
LocalCache.findEarliestOtsForNote(
|
||||
note = noteStatus?.note ?: note,
|
||||
otsVerifCacheBuilder = { Amethyst.instance.otsVerifCache },
|
||||
)
|
||||
target.earliestOtsVerifiedTime(Amethyst.instance.otsResolverBuilder.build())
|
||||
}
|
||||
|
||||
earliestDate =
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.anyHashTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.publishedAt.PublishedAtProvider
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.threadRootIdOrSelf
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
@@ -164,6 +165,7 @@ open class Note(
|
||||
removeBoost(note)
|
||||
removeReaction(note)
|
||||
removeEdit(note)
|
||||
removeTimestamp(note)
|
||||
removeZap(note)
|
||||
removeZapPayment(note)
|
||||
removeReport(note)
|
||||
@@ -178,6 +180,18 @@ open class Note(
|
||||
|
||||
fun pollState(): PollResponsesCache = poll ?: PollResponsesCache().also { poll = it }
|
||||
|
||||
/**
|
||||
* When this note holds a NIP-03 OpenTimestamps event (kind 1040), its memoized blockchain
|
||||
* verification verdict. Kept on the note itself — not in a separate id-keyed cache — so the
|
||||
* (expensive, network-backed) result shares the note's lifecycle: the attestation is anchored on
|
||||
* its target's [timestamps] and both are evicted together. `null` means never verified yet.
|
||||
*
|
||||
* `@Volatile`: written by the OTS verifier on `applicationIOScope` and read from the Compose
|
||||
* read path when folding an earliest-attested time.
|
||||
*/
|
||||
@Volatile
|
||||
var otsVerification: VerificationState? = null
|
||||
|
||||
// These fields are updated every time an event related to this note is received.
|
||||
var replies = listOf<Note>()
|
||||
private set
|
||||
@@ -199,6 +213,16 @@ open class Note(
|
||||
var edits = listOf<Note>()
|
||||
private set
|
||||
|
||||
/**
|
||||
* NIP-03 OpenTimestamps attestations (kind 1040) proving this note existed by a given time,
|
||||
* held here — like [reactions] and [edits] — so an attestation survives exactly as long as the
|
||||
* note it timestamps and is dropped the moment that note leaves the cache (including on a NIP-09
|
||||
* delete). Anchoring them here also replaces the old full-cache scan: the OTS pill folds this
|
||||
* list directly. Each attestation memoizes its own blockchain verdict in [Note.otsVerification].
|
||||
*/
|
||||
var timestamps = listOf<Note>()
|
||||
private set
|
||||
|
||||
var reports = mapOf<User, List<Note>>()
|
||||
private set
|
||||
|
||||
@@ -424,6 +448,20 @@ open class Note(
|
||||
}
|
||||
}
|
||||
|
||||
fun addTimestamp(note: Note) {
|
||||
if (note !in timestamps) {
|
||||
timestamps = timestamps + note
|
||||
flowSet?.ots?.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeTimestamp(note: Note) {
|
||||
if (note in timestamps) {
|
||||
timestamps = timestamps - note
|
||||
flowSet?.ots?.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeBoost(note: Note) {
|
||||
if (note in boosts) {
|
||||
boosts = boosts - note
|
||||
@@ -437,6 +475,7 @@ open class Note(
|
||||
val zapsChanged = zaps.isNotEmpty() || zapPayments.isNotEmpty() || onchainZaps.isNotEmpty() || nutzaps.isNotEmpty()
|
||||
val boostsChanged = boosts.isNotEmpty()
|
||||
val editsChanged = edits.isNotEmpty()
|
||||
val timestampsChanged = timestamps.isNotEmpty()
|
||||
val reportsChanged = reports.isNotEmpty()
|
||||
val labelsChanged = labels.isNotEmpty()
|
||||
|
||||
@@ -445,6 +484,7 @@ open class Note(
|
||||
reactions.values.flatten() +
|
||||
boosts +
|
||||
edits +
|
||||
timestamps +
|
||||
reports.values.flatten() +
|
||||
labels.values.flatten() +
|
||||
zaps.keys +
|
||||
@@ -458,6 +498,7 @@ open class Note(
|
||||
reactions = mapOf()
|
||||
boosts = listOf()
|
||||
edits = listOf()
|
||||
timestamps = listOf()
|
||||
reports = mapOf()
|
||||
labels = mapOf()
|
||||
zaps = mapOf()
|
||||
@@ -472,6 +513,7 @@ open class Note(
|
||||
if (reactionsChanged) flowSet?.reactions?.invalidateData()
|
||||
if (boostsChanged) flowSet?.boosts?.invalidateData()
|
||||
if (editsChanged) flowSet?.edits?.invalidateData()
|
||||
if (timestampsChanged) flowSet?.ots?.invalidateData()
|
||||
if (reportsChanged) flowSet?.reports?.invalidateData()
|
||||
if (labelsChanged) flowSet?.labels?.invalidateData()
|
||||
if (zapsChanged) flowSet?.zaps?.invalidateData()
|
||||
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip03Timestamp
|
||||
|
||||
import androidx.collection.LruCache
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class VerificationStateCache(
|
||||
val otsResolverBuilder: OtsResolverBuilder,
|
||||
) {
|
||||
private val cache = LruCache<HexKey, VerificationState>(200)
|
||||
|
||||
suspend fun verify(event: OtsEvent): VerificationState {
|
||||
cache.put(event.id, VerificationState.Verifying)
|
||||
return event.verifyState(otsResolverBuilder.build()).also { cache.put(event.id, it) }
|
||||
}
|
||||
|
||||
fun justCache(event: OtsEvent): VerificationState? = cache[event.id]
|
||||
|
||||
suspend fun cacheVerify(event: OtsEvent): VerificationState =
|
||||
when (val verif = cache[event.id]) {
|
||||
is VerificationState.Verifying -> {
|
||||
verif
|
||||
}
|
||||
|
||||
is VerificationState.Verified -> {
|
||||
verif
|
||||
}
|
||||
|
||||
is VerificationState.NetworkError -> {
|
||||
// try again in 5 mins
|
||||
if (verif.time < TimeUtils.fiveMinutesAgo()) {
|
||||
verify(event)
|
||||
} else {
|
||||
verif
|
||||
}
|
||||
}
|
||||
|
||||
is VerificationState.Error -> {
|
||||
verif
|
||||
}
|
||||
|
||||
else -> {
|
||||
verify(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user