Caches OTS web calls to avoid pinging the server repeatedly for the same event.

This commit is contained in:
Vitor Pamplona
2024-09-26 13:45:01 -04:00
parent d23a859f34
commit d8d6736d55
4 changed files with 77 additions and 18 deletions

View File

@@ -22,6 +22,7 @@ package com.vitorpamplona.quartz.events
import android.util.Log
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.ots.BlockstreamExplorer
@@ -30,6 +31,7 @@ import com.vitorpamplona.quartz.ots.DetachedTimestampFile
import com.vitorpamplona.quartz.ots.Hash
import com.vitorpamplona.quartz.ots.OpenTimestamps
import com.vitorpamplona.quartz.ots.VerifyResult
import com.vitorpamplona.quartz.ots.exceptions.UrlException
import com.vitorpamplona.quartz.ots.op.OpSHA256
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -37,6 +39,25 @@ import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import kotlinx.coroutines.CancellationException
import java.util.Base64
@Immutable
sealed class VerificationState {
@Immutable object NotStarted : VerificationState()
@Stable
class Verified(
val verifiedTime: Long,
) : VerificationState()
@Immutable class Error(
val errorMessage: String,
) : VerificationState()
@Immutable class NetworkError(
val errorMessage: String,
val time: Long = TimeUtils.now(),
) : VerificationState()
}
@Immutable
class OtsEvent(
id: HexKey,
@@ -47,7 +68,7 @@ class OtsEvent(
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient
var verifiedTime: Long? = null
var verification: VerificationState = VerificationState.NotStarted
override fun countMemory(): Long =
super.countMemory() +
@@ -61,15 +82,24 @@ class OtsEvent(
fun otsByteArray(): ByteArray = Base64.getDecoder().decode(content)
fun cacheVerify(): Long? =
if (verifiedTime != null) {
verifiedTime
} else {
verifiedTime = verify()
verifiedTime
fun cacheVerify(): VerificationState =
when (val verif = verification) {
is VerificationState.Verified -> verif
is VerificationState.NotStarted -> verifyState().also { verification = it }
is VerificationState.NetworkError -> {
// try again in 5 mins
if (verif.time < TimeUtils.fiveMinutesAgo()) {
verifyState().also { verification = it }
} else {
verif
}
}
is VerificationState.Error -> verif
}
fun verify(): Long? = digestEvent()?.let { OtsEvent.verify(otsByteArray(), it) }
fun verifyState(): VerificationState = digestEvent()?.let { verify(otsByteArray(), it) } ?: VerificationState.Error("Digest Not found")
fun verify(): Long? = (verifyState() as? VerificationState.Verified)?.verifiedTime
fun info(): String {
val detachedOts = DetachedTimestampFile.deserialize(otsByteArray())
@@ -98,7 +128,7 @@ class OtsEvent(
return if (otsInstance.upgrade(detachedOts)) {
// if the change is now verifiable.
if (verify(detachedOts, eventId) != null) {
if (verify(detachedOts, eventId) is VerificationState.Verified) {
Base64.getEncoder().encodeToString(detachedOts.serialize())
} else {
otsFile
@@ -111,28 +141,37 @@ class OtsEvent(
fun verify(
otsFile: String,
eventId: HexKey,
): Long? = verify(Base64.getDecoder().decode(otsFile), eventId)
): VerificationState = verify(Base64.getDecoder().decode(otsFile), eventId)
fun verify(
otsFile: ByteArray,
eventId: HexKey,
): Long? = verify(DetachedTimestampFile.deserialize(otsFile), eventId)
): VerificationState = verify(DetachedTimestampFile.deserialize(otsFile), eventId)
fun verify(
detachedOts: DetachedTimestampFile,
eventId: HexKey,
): Long? {
): VerificationState {
try {
val result = otsInstance.verify(detachedOts, eventId.hexToByteArray())
if (result == null || result.isEmpty()) {
return null
return VerificationState.Error("Verification hashmap is empty")
} else {
return result.get(VerifyResult.Chains.BITCOIN)?.timestamp
val time = result.get(VerifyResult.Chains.BITCOIN)?.timestamp
return if (time != null) {
VerificationState.Verified(time)
} else {
VerificationState.Error("Does not include a Bitcoin verification")
}
}
} catch (e: Exception) {
if (e is CancellationException) throw e
Log.e("OpenTimeStamps", "Failed to verify", e)
return null
return if (e is UrlException) {
VerificationState.NetworkError(e.message ?: e.cause?.message ?: "Failed to verify")
} else {
VerificationState.Error(e.message ?: e.cause?.message ?: "Failed to verify")
}
}
}