mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-18 19:40:45 +02:00
Caches OTS web calls to avoid pinging the server repeatedly for the same event.
This commit is contained in:
@@ -116,6 +116,7 @@ import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.events.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.events.TorrentEvent
|
||||
import com.vitorpamplona.quartz.events.VerificationState
|
||||
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
||||
import com.vitorpamplona.quartz.events.WikiNoteEvent
|
||||
@@ -1093,7 +1094,9 @@ object LocalCache {
|
||||
if (version.event?.id() == event.id()) return
|
||||
|
||||
// makes sure the OTS has a valid certificate
|
||||
if (event.cacheVerify() == null) return // no valid OTS
|
||||
val verif = event.cacheVerify()
|
||||
Log.d("AABBCC", "" + verif)
|
||||
if (verif is VerificationState.Error) return // no valid OTS
|
||||
|
||||
if (version.event == null) {
|
||||
version.loadEvent(event, author, emptyList())
|
||||
@@ -2155,7 +2158,7 @@ object LocalCache {
|
||||
notes.forEach { _, item ->
|
||||
val noteEvent = item.event
|
||||
if ((noteEvent is OtsEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time))) {
|
||||
noteEvent.verifiedTime?.let { stampedTime ->
|
||||
(noteEvent.cacheVerify() as? VerificationState.Verified)?.verifiedTime?.let { stampedTime ->
|
||||
if (minTime == null || stampedTime < (minTime ?: Long.MAX_VALUE)) {
|
||||
minTime = stampedTime
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.ots
|
||||
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.ammolite.service.HttpClientManager
|
||||
@@ -32,6 +33,9 @@ import okhttp3.Request
|
||||
class OkHttpBlockstreamExplorer(
|
||||
val forceProxy: (String) -> Boolean,
|
||||
) : BitcoinExplorer {
|
||||
private val cacheHeaders = LruCache<String, BlockHeader>(100)
|
||||
private val cacheHeights = LruCache<Int, String>(100)
|
||||
|
||||
/**
|
||||
* Retrieve the block information from the block hash.
|
||||
*
|
||||
@@ -40,6 +44,10 @@ class OkHttpBlockstreamExplorer(
|
||||
* @throws Exception desc
|
||||
*/
|
||||
override fun block(hash: String): BlockHeader {
|
||||
cacheHeaders.get(hash)?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val url = "$BLOCKSTREAM_API_URL/block/$hash"
|
||||
val client = HttpClientManager.getHttpClient(forceProxy(url))
|
||||
|
||||
@@ -61,6 +69,9 @@ class OkHttpBlockstreamExplorer(
|
||||
blockHeader.setTime(jsonObject["timestamp"].asInt().toString())
|
||||
blockHeader.blockHash = hash
|
||||
Log.d("OkHttpBlockstreamExplorer", "$BLOCKSTREAM_API_URL/block/$hash")
|
||||
|
||||
cacheHeaders.put(hash, blockHeader)
|
||||
|
||||
return blockHeader
|
||||
} else {
|
||||
throw UrlException("Couldn't open $url: " + it.message + " " + it.code)
|
||||
@@ -77,6 +88,10 @@ class OkHttpBlockstreamExplorer(
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun blockHash(height: Int): String {
|
||||
cacheHeights[height]?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val url = "$BLOCKSTREAM_API_URL/block-height/$height"
|
||||
val client = HttpClientManager.getHttpClient(forceProxy(url))
|
||||
|
||||
@@ -93,6 +108,8 @@ class OkHttpBlockstreamExplorer(
|
||||
val blockHash = it.body.string()
|
||||
|
||||
Log.d("OkHttpBlockstreamExplorer", "$url $blockHash")
|
||||
|
||||
cacheHeights.put(height, blockHash)
|
||||
return blockHash
|
||||
} else {
|
||||
throw UrlException("Couldn't open $url: " + it.message + " " + it.code)
|
||||
|
@@ -106,7 +106,7 @@ open class BaseCacheBenchmark {
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CacheLoadingBenchmark : BaseCacheBenchmark() {
|
||||
class CacheNotStartedBenchmark : BaseCacheBenchmark() {
|
||||
@get:Rule val benchmarkRule = BenchmarkRule()
|
||||
|
||||
@Test
|
||||
|
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user