problem: quorum is not applied for internal reads
This commit is contained in:
@@ -20,7 +20,7 @@ import io.emeraldpay.dshackle.upstream.ApiSource
|
|||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
|
|
||||||
// creates instance of a Quorum based reader
|
// creates instance of a Quorum based reader
|
||||||
interface QuorumReaderFactory {
|
open interface QuorumReaderFactory {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun default(): QuorumReaderFactory {
|
fun default(): QuorumReaderFactory {
|
||||||
@@ -28,7 +28,7 @@ interface QuorumReaderFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result>
|
open fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result>
|
||||||
|
|
||||||
class Default : QuorumReaderFactory {
|
class Default : QuorumReaderFactory {
|
||||||
override fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result> {
|
override fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result> {
|
||||||
|
|||||||
@@ -16,9 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream
|
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 org.reactivestreams.Publisher
|
import org.reactivestreams.Publisher
|
||||||
|
|
||||||
interface ApiSource : Publisher<Upstream> {
|
interface ApiSource : Publisher<Upstream> {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import io.emeraldpay.dshackle.BlockchainType
|
|||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.cache.CachesEnabled
|
import io.emeraldpay.dshackle.cache.CachesEnabled
|
||||||
import io.emeraldpay.dshackle.cache.CachesFactory
|
import io.emeraldpay.dshackle.cache.CachesFactory
|
||||||
|
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChange
|
import io.emeraldpay.dshackle.startup.UpstreamChange
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
|||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import org.apache.commons.collections4.Factory
|
||||||
|
import org.apache.commons.collections4.FunctorException
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
import reactor.core.Disposable
|
import reactor.core.Disposable
|
||||||
@@ -52,6 +54,9 @@ abstract class Multistream(
|
|||||||
private var cacheSubscription: Disposable? = null
|
private var cacheSubscription: Disposable? = null
|
||||||
private val reconfigLock = ReentrantLock()
|
private val reconfigLock = ReentrantLock()
|
||||||
private var callMethods: CallMethods? = null
|
private var callMethods: CallMethods? = null
|
||||||
|
private var callMethodsFactory: Factory<CallMethods> = Factory {
|
||||||
|
return@Factory callMethods ?: throw FunctorException("Not initialized yet")
|
||||||
|
}
|
||||||
private var seq = 0
|
private var seq = 0
|
||||||
protected var lagObserver: HeadLagObserver? = null
|
protected var lagObserver: HeadLagObserver? = null
|
||||||
private var subscription: Disposable? = null
|
private var subscription: Disposable? = null
|
||||||
@@ -86,7 +91,7 @@ abstract class Multistream(
|
|||||||
/**
|
/**
|
||||||
* Get a source for direct APIs
|
* Get a source for direct APIs
|
||||||
*/
|
*/
|
||||||
fun getApiSource(matcher: Selector.Matcher): ApiSource {
|
open fun getApiSource(matcher: Selector.Matcher): ApiSource {
|
||||||
val i = seq++
|
val i = seq++
|
||||||
if (seq >= Int.MAX_VALUE / 2) {
|
if (seq >= Int.MAX_VALUE / 2) {
|
||||||
seq = 0
|
seq = 0
|
||||||
@@ -117,6 +122,7 @@ abstract class Multistream(
|
|||||||
fun onUpstreamsUpdated() {
|
fun onUpstreamsUpdated() {
|
||||||
reconfigLock.withLock {
|
reconfigLock.withLock {
|
||||||
getAll().map { it.getMethods() }.let {
|
getAll().map { it.getMethods() }.let {
|
||||||
|
//TODO made list of uniq instances, and then if only one, just use it directly
|
||||||
callMethods = AggregatedCallMethods(it)
|
callMethods = AggregatedCallMethods(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +153,10 @@ abstract class Multistream(
|
|||||||
return callMethods ?: throw IllegalStateException("Methods are not initialized yet")
|
return callMethods ?: throw IllegalStateException("Methods are not initialized yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMethodsFactory(): Factory<CallMethods> {
|
||||||
|
return callMethodsFactory
|
||||||
|
}
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
subscription = observeStatus()
|
subscription = observeStatus()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.emeraldpay.dshackle.Defaults
|
||||||
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
|
import io.emeraldpay.dshackle.cache.CurrentBlockCache
|
||||||
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.data.TxContainer
|
||||||
|
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
|
import io.infinitape.etherjar.domain.Address
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.domain.TransactionId
|
||||||
|
import io.infinitape.etherjar.domain.Wei
|
||||||
|
import io.infinitape.etherjar.hex.HexQuantity
|
||||||
|
import io.infinitape.etherjar.rpc.RpcException
|
||||||
|
import io.infinitape.etherjar.rpc.RpcResponseError
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import org.apache.commons.collections4.Factory
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.util.retry.Retry
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common reads from upstream, makes actual calls with applying quorum and retries
|
||||||
|
*/
|
||||||
|
class EthereumDirectReader(
|
||||||
|
private val up: Multistream,
|
||||||
|
private val caches: Caches,
|
||||||
|
private val balanceCache: CurrentBlockCache<Address, Wei>,
|
||||||
|
private val callMethodsFactory: Factory<CallMethods>
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(EthereumDirectReader::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val objectMapper: ObjectMapper = Global.objectMapper
|
||||||
|
open var quorumReaderFactory: QuorumReaderFactory = QuorumReaderFactory.default()
|
||||||
|
|
||||||
|
val blockReader: Reader<BlockHash, BlockContainer>
|
||||||
|
val blockByHeightReader: Reader<Long, BlockContainer>
|
||||||
|
val txReader: Reader<TransactionId, TxContainer>
|
||||||
|
val balanceReader: Reader<Address, Wei>
|
||||||
|
|
||||||
|
init {
|
||||||
|
blockReader = object : Reader<BlockHash, BlockContainer> {
|
||||||
|
override fun read(key: BlockHash): Mono<BlockContainer> {
|
||||||
|
val request = JsonRpcRequest("eth_getBlockByHash", listOf(key.toHex(), false))
|
||||||
|
return readBlock(request, key.toHex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blockByHeightReader = object : Reader<Long, BlockContainer> {
|
||||||
|
override fun read(key: Long): Mono<BlockContainer> {
|
||||||
|
val request = JsonRpcRequest("eth_getBlockByNumber", listOf(HexQuantity.from(key).toHex(), false))
|
||||||
|
return readBlock(request, key.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txReader = object : Reader<TransactionId, TxContainer> {
|
||||||
|
override fun read(key: TransactionId): Mono<TxContainer> {
|
||||||
|
val request = JsonRpcRequest("eth_getTransactionByHash", listOf(key.toHex()))
|
||||||
|
return readWithQuorum(request)
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Tx not read $key")))
|
||||||
|
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
||||||
|
.flatMap { txbytes ->
|
||||||
|
val tx = objectMapper.readValue(txbytes, TransactionJson::class.java)
|
||||||
|
if (tx == null) {
|
||||||
|
Mono.empty()
|
||||||
|
} else {
|
||||||
|
Mono.just(TxContainer.from(tx, txbytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.doOnNext { tx ->
|
||||||
|
if (tx.blockId != null) {
|
||||||
|
caches.cache(Caches.Tag.REQUESTED, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
balanceReader = object : Reader<Address, Wei> {
|
||||||
|
override fun read(key: Address): Mono<Wei> {
|
||||||
|
val request = JsonRpcRequest("eth_getBalance", listOf(key.toHex(), "latest"))
|
||||||
|
return readWithQuorum(request)
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key")))
|
||||||
|
.map {
|
||||||
|
val str = String(it)
|
||||||
|
// it's a json string, i.e. wrapped with quotes, ex. _"0x1234"_
|
||||||
|
if (str.startsWith("\"") && str.endsWith("\"")) {
|
||||||
|
Wei.from(str.substring(1, str.length - 1))
|
||||||
|
} else {
|
||||||
|
throw RpcException(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Not Wei value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
||||||
|
.doOnNext { value ->
|
||||||
|
balanceCache.put(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readBlock(request: JsonRpcRequest, id: String): Mono<BlockContainer> {
|
||||||
|
return readWithQuorum(request)
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $id")))
|
||||||
|
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
||||||
|
.map { blockbytes ->
|
||||||
|
val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson<TransactionRefJson>
|
||||||
|
BlockContainer.from(block, blockbytes)
|
||||||
|
}
|
||||||
|
.doOnNext { block ->
|
||||||
|
caches.cache(Caches.Tag.REQUESTED, block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from an Upstream applying a Quorum specific for that request
|
||||||
|
*/
|
||||||
|
private fun readWithQuorum(request: JsonRpcRequest): Mono<ByteArray> {
|
||||||
|
return quorumReaderFactory
|
||||||
|
.create(up.getApiSource(Selector.empty), callMethodsFactory.create().getQuorumFor(request.method))
|
||||||
|
.read(request)
|
||||||
|
.map { it.value }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import io.emeraldpay.dshackle.Global
|
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
@@ -39,10 +37,9 @@ open class EthereumMultistream(
|
|||||||
private val log = LoggerFactory.getLogger(EthereumMultistream::class.java)
|
private val log = LoggerFactory.getLogger(EthereumMultistream::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val objectMapper: ObjectMapper = Global.objectMapper
|
|
||||||
private var head: Head? = null
|
private var head: Head? = null
|
||||||
|
|
||||||
private val reader: EthereumReader = EthereumReader(this, this.caches)
|
private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.init()
|
this.init()
|
||||||
|
|||||||
@@ -16,38 +16,32 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import io.emeraldpay.dshackle.Defaults
|
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.cache.CurrentBlockCache
|
import io.emeraldpay.dshackle.cache.CurrentBlockCache
|
||||||
import io.emeraldpay.dshackle.data.*
|
import io.emeraldpay.dshackle.data.*
|
||||||
import io.emeraldpay.dshackle.reader.*
|
import io.emeraldpay.dshackle.reader.*
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
|
||||||
import io.infinitape.etherjar.domain.Address
|
import io.infinitape.etherjar.domain.Address
|
||||||
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.domain.Wei
|
import io.infinitape.etherjar.domain.Wei
|
||||||
import io.infinitape.etherjar.hex.HexQuantity
|
|
||||||
import io.infinitape.etherjar.rpc.RpcException
|
|
||||||
import io.infinitape.etherjar.rpc.RpcResponseError
|
|
||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
import io.infinitape.etherjar.rpc.json.TransactionJson
|
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import org.apache.commons.collections4.Factory
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
import reactor.core.Disposable
|
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.util.retry.Retry
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader for the common operations, that wraps caches + native call with quorum verification
|
||||||
|
*/
|
||||||
open class EthereumReader(
|
open class EthereumReader(
|
||||||
private val up: Multistream,
|
private val up: Multistream,
|
||||||
private val caches: Caches
|
private val caches: Caches,
|
||||||
|
private val callMethodsFactory: Factory<CallMethods>
|
||||||
) : Lifecycle {
|
) : Lifecycle {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -56,6 +50,7 @@ open class EthereumReader(
|
|||||||
|
|
||||||
private val objectMapper: ObjectMapper = Global.objectMapper
|
private val objectMapper: ObjectMapper = Global.objectMapper
|
||||||
private val balanceCache = CurrentBlockCache<Address, Wei>()
|
private val balanceCache = CurrentBlockCache<Address, Wei>()
|
||||||
|
private val directReader = EthereumDirectReader(up, caches, balanceCache, callMethodsFactory)
|
||||||
|
|
||||||
val extractBlock = Function<BlockContainer, BlockJson<TransactionRefJson>> { block ->
|
val extractBlock = Function<BlockContainer, BlockJson<TransactionRefJson>> { block ->
|
||||||
val existing = block.getParsed(BlockJson::class.java)
|
val existing = block.getParsed(BlockJson::class.java)
|
||||||
@@ -87,115 +82,17 @@ open class EthereumReader(
|
|||||||
TxContainer.from(tx)
|
TxContainer.from(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val blocksDirect: Reader<BlockHash, BlockContainer>
|
|
||||||
private val blocksByHeightDirect: Reader<Long, BlockContainer>
|
|
||||||
private val txDirect: Reader<TransactionId, TxContainer>
|
|
||||||
private val balanceDirect: Reader<Address, Wei>
|
|
||||||
|
|
||||||
private val idToBlockHash = Function<BlockId, BlockHash> { id -> BlockHash.from(id.value) }
|
private val idToBlockHash = Function<BlockId, BlockHash> { id -> BlockHash.from(id.value) }
|
||||||
private val blockHashToId = Function<BlockHash, BlockId> { hash -> BlockId.from(hash) }
|
private val blockHashToId = Function<BlockHash, BlockId> { hash -> BlockId.from(hash) }
|
||||||
|
|
||||||
private val txHashToId = Function<TransactionId, TxId> { hash -> TxId.from(hash) }
|
private val txHashToId = Function<TransactionId, TxId> { hash -> TxId.from(hash) }
|
||||||
private val idToTxHash = Function<TxId, TransactionId> { id -> TransactionId.from(id.value) }
|
private val idToTxHash = Function<TxId, TransactionId> { id -> TransactionId.from(id.value) }
|
||||||
|
|
||||||
private val directResponseBytes = Function<JsonRpcResponse, ByteArray> { resp ->
|
|
||||||
if (resp.error != null) {
|
|
||||||
throw resp.error.asException()
|
|
||||||
} else {
|
|
||||||
resp.getResult()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
blocksDirect = object : Reader<BlockHash, BlockContainer> {
|
|
||||||
override fun read(key: BlockHash): Mono<BlockContainer> {
|
|
||||||
return up.getDirectApi(Selector.empty).flatMap { api ->
|
|
||||||
val request = JsonRpcRequest("eth_getBlockByHash", listOf(key.toHex(), false))
|
|
||||||
api.read(request)
|
|
||||||
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $key")))
|
|
||||||
.map(directResponseBytes)
|
|
||||||
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
|
||||||
.map { blockbytes ->
|
|
||||||
val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson<TransactionRefJson>
|
|
||||||
BlockContainer.from(block, blockbytes)
|
|
||||||
}
|
|
||||||
.doOnNext { block ->
|
|
||||||
caches.cache(Caches.Tag.REQUESTED, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
blocksByHeightDirect = object : Reader<Long, BlockContainer> {
|
|
||||||
override fun read(key: Long): Mono<BlockContainer> {
|
|
||||||
return up.getDirectApi(Selector.empty).flatMap { api ->
|
|
||||||
val request = JsonRpcRequest("eth_getBlockByNumber", listOf(HexQuantity.from(key).toHex(), false))
|
|
||||||
api.read(request)
|
|
||||||
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $key")))
|
|
||||||
.map(directResponseBytes)
|
|
||||||
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
|
||||||
.map { blockbytes ->
|
|
||||||
val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson<TransactionRefJson>
|
|
||||||
BlockContainer.from(block, blockbytes)
|
|
||||||
}
|
|
||||||
.doOnNext { block ->
|
|
||||||
caches.cache(Caches.Tag.REQUESTED, block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
txDirect = object : Reader<TransactionId, TxContainer> {
|
|
||||||
override fun read(key: TransactionId): Mono<TxContainer> {
|
|
||||||
return up.getDirectApi(Selector.empty).flatMap { api ->
|
|
||||||
val request = JsonRpcRequest("eth_getTransactionByHash", listOf(key.toHex()))
|
|
||||||
api.read(request)
|
|
||||||
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Tx not read $key")))
|
|
||||||
.map(directResponseBytes)
|
|
||||||
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
|
||||||
.flatMap { txbytes ->
|
|
||||||
val tx = objectMapper.readValue(txbytes, TransactionJson::class.java)
|
|
||||||
if (tx == null) {
|
|
||||||
Mono.empty()
|
|
||||||
} else {
|
|
||||||
Mono.just(TxContainer.from(tx, txbytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.doOnNext { tx ->
|
|
||||||
if (tx.blockId != null) {
|
|
||||||
caches.cache(Caches.Tag.REQUESTED, tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
balanceDirect = object : Reader<Address, Wei> {
|
|
||||||
override fun read(key: Address): Mono<Wei> {
|
|
||||||
return up.getDirectApi(Selector.empty).flatMap { api ->
|
|
||||||
val request = JsonRpcRequest("eth_getBalance", listOf(key.toHex(), "latest"))
|
|
||||||
api.read(request)
|
|
||||||
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key")))
|
|
||||||
.map(directResponseBytes)
|
|
||||||
.map {
|
|
||||||
val str = String(it)
|
|
||||||
if (str.startsWith("\"") && str.endsWith("\"")) {
|
|
||||||
Wei.from(str.substring(1, str.length - 1))
|
|
||||||
} else {
|
|
||||||
throw RpcException(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Not Wei value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
|
||||||
.doOnNext { value ->
|
|
||||||
balanceCache.put(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun blocksByHash(): Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
fun blocksByHash(): Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
||||||
return TransformingReader(
|
return TransformingReader(
|
||||||
CompoundReader(
|
CompoundReader(
|
||||||
RekeyingReader(blockHashToId, caches.getBlocksByHash()),
|
RekeyingReader(blockHashToId, caches.getBlocksByHash()),
|
||||||
blocksDirect
|
directReader.blockReader
|
||||||
),
|
),
|
||||||
extractBlock
|
extractBlock
|
||||||
)
|
)
|
||||||
@@ -205,7 +102,7 @@ open class EthereumReader(
|
|||||||
return TransformingReader(
|
return TransformingReader(
|
||||||
CompoundReader(
|
CompoundReader(
|
||||||
caches.getBlocksByHash(),
|
caches.getBlocksByHash(),
|
||||||
RekeyingReader(idToBlockHash, blocksDirect)
|
RekeyingReader(idToBlockHash, directReader.blockReader)
|
||||||
),
|
),
|
||||||
extractBlock
|
extractBlock
|
||||||
)
|
)
|
||||||
@@ -228,7 +125,7 @@ open class EthereumReader(
|
|||||||
fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
|
fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
|
||||||
return CompoundReader(
|
return CompoundReader(
|
||||||
caches.getBlocksByHeight(),
|
caches.getBlocksByHeight(),
|
||||||
blocksByHeightDirect
|
directReader.blockByHeightReader
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +133,7 @@ open class EthereumReader(
|
|||||||
return TransformingReader(
|
return TransformingReader(
|
||||||
CompoundReader(
|
CompoundReader(
|
||||||
RekeyingReader(txHashToId, caches.getTxByHash()),
|
RekeyingReader(txHashToId, caches.getTxByHash()),
|
||||||
txDirect
|
directReader.txReader
|
||||||
),
|
),
|
||||||
extractTx
|
extractTx
|
||||||
)
|
)
|
||||||
@@ -245,13 +142,13 @@ open class EthereumReader(
|
|||||||
fun txByHashAsCont(): Reader<TxId, TxContainer> {
|
fun txByHashAsCont(): Reader<TxId, TxContainer> {
|
||||||
return CompoundReader(
|
return CompoundReader(
|
||||||
caches.getTxByHash(),
|
caches.getTxByHash(),
|
||||||
RekeyingReader(idToTxHash, txDirect)
|
RekeyingReader(idToTxHash, directReader.txReader)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun balance(): Reader<Address, Wei> {
|
fun balance(): Reader<Address, Wei> {
|
||||||
return CompoundReader(
|
return CompoundReader(
|
||||||
balanceCache, balanceDirect
|
balanceCache, directReader.balanceReader
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
|
import io.emeraldpay.dshackle.cache.CurrentBlockCache
|
||||||
|
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
|
||||||
|
import io.emeraldpay.dshackle.quorum.QuorumRpcReader
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
|
import io.emeraldpay.dshackle.upstream.ApiSource
|
||||||
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import io.infinitape.etherjar.domain.Address
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.domain.TransactionId
|
||||||
|
import io.infinitape.etherjar.domain.Wei
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||||
|
import org.apache.commons.collections4.Factory
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.test.StepVerifier
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class EthereumDirectReaderSpec extends Specification {
|
||||||
|
|
||||||
|
String hash1 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||||
|
String address1 = "0xe0aadb0a012dbcdc529c4c743d3e0385a0b54d3d"
|
||||||
|
|
||||||
|
def "Reads block by hash"() {
|
||||||
|
setup:
|
||||||
|
def json = new BlockJson().tap {
|
||||||
|
number = 100
|
||||||
|
hash = BlockHash.from(hash1)
|
||||||
|
timestamp = Instant.now()
|
||||||
|
totalDifficulty = BigInteger.ONE
|
||||||
|
transactions = []
|
||||||
|
}
|
||||||
|
def up = Mock(Multistream) {
|
||||||
|
1 * getApiSource(_) >> Stub(ApiSource)
|
||||||
|
}
|
||||||
|
def calls = Mock(Factory) {
|
||||||
|
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
}
|
||||||
|
EthereumDirectReader reader = new EthereumDirectReader(
|
||||||
|
up, Caches.default(), new CurrentBlockCache(), calls
|
||||||
|
)
|
||||||
|
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
|
||||||
|
1 * create(_, _) >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
|
||||||
|
new QuorumRpcReader.Result(
|
||||||
|
Global.objectMapper.writeValueAsBytes(json), 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when:
|
||||||
|
def act = reader.blockReader.read(BlockHash.from(hash1))
|
||||||
|
then:
|
||||||
|
StepVerifier.create(act)
|
||||||
|
.expectNextMatches { block ->
|
||||||
|
block.hash.toHexWithPrefix() == hash1
|
||||||
|
}
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Reads block by height"() {
|
||||||
|
setup:
|
||||||
|
def json = new BlockJson().tap {
|
||||||
|
number = 100
|
||||||
|
hash = BlockHash.from(hash1)
|
||||||
|
timestamp = Instant.now()
|
||||||
|
totalDifficulty = BigInteger.ONE
|
||||||
|
transactions = []
|
||||||
|
}
|
||||||
|
def up = Mock(Multistream) {
|
||||||
|
1 * getApiSource(_) >> Stub(ApiSource)
|
||||||
|
}
|
||||||
|
def calls = Mock(Factory) {
|
||||||
|
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
}
|
||||||
|
EthereumDirectReader reader = new EthereumDirectReader(
|
||||||
|
up, Caches.default(), new CurrentBlockCache(), calls
|
||||||
|
)
|
||||||
|
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
|
||||||
|
1 * create(_, _) >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just(
|
||||||
|
new QuorumRpcReader.Result(
|
||||||
|
Global.objectMapper.writeValueAsBytes(json), 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when:
|
||||||
|
def act = reader.blockByHeightReader.read(100)
|
||||||
|
then:
|
||||||
|
StepVerifier.create(act)
|
||||||
|
.expectNextMatches { block ->
|
||||||
|
block.hash.toHexWithPrefix() == hash1
|
||||||
|
}
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Reads tx"() {
|
||||||
|
setup:
|
||||||
|
def json = new TransactionJson().tap {
|
||||||
|
hash = TransactionId.from(hash1)
|
||||||
|
blockNumber = 100
|
||||||
|
blockHash = BlockHash.from(hash1)
|
||||||
|
}
|
||||||
|
def up = Mock(Multistream) {
|
||||||
|
1 * getApiSource(_) >> Stub(ApiSource)
|
||||||
|
}
|
||||||
|
def calls = Mock(Factory) {
|
||||||
|
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
}
|
||||||
|
EthereumDirectReader reader = new EthereumDirectReader(
|
||||||
|
up, Caches.default(), new CurrentBlockCache(), calls
|
||||||
|
)
|
||||||
|
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
|
||||||
|
1 * create(_, _) >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
|
||||||
|
new QuorumRpcReader.Result(
|
||||||
|
Global.objectMapper.writeValueAsBytes(json), 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when:
|
||||||
|
def act = reader.txReader.read(TransactionId.from(hash1))
|
||||||
|
then:
|
||||||
|
StepVerifier.create(act)
|
||||||
|
.expectNextMatches { block ->
|
||||||
|
block.hash.toHexWithPrefix() == hash1
|
||||||
|
}
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Reads balance"() {
|
||||||
|
setup:
|
||||||
|
def up = Mock(Multistream) {
|
||||||
|
1 * getApiSource(_) >> Stub(ApiSource)
|
||||||
|
}
|
||||||
|
def calls = Mock(Factory) {
|
||||||
|
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
}
|
||||||
|
EthereumDirectReader reader = new EthereumDirectReader(
|
||||||
|
up, Caches.default(), new CurrentBlockCache(), calls
|
||||||
|
)
|
||||||
|
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
|
||||||
|
1 * create(_, _) >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just(
|
||||||
|
new QuorumRpcReader.Result(
|
||||||
|
Global.objectMapper.writeValueAsBytes("0x100"), 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when:
|
||||||
|
def act = reader.balanceReader.read(Address.from(address1))
|
||||||
|
then:
|
||||||
|
StepVerifier.create(act)
|
||||||
|
.expectNext(Wei.from("0x100"))
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,6 +24,8 @@ import io.emeraldpay.dshackle.data.TxContainer
|
|||||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import io.infinitape.etherjar.domain.Address
|
import io.infinitape.etherjar.domain.Address
|
||||||
import io.infinitape.etherjar.domain.BlockHash
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
@@ -32,6 +34,8 @@ import io.infinitape.etherjar.domain.Wei
|
|||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
import io.infinitape.etherjar.rpc.json.TransactionJson
|
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import org.apache.commons.collections4.Factory
|
||||||
|
import org.apache.commons.collections4.functors.ConstantFactory
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
@@ -54,6 +58,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
json.blockHash = blockJson.hash
|
json.blockHash = blockJson.hash
|
||||||
json.blockNumber = blockJson.number
|
json.blockNumber = blockJson.number
|
||||||
}
|
}
|
||||||
|
Factory<CallMethods> calls = ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
|
||||||
|
|
||||||
def "Block by Id reads from cache"() {
|
def "Block by Id reads from cache"() {
|
||||||
setup:
|
setup:
|
||||||
@@ -63,7 +68,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
def caches = Caches.newBuilder()
|
def caches = Caches.newBuilder()
|
||||||
.setBlockByHash(memCache)
|
.setBlockByHash(memCache)
|
||||||
.build()
|
.build()
|
||||||
def reader = new EthereumReader(Stub(Multistream), caches)
|
def reader = new EthereumReader(Stub(Multistream), caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.blocksById().read(blockId).block()
|
def act = reader.blocksById().read(blockId).block()
|
||||||
@@ -84,7 +89,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||||
|
|
||||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
def reader = new EthereumReader(upstream, caches)
|
def reader = new EthereumReader(upstream, caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.blocksById().read(blockId).block()
|
def act = reader.blocksById().read(blockId).block()
|
||||||
@@ -105,7 +110,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||||
|
|
||||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
def reader = new EthereumReader(upstream, caches)
|
def reader = new EthereumReader(upstream, caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.blocksById().read(blockId).block()
|
def act = reader.blocksById().read(blockId).block()
|
||||||
@@ -122,7 +127,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
def caches = Caches.newBuilder()
|
def caches = Caches.newBuilder()
|
||||||
.setBlockByHash(memCache)
|
.setBlockByHash(memCache)
|
||||||
.build()
|
.build()
|
||||||
def reader = new EthereumReader(Stub(Multistream), caches)
|
def reader = new EthereumReader(Stub(Multistream), caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.blocksByHash().read(blockJson.hash).block()
|
def act = reader.blocksByHash().read(blockJson.hash).block()
|
||||||
@@ -142,7 +147,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
def api = TestingCommons.api()
|
def api = TestingCommons.api()
|
||||||
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
def reader = new EthereumReader(upstream, caches)
|
def reader = new EthereumReader(upstream, caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.blocksByHash().read(blockJson.hash).block()
|
def act = reader.blocksByHash().read(blockJson.hash).block()
|
||||||
@@ -159,7 +164,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
def caches = Caches.newBuilder()
|
def caches = Caches.newBuilder()
|
||||||
.setTxByHash(memCache)
|
.setTxByHash(memCache)
|
||||||
.build()
|
.build()
|
||||||
def reader = new EthereumReader(Stub(Multistream), caches)
|
def reader = new EthereumReader(Stub(Multistream), caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.txByHash().read(txJson.hash).block()
|
def act = reader.txByHash().read(txJson.hash).block()
|
||||||
@@ -180,7 +185,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
def api = TestingCommons.api()
|
def api = TestingCommons.api()
|
||||||
api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson)
|
api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson)
|
||||||
def upstream = TestingCommons.aggregatedUpstream(api)
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
def reader = new EthereumReader(upstream, caches)
|
def reader = new EthereumReader(upstream, caches, calls)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = reader.txByHash().read(txJson.hash).block()
|
def act = reader.txByHash().read(txJson.hash).block()
|
||||||
@@ -196,7 +201,7 @@ class EthereumReaderSpec extends Specification {
|
|||||||
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff")
|
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff")
|
||||||
EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
||||||
def upstreams = TestingCommons.aggregatedUpstream(upstream)
|
def upstreams = TestingCommons.aggregatedUpstream(upstream)
|
||||||
def reader = new EthereumReader(upstreams, Caches.default())
|
def reader = new EthereumReader(upstreams, Caches.default(), calls)
|
||||||
reader.start()
|
reader.start()
|
||||||
|
|
||||||
when:
|
when:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io.emeraldpay.dshackle.test.TestingCommons
|
|||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import org.apache.commons.collections4.functors.ConstantFactory
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -17,7 +18,8 @@ class NativeCallRouterSpec extends Specification {
|
|||||||
def router = new NativeCallRouter(
|
def router = new NativeCallRouter(
|
||||||
new EthereumReader(
|
new EthereumReader(
|
||||||
TestingCommons.aggregatedUpstream(TestingCommons.api()),
|
TestingCommons.aggregatedUpstream(TestingCommons.api()),
|
||||||
Caches.default()
|
Caches.default(),
|
||||||
|
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
|
||||||
),
|
),
|
||||||
methods
|
methods
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user