problem: doesn't use cache for block-by-height requests
This commit is contained in:
27
src/main/kotlin/io/emeraldpay/dshackle/cache/BlockByHeight.kt
vendored
Normal file
27
src/main/kotlin/io/emeraldpay/dshackle/cache/BlockByHeight.kt
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package io.emeraldpay.dshackle.cache
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects two caches to read through them. First is cache height->hash, second is hash->block.
|
||||||
|
*/
|
||||||
|
class BlockByHeight(
|
||||||
|
private val heights: Reader<Long, BlockHash>,
|
||||||
|
private val blocks: Reader<BlockHash, BlockJson<TransactionRefJson>>
|
||||||
|
): Reader<Long, BlockJson<TransactionRefJson>> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(BlockByHeight::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(key: Long): Mono<BlockJson<TransactionRefJson>> {
|
||||||
|
return heights.read(key)
|
||||||
|
.flatMap { blocks.read(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.cache
|
package io.emeraldpay.dshackle.cache
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.infinitape.etherjar.domain.BlockHash
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
import io.infinitape.etherjar.domain.TransactionId
|
import io.infinitape.etherjar.domain.TransactionId
|
||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
@@ -25,13 +26,13 @@ import java.util.concurrent.ConcurrentLinkedQueue
|
|||||||
|
|
||||||
class BlocksMemCache(
|
class BlocksMemCache(
|
||||||
val maxSize: Int = 64
|
val maxSize: Int = 64
|
||||||
) {
|
): Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
||||||
|
|
||||||
private val mapping = ConcurrentHashMap<BlockHash, BlockJson<TransactionRefJson>>()
|
private val mapping = ConcurrentHashMap<BlockHash, BlockJson<TransactionRefJson>>()
|
||||||
private val queue = ConcurrentLinkedQueue<BlockHash>()
|
private val queue = ConcurrentLinkedQueue<BlockHash>()
|
||||||
|
|
||||||
fun get(hash: BlockHash): Mono<BlockJson<TransactionRefJson>> {
|
override fun read(key: BlockHash): Mono<BlockJson<TransactionRefJson>> {
|
||||||
return Mono.justOrEmpty(mapping[hash])
|
return Mono.justOrEmpty(mapping[key])
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(block: BlockJson<TransactionRefJson>) {
|
fun add(block: BlockJson<TransactionRefJson>) {
|
||||||
|
|||||||
38
src/main/kotlin/io/emeraldpay/dshackle/cache/HeightCache.kt
vendored
Normal file
38
src/main/kotlin/io/emeraldpay/dshackle/cache/HeightCache.kt
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package io.emeraldpay.dshackle.cache
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory cache for blocks heights, keeps mapping height->hash.
|
||||||
|
*/
|
||||||
|
class HeightCache(
|
||||||
|
val maxSize: Int = 256
|
||||||
|
): Reader<Long, BlockHash> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(HeightCache::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val heights = ConcurrentHashMap<Long, BlockHash>()
|
||||||
|
|
||||||
|
override fun read(key: Long): Mono<BlockHash> {
|
||||||
|
return Mono.justOrEmpty(heights[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(block: BlockJson<TransactionRefJson>) {
|
||||||
|
heights[block.number] = block.hash
|
||||||
|
|
||||||
|
// evict old numbers if full
|
||||||
|
var dropHeight = block.number - maxSize
|
||||||
|
while (heights.size > maxSize && dropHeight < block.number) {
|
||||||
|
heights.remove(dropHeight)
|
||||||
|
dropHeight++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2019 ETCDEV GmbH
|
|
||||||
*
|
|
||||||
* 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.reader
|
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
|
||||||
import io.infinitape.etherjar.domain.BlockHash
|
|
||||||
import io.infinitape.etherjar.domain.TransactionId
|
|
||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
|
||||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
|
|
||||||
class BlockCacheReader(
|
|
||||||
val cache: BlocksMemCache
|
|
||||||
): Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
|
||||||
|
|
||||||
override fun read(key: BlockHash): Mono<BlockJson<TransactionRefJson>> {
|
|
||||||
return cache.get(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,18 +16,11 @@
|
|||||||
package io.emeraldpay.dshackle.upstream
|
package io.emeraldpay.dshackle.upstream
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.emeraldpay.dshackle.cache.BlockByHeight
|
||||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||||
|
import io.emeraldpay.dshackle.cache.HeightCache
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
import io.emeraldpay.dshackle.reader.BlockCacheReader
|
|
||||||
import io.emeraldpay.dshackle.reader.CompoundReader
|
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||||
import io.infinitape.etherjar.domain.BlockHash
|
|
||||||
import io.infinitape.etherjar.domain.TransactionId
|
|
||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
|
||||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
|
||||||
import org.reactivestreams.Publisher
|
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
import reactor.core.Disposable
|
import reactor.core.Disposable
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
@@ -42,11 +35,9 @@ abstract class AggregatedUpstream(
|
|||||||
val objectMapper: ObjectMapper
|
val objectMapper: ObjectMapper
|
||||||
): Upstream, Lifecycle {
|
): Upstream, Lifecycle {
|
||||||
|
|
||||||
private val blocksCache = BlocksMemCache()
|
|
||||||
private var cacheSubscription: Disposable? = null
|
private var cacheSubscription: Disposable? = null
|
||||||
private val blockReader: Reader<BlockHash, BlockJson<TransactionRefJson>> = CompoundReader(
|
private val blockReaderByHash = BlocksMemCache()
|
||||||
listOf(BlockCacheReader(blocksCache))
|
private val blockReaderByHeight = HeightCache()
|
||||||
)
|
|
||||||
var cache: CachingEthereumApi = CachingEthereumApi.empty()
|
var cache: CachingEthereumApi = CachingEthereumApi.empty()
|
||||||
private val reconfigLock = ReentrantLock()
|
private val reconfigLock = ReentrantLock()
|
||||||
private var callMethods: CallMethods? = null
|
private var callMethods: CallMethods? = null
|
||||||
@@ -118,9 +109,10 @@ abstract class AggregatedUpstream(
|
|||||||
reconfigLock.withLock {
|
reconfigLock.withLock {
|
||||||
cacheSubscription?.dispose()
|
cacheSubscription?.dispose()
|
||||||
cacheSubscription = head.getFlux().subscribe {
|
cacheSubscription = head.getFlux().subscribe {
|
||||||
blocksCache.add(it)
|
blockReaderByHash.add(it)
|
||||||
|
blockReaderByHeight.add(it)
|
||||||
}
|
}
|
||||||
cache = CachingEthereumApi(objectMapper, blockReader, head)
|
cache = CachingEthereumApi(objectMapper, blockReaderByHash, BlockByHeight(blockReaderByHeight, blockReaderByHash), head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ import io.infinitape.etherjar.rpc.json.ResponseJson
|
|||||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
import java.math.BigInteger
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
open class CachingEthereumApi(
|
open class CachingEthereumApi(
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
private val cache: Reader<BlockHash, BlockJson<TransactionRefJson>>,
|
private val cache: Reader<BlockHash, BlockJson<TransactionRefJson>>,
|
||||||
|
private val cacheHeight: Reader<Long, BlockJson<TransactionRefJson>>,
|
||||||
private val head: EthereumHead
|
private val head: EthereumHead
|
||||||
): EthereumApi(objectMapper) {
|
): EthereumApi(objectMapper) {
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ open class CachingEthereumApi(
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun empty(): CachingEthereumApi {
|
fun empty(): CachingEthereumApi {
|
||||||
return CachingEthereumApi(ObjectMapper(), EmptyReader(), EmptyEthereumHead())
|
return CachingEthereumApi(ObjectMapper(), EmptyReader(), EmptyReader(), EmptyEthereumHead())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +65,21 @@ open class CachingEthereumApi(
|
|||||||
Mono.empty()
|
Mono.empty()
|
||||||
}
|
}
|
||||||
else Mono.empty()
|
else Mono.empty()
|
||||||
|
"eth_getBlockByNumber" ->
|
||||||
|
if (params.size == 2 && (params[1] == "false" || params[1] == false))
|
||||||
|
Mono.just(params[0])
|
||||||
|
.map { HexQuantity.from(it as String) }
|
||||||
|
.filter {
|
||||||
|
it.value < BigInteger.valueOf(Long.MAX_VALUE)
|
||||||
|
}
|
||||||
|
.map { it.value.toLong() }
|
||||||
|
.flatMap(cacheHeight::read)
|
||||||
|
.map(toJson(id))
|
||||||
|
.onErrorResume { t ->
|
||||||
|
log.warn("Error during read from cache", t)
|
||||||
|
Mono.empty()
|
||||||
|
}
|
||||||
|
else Mono.empty()
|
||||||
else ->
|
else ->
|
||||||
Mono.empty()
|
Mono.empty()
|
||||||
}
|
}
|
||||||
|
|||||||
138
src/test/groovy/io/emeraldpay/dshackle/cache/BlockByHeightSpec.groovy
vendored
Normal file
138
src/test/groovy/io/emeraldpay/dshackle/cache/BlockByHeightSpec.groovy
vendored
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package io.emeraldpay.dshackle.cache
|
||||||
|
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class BlockByHeightSpec extends Specification {
|
||||||
|
|
||||||
|
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||||
|
String hash2 = "0x4aabdaff9acd2f30d15e00ab5dfd5f6c56ba4ea1c968a7ff8d3f34de70153b33"
|
||||||
|
|
||||||
|
def "Fetch with all data available"() {
|
||||||
|
setup:
|
||||||
|
def blocks = new BlocksMemCache()
|
||||||
|
def heights = new HeightCache()
|
||||||
|
|
||||||
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
|
block.number = 100
|
||||||
|
block.hash = BlockHash.from(hash1)
|
||||||
|
|
||||||
|
blocks.add(block)
|
||||||
|
heights.add(block)
|
||||||
|
|
||||||
|
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = blocksByHeight.read(100).block()
|
||||||
|
then:
|
||||||
|
act == block
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Fetch correct blocks if multiple"() {
|
||||||
|
setup:
|
||||||
|
def blocks = new BlocksMemCache()
|
||||||
|
def heights = new HeightCache()
|
||||||
|
|
||||||
|
def block1 = new BlockJson<TransactionRefJson>()
|
||||||
|
block1.number = 100
|
||||||
|
block1.hash = BlockHash.from(hash1)
|
||||||
|
def block2 = new BlockJson<TransactionRefJson>()
|
||||||
|
block2.number = 101
|
||||||
|
block2.hash = BlockHash.from(hash2)
|
||||||
|
|
||||||
|
blocks.add(block1)
|
||||||
|
heights.add(block1)
|
||||||
|
blocks.add(block2)
|
||||||
|
heights.add(block2)
|
||||||
|
|
||||||
|
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = blocksByHeight.read(100).block()
|
||||||
|
then:
|
||||||
|
act == block1
|
||||||
|
|
||||||
|
when:
|
||||||
|
act = blocksByHeight.read(101).block()
|
||||||
|
then:
|
||||||
|
act == block2
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Fetch last block if updated"() {
|
||||||
|
setup:
|
||||||
|
def blocks = new BlocksMemCache()
|
||||||
|
def heights = new HeightCache()
|
||||||
|
|
||||||
|
def block1 = new BlockJson<TransactionRefJson>()
|
||||||
|
block1.number = 100
|
||||||
|
block1.hash = BlockHash.from(hash1)
|
||||||
|
def block2 = new BlockJson<TransactionRefJson>()
|
||||||
|
block2.number = 100
|
||||||
|
block2.hash = BlockHash.from(hash2)
|
||||||
|
|
||||||
|
blocks.add(block1)
|
||||||
|
heights.add(block1)
|
||||||
|
blocks.add(block2)
|
||||||
|
heights.add(block2)
|
||||||
|
|
||||||
|
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = blocksByHeight.read(100).block()
|
||||||
|
then:
|
||||||
|
act == block2
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Fetch nothing if block expired"() {
|
||||||
|
setup:
|
||||||
|
def blocks = new BlocksMemCache()
|
||||||
|
def heights = new HeightCache()
|
||||||
|
|
||||||
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
|
block.number = 100
|
||||||
|
block.hash = BlockHash.from(hash1)
|
||||||
|
|
||||||
|
// add only to heights
|
||||||
|
heights.add(block)
|
||||||
|
|
||||||
|
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = blocksByHeight.read(100).block()
|
||||||
|
then:
|
||||||
|
act == null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Fetch nothing if height expired"() {
|
||||||
|
setup:
|
||||||
|
def blocks = new BlocksMemCache()
|
||||||
|
def heights = new HeightCache()
|
||||||
|
|
||||||
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
|
block.number = 100
|
||||||
|
block.hash = BlockHash.from(hash1)
|
||||||
|
|
||||||
|
// add only to blocks
|
||||||
|
blocks.add(block)
|
||||||
|
|
||||||
|
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = blocksByHeight.read(100).block()
|
||||||
|
then:
|
||||||
|
act == null
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Fetch nothing if both empty"() {
|
||||||
|
setup:
|
||||||
|
def blocks = new BlocksMemCache()
|
||||||
|
def heights = new HeightCache()
|
||||||
|
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||||
|
when:
|
||||||
|
def act = blocksByHeight.read(100).block()
|
||||||
|
then:
|
||||||
|
act == null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.cache
|
|||||||
import io.infinitape.etherjar.domain.BlockHash
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
import io.infinitape.etherjar.domain.TransactionId
|
import io.infinitape.etherjar.domain.TransactionId
|
||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
class BlocksMemCacheSpec extends Specification {
|
class BlocksMemCacheSpec extends Specification {
|
||||||
@@ -30,13 +31,13 @@ class BlocksMemCacheSpec extends Specification {
|
|||||||
def "Add and read"() {
|
def "Add and read"() {
|
||||||
setup:
|
setup:
|
||||||
def cache = new BlocksMemCache()
|
def cache = new BlocksMemCache()
|
||||||
def block = new BlockJson<TransactionId>()
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
block.number = 100
|
block.number = 100
|
||||||
block.hash = BlockHash.from(hash1)
|
block.hash = BlockHash.from(hash1)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
cache.add(block)
|
cache.add(block)
|
||||||
def act = cache.get(BlockHash.from(hash1)).block()
|
def act = cache.read(BlockHash.from(hash1)).block()
|
||||||
then:
|
then:
|
||||||
act == block
|
act == block
|
||||||
}
|
}
|
||||||
@@ -48,20 +49,21 @@ class BlocksMemCacheSpec extends Specification {
|
|||||||
|
|
||||||
when:
|
when:
|
||||||
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||||
def block = new BlockJson<TransactionId>()
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
block.number = 100 + i
|
block.number = 100 + i
|
||||||
block.hash = BlockHash.from(hash)
|
block.hash = BlockHash.from(hash)
|
||||||
cache.add(block)
|
cache.add(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
def act1 = cache.get(BlockHash.from(hash1)).block()
|
def act1 = cache.read(BlockHash.from(hash1)).block()
|
||||||
def act2 = cache.get(BlockHash.from(hash2)).block()
|
def act2 = cache.read(BlockHash.from(hash2)).block()
|
||||||
def act3 = cache.get(BlockHash.from(hash3)).block()
|
def act3 = cache.read(BlockHash.from(hash3)).block()
|
||||||
def act4 = cache.get(BlockHash.from(hash4)).block()
|
def act4 = cache.read(BlockHash.from(hash4)).block()
|
||||||
then:
|
then:
|
||||||
act2.hash.toHex() == hash2
|
act2.hash.toHex() == hash2
|
||||||
act3.hash.toHex() == hash3
|
act3.hash.toHex() == hash3
|
||||||
act4.hash.toHex() == hash4
|
act4.hash.toHex() == hash4
|
||||||
act1 == null
|
act1 == null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/test/groovy/io/emeraldpay/dshackle/cache/HeightCacheSpec.groovy
vendored
Normal file
61
src/test/groovy/io/emeraldpay/dshackle/cache/HeightCacheSpec.groovy
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package io.emeraldpay.dshackle.cache
|
||||||
|
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
class HeightCacheSpec extends Specification {
|
||||||
|
|
||||||
|
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||||
|
String hash2 = "0x4aabdaff9acd2f30d15e00ab5dfd5f6c56ba4ea1c968a7ff8d3f34de70153b33"
|
||||||
|
String hash3 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||||
|
String hash4 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||||
|
|
||||||
|
def "Add and read"() {
|
||||||
|
setup:
|
||||||
|
def cache = new HeightCache()
|
||||||
|
|
||||||
|
when:
|
||||||
|
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||||
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
|
block.number = 100 + i
|
||||||
|
block.hash = BlockHash.from(hash)
|
||||||
|
cache.add(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
def act1 = cache.read(100).block()
|
||||||
|
def act2 = cache.read(101).block()
|
||||||
|
def act3 = cache.read(102).block()
|
||||||
|
def act4 = cache.read(103).block()
|
||||||
|
then:
|
||||||
|
act1.toHex() == hash1
|
||||||
|
act2.toHex() == hash2
|
||||||
|
act3.toHex() == hash3
|
||||||
|
act4.toHex() == hash4
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Keeps only configured amount"() {
|
||||||
|
setup:
|
||||||
|
def cache = new HeightCache(3)
|
||||||
|
[hash1]
|
||||||
|
|
||||||
|
when:
|
||||||
|
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||||
|
def block = new BlockJson<TransactionRefJson>()
|
||||||
|
block.number = 100 + i
|
||||||
|
block.hash = BlockHash.from(hash)
|
||||||
|
cache.add(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
def act1 = cache.read(100).block()
|
||||||
|
def act2 = cache.read(101).block()
|
||||||
|
def act3 = cache.read(102).block()
|
||||||
|
def act4 = cache.read(103).block()
|
||||||
|
then:
|
||||||
|
act1 == null
|
||||||
|
act2.toHex() == hash2
|
||||||
|
act3.toHex() == hash3
|
||||||
|
act4.toHex() == hash4
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
package io.emeraldpay.dshackle.upstream
|
package io.emeraldpay.dshackle.upstream
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.cache.BlockByHeight
|
||||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||||
import io.emeraldpay.dshackle.reader.BlockCacheReader
|
import io.emeraldpay.dshackle.cache.HeightCache
|
||||||
import io.emeraldpay.dshackle.reader.EmptyReader
|
import io.emeraldpay.dshackle.reader.EmptyReader
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||||
@@ -23,6 +24,7 @@ class CachingEthereumApiSpec extends Specification {
|
|||||||
def api = new CachingEthereumApi(
|
def api = new CachingEthereumApi(
|
||||||
TestingCommons.objectMapper(),
|
TestingCommons.objectMapper(),
|
||||||
new EmptyReader<BlockHash, BlockJson<TransactionRefJson>>(),
|
new EmptyReader<BlockHash, BlockJson<TransactionRefJson>>(),
|
||||||
|
new EmptyReader<>(),
|
||||||
head
|
head
|
||||||
)
|
)
|
||||||
1 * head.getFlux() >> Flux.just(new BlockJson<TransactionRefJson>(number: 100))
|
1 * head.getFlux() >> Flux.just(new BlockJson<TransactionRefJson>(number: 100))
|
||||||
@@ -42,6 +44,7 @@ class CachingEthereumApiSpec extends Specification {
|
|||||||
def api = new CachingEthereumApi(
|
def api = new CachingEthereumApi(
|
||||||
TestingCommons.objectMapper(),
|
TestingCommons.objectMapper(),
|
||||||
new EmptyReader<BlockHash, BlockJson<TransactionRefJson>>(),
|
new EmptyReader<BlockHash, BlockJson<TransactionRefJson>>(),
|
||||||
|
new EmptyReader<>(),
|
||||||
head
|
head
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
@@ -53,13 +56,14 @@ class CachingEthereumApiSpec extends Specification {
|
|||||||
.verify(Duration.ofSeconds(3))
|
.verify(Duration.ofSeconds(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Return block when cached"() {
|
def "Return block by hash when cached"() {
|
||||||
setup:
|
setup:
|
||||||
def cache = new BlocksMemCache();
|
def cache = new BlocksMemCache();
|
||||||
def head = Mock(EthereumHead.class)
|
def head = Mock(EthereumHead.class)
|
||||||
def api = new CachingEthereumApi(
|
def api = new CachingEthereumApi(
|
||||||
TestingCommons.objectMapper(),
|
TestingCommons.objectMapper(),
|
||||||
new BlockCacheReader(cache),
|
cache,
|
||||||
|
new EmptyReader<>(),
|
||||||
head
|
head
|
||||||
)
|
)
|
||||||
cache.add(new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")))
|
cache.add(new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")))
|
||||||
@@ -74,4 +78,29 @@ class CachingEthereumApiSpec extends Specification {
|
|||||||
.verify(Duration.ofSeconds(3))
|
.verify(Duration.ofSeconds(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "Return block by height when cached"() {
|
||||||
|
setup:
|
||||||
|
def blocksCache = new BlocksMemCache()
|
||||||
|
def heightCache = new HeightCache()
|
||||||
|
def head = Mock(EthereumHead.class)
|
||||||
|
def api = new CachingEthereumApi(
|
||||||
|
TestingCommons.objectMapper(),
|
||||||
|
blocksCache,
|
||||||
|
new BlockByHeight(heightCache, blocksCache),
|
||||||
|
head
|
||||||
|
)
|
||||||
|
def block = new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||||
|
heightCache.add(block)
|
||||||
|
blocksCache.add(block)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = api.execute(1, "eth_getBlockByNumber", ["0x64", false]).map { new String(it)}
|
||||||
|
|
||||||
|
then:
|
||||||
|
StepVerifier.create(act)
|
||||||
|
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","transactions":[],"uncles":[]}}')
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(3))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user