problem: doesn't cache recent Tx Receipts
This commit is contained in:
@@ -33,6 +33,7 @@ open class Caches(
|
||||
private val memBlocksByHash: BlocksMemCache,
|
||||
private val blocksByHeight: HeightCache,
|
||||
private val memTxsByHash: TxMemCache,
|
||||
private val memReceipts: ReceiptMemCache,
|
||||
private val redisBlocksByHash: BlocksRedisCache?,
|
||||
private val redisTxsByHash: TxRedisCache?,
|
||||
private val redisReceipts: ReceiptRedisCache?,
|
||||
@@ -59,6 +60,8 @@ open class Caches(
|
||||
private val txsByHash: Reader<TxId, TxContainer>
|
||||
private val receiptByHash: Reader<TxId, ByteArray>
|
||||
|
||||
private var head: Head? = null
|
||||
|
||||
init {
|
||||
blocksByHash = if (redisBlocksByHash == null) {
|
||||
memBlocksByHash
|
||||
@@ -70,26 +73,24 @@ open class Caches(
|
||||
} else {
|
||||
CompoundReader(memTxsByHash, redisTxsByHash)
|
||||
}
|
||||
receiptByHash = redisReceipts ?: EmptyReader()
|
||||
receiptByHash = if (redisReceipts == null) {
|
||||
memReceipts
|
||||
} else {
|
||||
CompoundReader(memReceipts, redisReceipts)
|
||||
}
|
||||
}
|
||||
|
||||
fun setHead(head: Head) {
|
||||
this.head = head
|
||||
redisTxsByHash?.head = head
|
||||
redisReceipts?.head = head
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache data that was just requested
|
||||
*/
|
||||
fun cacheRequested(data: Any) {
|
||||
if (data is TxContainer) {
|
||||
cache(Tag.REQUESTED, data)
|
||||
} else if (data is BlockContainer) {
|
||||
cache(Tag.REQUESTED, data)
|
||||
}
|
||||
}
|
||||
|
||||
open fun cacheReceipt(tag: Tag, data: DefaultContainer<TransactionReceiptJson>) {
|
||||
val currentHeight = head?.getCurrentHeight()
|
||||
if (currentHeight != null && data.height != null && memReceipts.acceptsRecentBlocks(currentHeight - data.height)) {
|
||||
memReceipts.add(data)
|
||||
}
|
||||
//TODO move subscription to the caller
|
||||
redisReceipts?.add(data)?.subscribe()
|
||||
}
|
||||
@@ -165,6 +166,7 @@ open class Caches(
|
||||
memBlocksByHash.get(blockId)?.let { block ->
|
||||
memTxsByHash.evict(block)
|
||||
redisTxsByHash?.evict(block)
|
||||
memReceipts.evict(block)
|
||||
evicted = true
|
||||
}
|
||||
if (!evicted) {
|
||||
@@ -224,6 +226,7 @@ open class Caches(
|
||||
private var blocksByHash: BlocksMemCache? = null
|
||||
private var blocksByHeight: HeightCache? = null
|
||||
private var txsByHash: TxMemCache? = null
|
||||
private var receipts: ReceiptMemCache? = null
|
||||
private var redisBlocksByHash: BlocksRedisCache? = null
|
||||
private var redisTxsByHash: TxRedisCache? = null
|
||||
private var redisReceiptCache: ReceiptRedisCache? = null
|
||||
@@ -259,6 +262,11 @@ open class Caches(
|
||||
return this
|
||||
}
|
||||
|
||||
fun setReceipts(cache: ReceiptMemCache): Builder {
|
||||
this.receipts = cache
|
||||
return this
|
||||
}
|
||||
|
||||
fun setHeightByHash(cache: HeightByHashRedisCache): Builder {
|
||||
redisHeightByHashCache = cache
|
||||
return this
|
||||
@@ -274,7 +282,10 @@ open class Caches(
|
||||
if (txsByHash == null) {
|
||||
txsByHash = TxMemCache()
|
||||
}
|
||||
return Caches(blocksByHash!!, blocksByHeight!!, txsByHash!!,
|
||||
if (receipts == null) {
|
||||
receipts = ReceiptMemCache()
|
||||
}
|
||||
return Caches(blocksByHash!!, blocksByHeight!!, txsByHash!!, receipts!!,
|
||||
redisBlocksByHash, redisTxsByHash, redisReceiptCache, redisHeightByHashCache)
|
||||
}
|
||||
}
|
||||
|
||||
64
src/main/kotlin/io/emeraldpay/dshackle/cache/ReceiptMemCache.kt
vendored
Normal file
64
src/main/kotlin/io/emeraldpay/dshackle/cache/ReceiptMemCache.kt
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2021 EmeraldPay, Inc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.DefaultContainer
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson
|
||||
import org.slf4j.LoggerFactory
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Keeps receipts for recent blocks in memory
|
||||
*/
|
||||
open class ReceiptMemCache(
|
||||
// how many blocks to keeps in memory
|
||||
val blocks: Int = 6
|
||||
) : Reader<TxId, ByteArray> {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(ReceiptMemCache::class.java)
|
||||
}
|
||||
|
||||
private val mapping = Caffeine.newBuilder()
|
||||
.maximumSize(blocks * 200L)
|
||||
.build<TxId, ByteArray>()
|
||||
|
||||
open fun evict(block: BlockContainer) {
|
||||
block.transactions.forEach {
|
||||
mapping.invalidate(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(key: TxId): Mono<ByteArray> {
|
||||
return mapping.getIfPresent(key)?.let { Mono.just(it) } ?: Mono.empty()
|
||||
}
|
||||
|
||||
open fun add(receipt: DefaultContainer<TransactionReceiptJson>): Mono<Void> {
|
||||
if (receipt.txId != null && receipt.json != null) {
|
||||
mapping.put(receipt.txId, receipt.json)
|
||||
}
|
||||
return Mono.empty()
|
||||
}
|
||||
|
||||
open fun acceptsRecentBlocks(heightDelta: Long): Boolean {
|
||||
return blocks <= heightDelta && heightDelta >= 0
|
||||
}
|
||||
|
||||
}
|
||||
@@ -200,12 +200,6 @@ open class NativeCall(
|
||||
.map {
|
||||
CallResult(ctx.id, it.value, null)
|
||||
}
|
||||
.doOnNext {
|
||||
it.result?.let { value ->
|
||||
ctx.upstream.postprocessor
|
||||
.onReceive(ctx.payload.method, ctx.payload.params, value)
|
||||
}
|
||||
}
|
||||
.onErrorResume { t ->
|
||||
val failure = if (t is CallFailure) {
|
||||
CallResult.fail(t.id, t.reason)
|
||||
|
||||
@@ -140,6 +140,7 @@ abstract class Multistream(
|
||||
apis.request(1)
|
||||
return Mono.from(apis)
|
||||
.map(Upstream::getApi)
|
||||
.map { RequestPostprocessor.wrap(it, postprocessor) } //TODO do it on upstream init, not each time it's called
|
||||
.switchIfEmpty(Mono.error(Exception("No API available for $chain")))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,38 @@
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
interface RequestPostprocessor {
|
||||
|
||||
fun onReceive(method: String, params: List<Any>, json: ByteArray)
|
||||
fun onReceive(method: String, params: List<Any?>, json: ByteArray)
|
||||
|
||||
class Empty : RequestPostprocessor {
|
||||
override fun onReceive(method: String, params: List<Any>, json: ByteArray) {}
|
||||
override fun onReceive(method: String, params: List<Any?>, json: ByteArray) {}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun wrap(reader: Reader<JsonRpcRequest, JsonRpcResponse>, processor: RequestPostprocessor): Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||
return Wrapper(reader, processor)
|
||||
}
|
||||
}
|
||||
|
||||
class Wrapper(
|
||||
private val reader: Reader<JsonRpcRequest, JsonRpcResponse>,
|
||||
private val processor: RequestPostprocessor
|
||||
) : Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||
|
||||
override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> {
|
||||
return reader.read(key)
|
||||
.doOnNext {
|
||||
if (it.hasResult()) {
|
||||
val result = it.getResult()
|
||||
processor.onReceive(key.method, key.params, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ class Selector {
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
val empty = EmptyMatcher()
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.DefaultContainer
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.RequestPostprocessor
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -32,7 +33,7 @@ class CacheRequested(
|
||||
private val log = LoggerFactory.getLogger(CacheRequested::class.java)
|
||||
}
|
||||
|
||||
override fun onReceive(method: String, params: List<Any>, json: ByteArray) {
|
||||
override fun onReceive(method: String, params: List<Any?>, json: ByteArray) {
|
||||
try {
|
||||
if (method == "eth_getTransactionReceipt") {
|
||||
cacheTxReceipt(params, json)
|
||||
@@ -42,7 +43,7 @@ class CacheRequested(
|
||||
}
|
||||
}
|
||||
|
||||
fun cacheTxReceipt(params: List<Any>, json: ByteArray) {
|
||||
fun cacheTxReceipt(params: List<Any?>, json: ByteArray) {
|
||||
if (params.size != 1) {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user