problem: different places to fetch from upstream
solution: single access reader for Ethereum upstreams
This commit is contained in:
24
src/main/kotlin/io/emeraldpay/dshackle/cache/CurrentBlockCache.kt
vendored
Normal file
24
src/main/kotlin/io/emeraldpay/dshackle/cache/CurrentBlockCache.kt
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package io.emeraldpay.dshackle.cache
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
|
class CurrentBlockCache<K, D> : Reader<K, D> {
|
||||||
|
|
||||||
|
private val cache = AtomicReference(ConcurrentHashMap<K, D>())
|
||||||
|
|
||||||
|
override fun read(key: K): Mono<D> {
|
||||||
|
return Mono.justOrEmpty(cache.get()[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
fun put(key: K, data: D) {
|
||||||
|
cache.get()[key] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun evict() {
|
||||||
|
cache.set(ConcurrentHashMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,8 +16,11 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.reader
|
package io.emeraldpay.dshackle.reader
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Defaults
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composition of multiple readers. Reader returns first value returned by any of the source readers.
|
* Composition of multiple readers. Reader returns first value returned by any of the source readers.
|
||||||
@@ -26,12 +29,21 @@ class CompoundReader<K, D>(
|
|||||||
private vararg val readers: Reader<K, D>
|
private vararg val readers: Reader<K, D>
|
||||||
): Reader<K, D> {
|
): Reader<K, D> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(CompoundReader::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
override fun read(key: K): Mono<D> {
|
override fun read(key: K): Mono<D> {
|
||||||
if (readers.isEmpty()) {
|
if (readers.isEmpty()) {
|
||||||
return Mono.empty()
|
return Mono.empty()
|
||||||
}
|
}
|
||||||
return Flux.fromIterable(readers.asIterable())
|
return Flux.fromIterable(readers.asIterable())
|
||||||
.flatMap { it.read(key) }.next()
|
.flatMap { rdr ->
|
||||||
|
rdr.read(key)
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.empty())
|
||||||
|
.doOnError { t -> log.warn("Failed to read from $rdr", t) }
|
||||||
|
.onErrorResume { Mono.empty() }
|
||||||
|
}.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 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.reader
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader wrapper that maps the input key from ne value to another (ex. convert from Long to String)
|
||||||
|
*/
|
||||||
|
class RekeyingReader<K, K1, D>(
|
||||||
|
/**
|
||||||
|
* Mapping between original Key and Key supported by the reader
|
||||||
|
*/
|
||||||
|
private val rekey: Function<K, K1>,
|
||||||
|
/**
|
||||||
|
* Actual reader
|
||||||
|
*/
|
||||||
|
private val reader: Reader<K1, D>
|
||||||
|
) : Reader<K, D> {
|
||||||
|
|
||||||
|
override fun read(key: K): Mono<D> {
|
||||||
|
return Mono.just(key)
|
||||||
|
.map(rekey)
|
||||||
|
.flatMap {
|
||||||
|
reader.read(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 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.reader
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader wrapper that transforms output of the reader to a different format
|
||||||
|
*/
|
||||||
|
class TransformingReader<K, D0, D>(
|
||||||
|
/**
|
||||||
|
* Actual reader
|
||||||
|
*/
|
||||||
|
private val reader: Reader<K, D0>,
|
||||||
|
/**
|
||||||
|
* Result transformation
|
||||||
|
*/
|
||||||
|
private val transformer: Function<in D0, out D>
|
||||||
|
) : Reader<K, D> {
|
||||||
|
|
||||||
|
override fun read(key: K): Mono<D> {
|
||||||
|
return reader.read(key).map(transformer)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -51,24 +51,39 @@ class BlockchainRpc(
|
|||||||
override fun subscribeTxStatus(request: Mono<BlockchainOuterClass.TxStatusRequest>): Flux<BlockchainOuterClass.TxStatus> {
|
override fun subscribeTxStatus(request: Mono<BlockchainOuterClass.TxStatusRequest>): Flux<BlockchainOuterClass.TxStatus> {
|
||||||
return request.flatMapMany { request ->
|
return request.flatMapMany { request ->
|
||||||
val chain = Chain.byId(request.chainValue)
|
val chain = Chain.byId(request.chainValue)
|
||||||
trackTx.find { it.isSupported(chain) }?.subscribe(request)
|
try {
|
||||||
?: Flux.error(SilentException.UnsupportedBlockchain(chain))
|
trackTx.find { it.isSupported(chain) }?.subscribe(request)
|
||||||
|
?: Flux.error(SilentException.UnsupportedBlockchain(chain))
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
log.error("Internal error during Tx Subscription", t)
|
||||||
|
Flux.error<BlockchainOuterClass.TxStatus>(IllegalStateException("Internal Error"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun subscribeBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
override fun subscribeBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
||||||
return requestMono.flatMapMany { request ->
|
return requestMono.flatMapMany { request ->
|
||||||
val chain = Chain.byId(request.asset.chainValue)
|
val chain = Chain.byId(request.asset.chainValue)
|
||||||
trackAddress.find { it.isSupported(chain) }?.subscribe(request)
|
try {
|
||||||
?: Flux.error(SilentException.UnsupportedBlockchain(chain))
|
trackAddress.find { it.isSupported(chain) }?.subscribe(request)
|
||||||
|
?: Flux.error(SilentException.UnsupportedBlockchain(chain))
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
log.error("Internal error during Balance Subscription", t)
|
||||||
|
Flux.error<BlockchainOuterClass.AddressBalance>(IllegalStateException("Internal Error"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
override fun getBalance(requestMono: Mono<BlockchainOuterClass.BalanceRequest>): Flux<BlockchainOuterClass.AddressBalance> {
|
||||||
return requestMono.flatMapMany { request ->
|
return requestMono.flatMapMany { request ->
|
||||||
val chain = Chain.byId(request.asset.chainValue)
|
val chain = Chain.byId(request.asset.chainValue)
|
||||||
trackAddress.find { it.isSupported(chain) }?.getBalance(request)
|
try {
|
||||||
?: Flux.error(SilentException.UnsupportedBlockchain(chain))
|
trackAddress.find { it.isSupported(chain) }?.getBalance(request)
|
||||||
|
?: Flux.error(SilentException.UnsupportedBlockchain(chain))
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
log.error("Internal error during Balance Request", t)
|
||||||
|
Flux.error<BlockchainOuterClass.AddressBalance>(IllegalStateException("Internal Error"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,6 @@ class TrackBitcoinTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadMempool(upstream: BitcoinUpstream, txid: String): Mono<TxStatus> {
|
fun loadMempool(upstream: BitcoinUpstream, txid: String): Mono<TxStatus> {
|
||||||
println("access: ${upstream.getData()}")
|
|
||||||
val mempool = upstream.getData().getMempool().get()
|
val mempool = upstream.getData().getMempool().get()
|
||||||
return mempool.map {
|
return mempool.map {
|
||||||
if (it.contains(txid)) {
|
if (it.contains(txid)) {
|
||||||
|
|||||||
@@ -21,15 +21,12 @@ import io.emeraldpay.api.proto.Common
|
|||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
import io.emeraldpay.dshackle.Defaults
|
import io.emeraldpay.dshackle.Defaults
|
||||||
import io.emeraldpay.dshackle.SilentException
|
import io.emeraldpay.dshackle.SilentException
|
||||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
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.Wei
|
import io.infinitape.etherjar.domain.Wei
|
||||||
import io.infinitape.etherjar.rpc.Commands
|
|
||||||
import io.infinitape.etherjar.rpc.json.BlockTag
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
@@ -90,6 +87,11 @@ class TrackEthereumAddress(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUpstream(chain: Chain): AggregatedEthereumUpstreams {
|
||||||
|
return upstreams.getUpstream(chain)?.cast(AggregatedEthereumUpstreams::class.java, EthereumApi::class.java)
|
||||||
|
?: throw SilentException.UnsupportedBlockchain(chain)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initAddress(request: BlockchainOuterClass.BalanceRequest): Flux<TrackedAddress> {
|
private fun initAddress(request: BlockchainOuterClass.BalanceRequest): Flux<TrackedAddress> {
|
||||||
val chain = Chain.byId(request.asset.chainValue)
|
val chain = Chain.byId(request.asset.chainValue)
|
||||||
if (!upstreams.isAvailable(chain)) {
|
if (!upstreams.isAvailable(chain)) {
|
||||||
@@ -120,10 +122,10 @@ class TrackEthereumAddress(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getBalance(addr: TrackedAddress): Mono<Wei> {
|
fun getBalance(addr: TrackedAddress): Mono<Wei> {
|
||||||
val up = upstreams.getUpstream(addr.chain) as AggregatedUpstream<EthereumApi>?
|
return getUpstream(addr.chain)
|
||||||
?: return Mono.error(SilentException.UnsupportedBlockchain(addr.chain))
|
.getReader()
|
||||||
return up.getApi(Selector.empty)
|
.balance()
|
||||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getBalance(addr.address, BlockTag.LATEST)) }
|
.read(addr.address)
|
||||||
.timeout(Defaults.timeout)
|
.timeout(Defaults.timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,15 +23,13 @@ import io.emeraldpay.dshackle.BlockchainType
|
|||||||
import io.emeraldpay.dshackle.SilentException
|
import io.emeraldpay.dshackle.SilentException
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
import io.emeraldpay.dshackle.data.TxId
|
import io.emeraldpay.dshackle.data.TxId
|
||||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
|
||||||
import io.emeraldpay.dshackle.upstream.Selector
|
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
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.Commands
|
|
||||||
import io.infinitape.etherjar.rpc.RpcException
|
import io.infinitape.etherjar.rpc.RpcException
|
||||||
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
|
||||||
@@ -72,8 +70,7 @@ class TrackEthereumTx(
|
|||||||
|
|
||||||
override fun subscribe(request: BlockchainOuterClass.TxStatusRequest): Flux<BlockchainOuterClass.TxStatus> {
|
override fun subscribe(request: BlockchainOuterClass.TxStatusRequest): Flux<BlockchainOuterClass.TxStatus> {
|
||||||
val base = prepareTracking(request)
|
val base = prepareTracking(request)
|
||||||
val up = upstreams.getUpstream(base.chain)?.castApi(EthereumApi::class.java)
|
val up = getUpstream(base.chain)
|
||||||
?: return Flux.empty()
|
|
||||||
return update(base)
|
return update(base)
|
||||||
.defaultIfEmpty(base)
|
.defaultIfEmpty(base)
|
||||||
.flatMapMany {
|
.flatMapMany {
|
||||||
@@ -82,9 +79,18 @@ class TrackEthereumTx(
|
|||||||
.map(this@TrackEthereumTx::asProto)
|
.map(this@TrackEthereumTx::asProto)
|
||||||
.subscribeOn(scheduler)
|
.subscribeOn(scheduler)
|
||||||
}
|
}
|
||||||
|
.doOnError { t ->
|
||||||
|
log.error("Subscription error", t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribe(base: TxDetails, up: Upstream<EthereumApi>): Flux<TxDetails> {
|
|
||||||
|
fun getUpstream(chain: Chain): AggregatedEthereumUpstreams {
|
||||||
|
return upstreams.getUpstream(chain)?.cast(AggregatedEthereumUpstreams::class.java, EthereumApi::class.java)
|
||||||
|
?: throw SilentException.UnsupportedBlockchain(chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun subscribe(base: TxDetails, up: AggregatedEthereumUpstreams): Flux<TxDetails> {
|
||||||
var latestTx = base
|
var latestTx = base
|
||||||
|
|
||||||
val untilFound = Mono.just(latestTx)
|
val untilFound = Mono.just(latestTx)
|
||||||
@@ -151,11 +157,9 @@ class TrackEthereumTx(
|
|||||||
|
|
||||||
private fun update(tx: TxDetails): Mono<TxDetails> {
|
private fun update(tx: TxDetails): Mono<TxDetails> {
|
||||||
val initialStatus = tx.status
|
val initialStatus = tx.status
|
||||||
val upstream = upstreams.getUpstream(tx.chain) as AggregatedUpstream<EthereumApi>?
|
val upstream = getUpstream(tx.chain)
|
||||||
?: return Mono.error(SilentException.UnsupportedBlockchain(tx.chain))
|
return upstream.getReader()
|
||||||
val execution = upstream.getApi(Selector.empty)
|
.txByHash().read(tx.txid)
|
||||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getTransaction(tx.txid)) }
|
|
||||||
return execution
|
|
||||||
.onErrorResume(RpcException::class.java) { t ->
|
.onErrorResume(RpcException::class.java) { t ->
|
||||||
log.warn("Upstream error, ignoring. {}", t.rpcMessage)
|
log.warn("Upstream error, ignoring. {}", t.rpcMessage)
|
||||||
Mono.empty<TransactionJson>()
|
Mono.empty<TransactionJson>()
|
||||||
@@ -198,10 +202,12 @@ class TrackEthereumTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadWeight(tx: TxDetails): Mono<TxDetails> {
|
private fun loadWeight(tx: TxDetails): Mono<TxDetails> {
|
||||||
val upstream = upstreams.getUpstream(tx.chain) as AggregatedUpstream<EthereumApi>?
|
val upstream = getUpstream(tx.chain)
|
||||||
?: return Mono.error(SilentException.UnsupportedBlockchain(tx.chain))
|
if (tx.status.blockHash == null) {
|
||||||
return upstream.getApi(Selector.empty)
|
return Mono.empty()
|
||||||
.flatMap { api -> api.executeAndConvert(Commands.eth().getBlock(tx.status.blockHash)) }
|
}
|
||||||
|
return upstream.getReader()
|
||||||
|
.blocksByHash().read(tx.status.blockHash)
|
||||||
.map { block ->
|
.map { block ->
|
||||||
setBlockDetails(tx, block)
|
setBlockDetails(tx, block)
|
||||||
}.doOnError { t ->
|
}.doOnError { t ->
|
||||||
@@ -209,7 +215,7 @@ class TrackEthereumTx(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFromBlock(upstream: Upstream<EthereumApi>, tx: TxDetails, blockTx: TransactionJson): Mono<TxDetails> {
|
fun updateFromBlock(upstream: AggregatedEthereumUpstreams, tx: TxDetails, blockTx: TransactionJson): Mono<TxDetails> {
|
||||||
return if (blockTx.blockNumber != null && blockTx.blockHash != null && blockTx.blockHash != ZERO_BLOCK) {
|
return if (blockTx.blockNumber != null && blockTx.blockHash != null && blockTx.blockHash != ZERO_BLOCK) {
|
||||||
val updated = tx.withStatus(
|
val updated = tx.withStatus(
|
||||||
blockHash = blockTx.blockHash,
|
blockHash = blockTx.blockHash,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ package io.emeraldpay.dshackle.upstream
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import io.emeraldpay.dshackle.upstream.bitcoin.DefaultBitcoinMethods
|
|||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -65,7 +65,7 @@ class CurrentUpstreams(
|
|||||||
.cast(EthereumUpstream::class.java, EthereumApi::class.java) as Upstream<EthereumApi>
|
.cast(EthereumUpstream::class.java, EthereumApi::class.java) as Upstream<EthereumApi>
|
||||||
val current = chainMapping[chain] as ChainUpstreams<EthereumApi>?
|
val current = chainMapping[chain] as ChainUpstreams<EthereumApi>?
|
||||||
val factory = Callable {
|
val factory = Callable {
|
||||||
EthereumChainUpstreams(chain, ArrayList(), cachesFactory.getCaches(chain), objectMapper) as ChainUpstreams<EthereumApi>
|
AggregatedEthereumUpstreams(chain, ArrayList(), cachesFactory.getCaches(chain), objectMapper) as ChainUpstreams<EthereumApi>
|
||||||
}
|
}
|
||||||
processUpdate(change, up, current, factory)
|
processUpdate(change, up, current, factory)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
|||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
interface Upstream<out T : UpstreamApi> {
|
interface Upstream<out A : UpstreamApi> {
|
||||||
fun isAvailable(): Boolean
|
fun isAvailable(): Boolean
|
||||||
fun getStatus(): UpstreamAvailability
|
fun getStatus(): UpstreamAvailability
|
||||||
fun observeStatus(): Flux<UpstreamAvailability>
|
fun observeStatus(): Flux<UpstreamAvailability>
|
||||||
fun getHead(): Head
|
fun getHead(): Head
|
||||||
fun getApi(matcher: Selector.Matcher): Mono<out T>
|
fun getApi(matcher: Selector.Matcher): Mono<out A>
|
||||||
fun getOptions(): UpstreamsConfig.Options
|
fun getOptions(): UpstreamsConfig.Options
|
||||||
fun setLag(lag: Long)
|
fun setLag(lag: Long)
|
||||||
fun getLag(): Long
|
fun getLag(): Long
|
||||||
@@ -34,6 +34,6 @@ interface Upstream<out T : UpstreamApi> {
|
|||||||
fun getMethods(): CallMethods
|
fun getMethods(): CallMethods
|
||||||
fun getId(): String
|
fun getId(): String
|
||||||
|
|
||||||
fun <A : UpstreamApi> castApi(apiType: Class<A>): Upstream<A>
|
fun <TA : UpstreamApi> castApi(apiType: Class<TA>): Upstream<TA>
|
||||||
fun <T : Upstream<TA>, TA : UpstreamApi> cast(selfType: Class<T>, apiType: Class<TA>): T
|
fun <T : Upstream<TA>, TA : UpstreamApi> cast(selfType: Class<T>, apiType: Class<TA>): T
|
||||||
}
|
}
|
||||||
@@ -19,13 +19,13 @@ import io.emeraldpay.dshackle.upstream.Head
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
|
|
||||||
open class BitcoinData(
|
open class BitcoinReader(
|
||||||
api: DirectBitcoinApi,
|
api: DirectBitcoinApi,
|
||||||
head: Head
|
head: Head
|
||||||
) : Lifecycle {
|
) : Lifecycle {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(BitcoinData::class.java)
|
private val log = LoggerFactory.getLogger(BitcoinReader::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mempool = CachingMempoolData(api, head)
|
private val mempool = CachingMempoolData(api, head)
|
||||||
@@ -42,7 +42,7 @@ open class BitcoinUpstream(
|
|||||||
|
|
||||||
private val head: Head = createHead()
|
private val head: Head = createHead()
|
||||||
private var validatorSubscription: Disposable? = null
|
private var validatorSubscription: Disposable? = null
|
||||||
private val data = BitcoinData(api, head)
|
private val data = BitcoinReader(api, head)
|
||||||
|
|
||||||
private fun createHead(): Head {
|
private fun createHead(): Head {
|
||||||
return BitcoinRpcHead(
|
return BitcoinRpcHead(
|
||||||
@@ -51,7 +51,7 @@ open class BitcoinUpstream(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getData(): BitcoinData {
|
open fun getData(): BitcoinReader {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import io.emeraldpay.grpc.Chain
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.Lifecycle
|
import org.springframework.context.Lifecycle
|
||||||
|
|
||||||
class EthereumChainUpstreams(
|
open class AggregatedEthereumUpstreams(
|
||||||
chain: Chain,
|
chain: Chain,
|
||||||
val upstreams: MutableList<EthereumUpstream>,
|
val upstreams: MutableList<EthereumUpstream>,
|
||||||
caches: Caches,
|
caches: Caches,
|
||||||
@@ -32,11 +32,13 @@ class EthereumChainUpstreams(
|
|||||||
) : ChainUpstreams<EthereumApi>(chain, upstreams as MutableList<Upstream<EthereumApi>>, caches, objectMapper) {
|
) : ChainUpstreams<EthereumApi>(chain, upstreams as MutableList<Upstream<EthereumApi>>, caches, objectMapper) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(EthereumChainUpstreams::class.java)
|
private val log = LoggerFactory.getLogger(AggregatedEthereumUpstreams::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var head: Head? = null
|
private var head: Head? = null
|
||||||
|
|
||||||
|
private val reader: EthereumReader = EthereumReader(this, this.caches, objectMapper)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
@@ -48,6 +50,24 @@ class EthereumChainUpstreams(
|
|||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
super.start()
|
||||||
|
reader.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
super.stop()
|
||||||
|
reader.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRunning(): Boolean {
|
||||||
|
return super.isRunning() || reader.isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun getReader(): EthereumReader {
|
||||||
|
return reader
|
||||||
|
}
|
||||||
|
|
||||||
override fun getHead(): Head {
|
override fun getHead(): Head {
|
||||||
return head!!
|
return head!!
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
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.reader.Reader
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamApi
|
import io.emeraldpay.dshackle.upstream.UpstreamApi
|
||||||
import io.infinitape.etherjar.rpc.*
|
import io.infinitape.etherjar.rpc.*
|
||||||
@@ -37,6 +38,14 @@ abstract class EthereumApi(
|
|||||||
private val jacksonRpcConverter = JacksonRpcConverter(objectMapper)
|
private val jacksonRpcConverter = JacksonRpcConverter(objectMapper)
|
||||||
var upstream: Upstream<EthereumApi>? = null
|
var upstream: Upstream<EthereumApi>? = null
|
||||||
|
|
||||||
|
fun <JS, RS> reader(): Reader<RpcCall<JS, RS>, RS> {
|
||||||
|
return object : Reader<RpcCall<JS, RS>, RS> {
|
||||||
|
override fun read(key: RpcCall<JS, RS>): Mono<RS> {
|
||||||
|
return this@EthereumApi.executeAndConvert(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun <JS, RS> execute(rpcCall: RpcCall<JS, RS>): Mono<ByteArray> {
|
fun <JS, RS> execute(rpcCall: RpcCall<JS, RS>): Mono<ByteArray> {
|
||||||
return execute(0, rpcCall.method, rpcCall.params as List<Any>)
|
return execute(0, rpcCall.method, rpcCall.params as List<Any>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 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.upstream.ethereum
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.emeraldpay.dshackle.Defaults
|
||||||
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
|
import io.emeraldpay.dshackle.cache.CachesEnabled
|
||||||
|
import io.emeraldpay.dshackle.cache.CurrentBlockCache
|
||||||
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
|
import io.emeraldpay.dshackle.data.TxContainer
|
||||||
|
import io.emeraldpay.dshackle.data.TxId
|
||||||
|
import io.emeraldpay.dshackle.reader.*
|
||||||
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.Selector
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
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.Commands
|
||||||
|
import io.infinitape.etherjar.rpc.RpcCall
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockTag
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
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
|
||||||
|
|
||||||
|
open class EthereumReader(
|
||||||
|
private val up: Upstream<EthereumApi>,
|
||||||
|
private val caches: Caches,
|
||||||
|
private val objectMapper: ObjectMapper
|
||||||
|
) : Lifecycle {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = LoggerFactory.getLogger(EthereumReader::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var headListener: Disposable? = null
|
||||||
|
private val balanceCache = CurrentBlockCache<Address, Wei>()
|
||||||
|
|
||||||
|
private val extractBlock = Function<BlockContainer, BlockJson<TransactionRefJson>> { block ->
|
||||||
|
objectMapper
|
||||||
|
.readValue(block.json, BlockJson::class.java)
|
||||||
|
.withoutTransactionDetails()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val extractTx = Function<TxContainer, TransactionJson> { tx ->
|
||||||
|
objectMapper
|
||||||
|
.readValue(tx.json, TransactionJson::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val blocksDirect: Reader<BlockHash, BlockJson<TransactionRefJson>>
|
||||||
|
private val txDirect: Reader<TransactionId, TransactionJson>
|
||||||
|
private val balanceDirect: Reader<Address, Wei>
|
||||||
|
|
||||||
|
private val idToBlockHash = Function<BlockId, BlockHash> { id -> BlockHash.from(id.value) }
|
||||||
|
private val blockHashToId = Function<BlockHash, BlockId> { hash -> BlockId.from(hash) }
|
||||||
|
|
||||||
|
private val txHashToId = Function<TransactionId, TxId> { hash -> TxId.from(hash) }
|
||||||
|
|
||||||
|
init {
|
||||||
|
blocksDirect = object : Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
||||||
|
override fun read(key: BlockHash): Mono<BlockJson<TransactionRefJson>> {
|
||||||
|
return up.getApi(Selector.empty).flatMap { api ->
|
||||||
|
api.executeAndConvert(Commands.eth().getBlock(key))
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $key")))
|
||||||
|
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
||||||
|
.doOnNext { block ->
|
||||||
|
caches.cache(Caches.Tag.REQUESTED, BlockContainer.from(block, objectMapper))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txDirect = object : Reader<TransactionId, TransactionJson> {
|
||||||
|
override fun read(key: TransactionId): Mono<TransactionJson> {
|
||||||
|
return up.getApi(Selector.empty).flatMap { api ->
|
||||||
|
api.executeAndConvert(Commands.eth().getTransaction(key))
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Tx not read $key")))
|
||||||
|
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
||||||
|
.doOnNext { tx ->
|
||||||
|
if (tx.blockNumber != null && tx.blockHash != null) {
|
||||||
|
caches.cache(Caches.Tag.REQUESTED, TxContainer.from(tx, objectMapper))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
balanceDirect = object : Reader<Address, Wei> {
|
||||||
|
override fun read(key: Address): Mono<Wei> {
|
||||||
|
return up.getApi(Selector.empty).flatMap { api ->
|
||||||
|
api.executeAndConvert(Commands.eth().getBalance(key, BlockTag.LATEST))
|
||||||
|
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key")))
|
||||||
|
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
|
||||||
|
.doOnNext { value ->
|
||||||
|
balanceCache.put(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun blocksById(): Reader<BlockId, BlockJson<TransactionRefJson>> {
|
||||||
|
return CompoundReader(
|
||||||
|
TransformingReader(caches.getBlocksByHash(), extractBlock),
|
||||||
|
RekeyingReader(idToBlockHash, blocksDirect)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun blocksByHash(): Reader<BlockHash, BlockJson<TransactionRefJson>> {
|
||||||
|
return CompoundReader(
|
||||||
|
RekeyingReader(
|
||||||
|
blockHashToId,
|
||||||
|
TransformingReader(caches.getBlocksByHash(), extractBlock)
|
||||||
|
),
|
||||||
|
blocksDirect
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun txByHash(): Reader<TransactionId, TransactionJson> {
|
||||||
|
return CompoundReader(
|
||||||
|
RekeyingReader(
|
||||||
|
txHashToId,
|
||||||
|
TransformingReader(caches.getTxByHash(), extractTx)
|
||||||
|
),
|
||||||
|
txDirect
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun balance(): Reader<Address, Wei> {
|
||||||
|
return CompoundReader(
|
||||||
|
balanceCache, balanceDirect
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRunning(): Boolean {
|
||||||
|
return this.headListener != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
this.headListener = up.getHead().getFlux().subscribe {
|
||||||
|
balanceCache.evict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
|
val headListener = this.headListener
|
||||||
|
this.headListener = null
|
||||||
|
headListener?.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 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.reader
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
class RekeyingReaderSpec extends Specification {
|
||||||
|
|
||||||
|
def "Simple rekey"() {
|
||||||
|
setup:
|
||||||
|
Reader<Long, Long> r = new Reader<Long, Long>() {
|
||||||
|
@Override
|
||||||
|
Mono<Long> read(Long key) {
|
||||||
|
return Mono.just(key * 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Function<String, Long> f = { String s -> Long.parseLong(s) }
|
||||||
|
when:
|
||||||
|
def rekey = new RekeyingReader(f, r)
|
||||||
|
def act = rekey.read("4").block()
|
||||||
|
then:
|
||||||
|
act == 8
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 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.reader
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
class TransformingReaderSpec extends Specification {
|
||||||
|
|
||||||
|
def "Transform"() {
|
||||||
|
Reader<String, String> r = new Reader<String, String>() {
|
||||||
|
@Override
|
||||||
|
Mono<String> read(String key) {
|
||||||
|
return Mono.just(key + "2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Function<String, Long> f = { String s -> Long.parseLong(s) }
|
||||||
|
when:
|
||||||
|
def transform = new TransformingReader(r, f)
|
||||||
|
def act = transform.read("4").block()
|
||||||
|
then:
|
||||||
|
act == 42L
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import io.emeraldpay.dshackle.data.BlockId
|
|||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinData
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinReader
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi
|
import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.bitcoin.CachingMempoolData
|
import io.emeraldpay.dshackle.upstream.bitcoin.CachingMempoolData
|
||||||
@@ -46,7 +46,7 @@ class TrackBitcoinTxSpec extends Specification {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||||
_ * getData() >> Mock(BitcoinData) {
|
_ * getData() >> Mock(BitcoinReader) {
|
||||||
_ * getMempool() >> mempoolAccess
|
_ * getMempool() >> mempoolAccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +72,7 @@ class TrackBitcoinTxSpec extends Specification {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||||
_ * getData() >> Mock(BitcoinData) {
|
_ * getData() >> Mock(BitcoinReader) {
|
||||||
_ * getMempool() >> mempoolAccess
|
_ * getMempool() >> mempoolAccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ class TrackBitcoinTxSpec extends Specification {
|
|||||||
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
BitcoinUpstream upstream = Mock(BitcoinUpstream) {
|
||||||
_ * getApi(_) >> Mono.just(api)
|
_ * getApi(_) >> Mono.just(api)
|
||||||
_ * getHead() >> head
|
_ * getHead() >> head
|
||||||
_ * getData() >> Mock(BitcoinData) {
|
_ * getData() >> Mock(BitcoinReader) {
|
||||||
_ * getMempool() >> mempoolAccess
|
_ * getMempool() >> mempoolAccess
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,14 +19,18 @@ package io.emeraldpay.dshackle.rpc
|
|||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||||
import io.emeraldpay.api.proto.Common
|
import io.emeraldpay.api.proto.Common
|
||||||
import io.emeraldpay.dshackle.data.BlockContainer
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumReader
|
||||||
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
|
||||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||||
|
import io.infinitape.etherjar.rpc.RpcCall
|
||||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import reactor.core.publisher.TopicProcessor
|
import reactor.core.publisher.TopicProcessor
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
@@ -111,12 +115,12 @@ class TrackEthereumAddressSpec extends Specification {
|
|||||||
def flux = trackAddress.subscribe(req)
|
def flux = trackAddress.subscribe(req)
|
||||||
then:
|
then:
|
||||||
StepVerifier.create(flux)
|
StepVerifier.create(flux)
|
||||||
.expectNext(exp1)
|
.expectNext(exp1).as("First block")
|
||||||
.then {
|
.then {
|
||||||
upstreamMock.nextBlock(BlockContainer.from(block2, TestingCommons.objectMapper()))
|
upstreamMock.nextBlock(BlockContainer.from(block2, TestingCommons.objectMapper()))
|
||||||
}
|
}
|
||||||
.expectNext(exp2)
|
.expectNext(exp2).as("Second block")
|
||||||
.thenCancel()
|
.thenCancel()
|
||||||
.verify(Duration.ofSeconds(3))
|
.verify(Duration.ofSeconds(1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ import io.emeraldpay.dshackle.test.UpstreamsMock
|
|||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumWs
|
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import io.infinitape.etherjar.domain.BlockHash
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
import io.infinitape.etherjar.domain.TransactionId
|
import io.infinitape.etherjar.domain.TransactionId
|
||||||
@@ -39,7 +38,6 @@ import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
|||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.test.StepVerifier
|
import reactor.test.StepVerifier
|
||||||
import reactor.test.scheduler.VirtualTimeScheduler
|
import reactor.test.scheduler.VirtualTimeScheduler
|
||||||
import spock.lang.Ignore
|
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -68,7 +66,7 @@ class TrackEthereumTxSpec extends Specification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def blockHeadJson = new BlockJson().with {
|
def blockHeadJson = new BlockJson().with {
|
||||||
it.hash = BlockHash.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")
|
it.hash = BlockHash.from("0x552d882f16f34a9dba2ccdc05c0a6a27c22a0e65cbc1b52a8ca60562112c6060")
|
||||||
it.timestamp = Instant.ofEpochMilli(156400200000)
|
it.timestamp = Instant.ofEpochMilli(156400200000)
|
||||||
it.number = 108
|
it.number = 108
|
||||||
it.totalDifficulty = BigInteger.valueOf(800)
|
it.totalDifficulty = BigInteger.valueOf(800)
|
||||||
@@ -123,7 +121,7 @@ class TrackEthereumTxSpec extends Specification {
|
|||||||
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
def apiMock = TestingCommons.api(Stub(ReactorRpcClient))
|
||||||
def upstreamMock = TestingCommons.upstream(apiMock)
|
def upstreamMock = TestingCommons.upstream(apiMock)
|
||||||
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
Upstreams upstreams = new UpstreamsMock(Chain.ETHEREUM, upstreamMock)
|
||||||
((EthereumChainUpstreams) upstreams.getUpstream(Chain.ETHEREUM)).head = Mock(Head) {
|
((AggregatedEthereumUpstreams) upstreams.getUpstream(Chain.ETHEREUM)).head = Mock(Head) {
|
||||||
_ * getFlux() >> Flux.empty()
|
_ * getFlux() >> Flux.empty()
|
||||||
}
|
}
|
||||||
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
TrackEthereumTx trackTx = new TrackEthereumTx(upstreams)
|
||||||
|
|||||||
@@ -25,15 +25,13 @@ import io.emeraldpay.dshackle.cache.Caches
|
|||||||
import io.emeraldpay.dshackle.cache.CachesFactory
|
import io.emeraldpay.dshackle.cache.CachesFactory
|
||||||
import io.emeraldpay.dshackle.config.CacheConfig
|
import io.emeraldpay.dshackle.config.CacheConfig
|
||||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.ChainUpstreams
|
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import io.infinitape.etherjar.rpc.JacksonRpcConverter
|
import io.infinitape.etherjar.rpc.JacksonRpcConverter
|
||||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||||
import org.springframework.core.env.StandardEnvironment
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
@@ -77,7 +75,7 @@ class TestingCommons {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static AggregatedUpstream aggregatedUpstream(EthereumUpstream up) {
|
static AggregatedUpstream aggregatedUpstream(EthereumUpstream up) {
|
||||||
return new EthereumChainUpstreams(Chain.ETHEREUM, [up], Caches.default(objectMapper()), objectMapper())
|
return new AggregatedEthereumUpstreams(Chain.ETHEREUM, [up], Caches.default(objectMapper()), objectMapper())
|
||||||
}
|
}
|
||||||
|
|
||||||
static CachesFactory emptyCaches() {
|
static CachesFactory emptyCaches() {
|
||||||
|
|||||||
@@ -16,13 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.test
|
package io.emeraldpay.dshackle.test
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
|
||||||
import io.emeraldpay.dshackle.upstream.ChainUpstreams
|
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumReader
|
||||||
|
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import org.jetbrains.annotations.NotNull
|
import org.jetbrains.annotations.NotNull
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
@@ -30,7 +32,7 @@ import reactor.core.publisher.Flux
|
|||||||
class UpstreamsMock implements Upstreams {
|
class UpstreamsMock implements Upstreams {
|
||||||
|
|
||||||
private Map<Chain, DefaultEthereumMethods> target = [:]
|
private Map<Chain, DefaultEthereumMethods> target = [:]
|
||||||
private Map<Chain, AggregatedUpstream> upstreams = [:]
|
private Map<Chain, AggregatedEthereumUpstreamsMock> upstreams = [:]
|
||||||
|
|
||||||
UpstreamsMock(Chain chain, Upstream up) {
|
UpstreamsMock(Chain chain, Upstream up) {
|
||||||
addUpstream(chain, up)
|
addUpstream(chain, up)
|
||||||
@@ -40,15 +42,20 @@ class UpstreamsMock implements Upstreams {
|
|||||||
addUpstream(chain2, up2)
|
addUpstream(chain2, up2)
|
||||||
}
|
}
|
||||||
|
|
||||||
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
|
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull EthereumUpstream up) {
|
||||||
if (!upstreams.containsKey(chain)) {
|
if (!upstreams.containsKey(chain)) {
|
||||||
upstreams[chain] = new EthereumChainUpstreams(chain, [up], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
upstreams[chain] = new AggregatedEthereumUpstreamsMock(chain, [up], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||||
|
upstreams[chain].start()
|
||||||
} else {
|
} else {
|
||||||
upstreams[chain].addUpstream(up)
|
upstreams[chain].addUpstream(up)
|
||||||
}
|
}
|
||||||
return upstreams[chain]
|
return upstreams[chain]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setReader(@NotNull Chain chain, EthereumReader reader) {
|
||||||
|
upstreams[chain].customReader = reader
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
AggregatedUpstream getUpstream(@NotNull Chain chain) {
|
AggregatedUpstream getUpstream(@NotNull Chain chain) {
|
||||||
return upstreams[chain]
|
return upstreams[chain]
|
||||||
@@ -78,4 +85,21 @@ class UpstreamsMock implements Upstreams {
|
|||||||
return upstreams.containsKey(chain)
|
return upstreams.containsKey(chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class AggregatedEthereumUpstreamsMock extends AggregatedEthereumUpstreams {
|
||||||
|
|
||||||
|
EthereumReader customReader = null
|
||||||
|
|
||||||
|
AggregatedEthereumUpstreamsMock(@NotNull Chain chain, @NotNull List<EthereumUpstream> upstreams, @NotNull Caches caches, @NotNull ObjectMapper objectMapper) {
|
||||||
|
super(chain, upstreams, caches, objectMapper)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
EthereumReader getReader() {
|
||||||
|
if (customReader != null) {
|
||||||
|
return customReader
|
||||||
|
}
|
||||||
|
return super.getReader()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
|||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams
|
import io.emeraldpay.dshackle.upstream.ethereum.AggregatedEthereumUpstreams
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class AggregatedUpstreamSpec extends Specification {
|
|||||||
setup:
|
setup:
|
||||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||||
def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
||||||
def aggr = new EthereumChainUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
def aggr = new AggregatedEthereumUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||||
when:
|
when:
|
||||||
aggr.onUpstreamsUpdated()
|
aggr.onUpstreamsUpdated()
|
||||||
def act = aggr.getMethods()
|
def act = aggr.getMethods()
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2020 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.upstream.ethereum
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||||
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
|
import io.emeraldpay.dshackle.cache.TxMemCache
|
||||||
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
|
import io.emeraldpay.dshackle.data.TxContainer
|
||||||
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
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.ReactorRpcClient
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||||
|
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import spock.lang.Specification
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class EthereumReaderSpec extends Specification {
|
||||||
|
|
||||||
|
def blockId = BlockId.from("f85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")
|
||||||
|
def blockJson = new BlockJson<TransactionRefJson>().tap { blockJson ->
|
||||||
|
blockJson.hash = BlockHash.from(blockId.value)
|
||||||
|
blockJson.totalDifficulty = BigInteger.ONE
|
||||||
|
blockJson.number = 101
|
||||||
|
blockJson.timestamp = Instant.ofEpochSecond(100000000)
|
||||||
|
blockJson.transactions = []
|
||||||
|
blockJson.uncles = []
|
||||||
|
}
|
||||||
|
def txId = BlockId.from("a38e7b4d456777c94b46c61a1e4cf52fbdd92acc4444719d1fad77005698c221")
|
||||||
|
def txJson = new TransactionJson().tap { json ->
|
||||||
|
json.hash = TransactionId.from(txId.value)
|
||||||
|
json.blockHash = blockJson.hash
|
||||||
|
json.blockNumber = blockJson.number
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Block by Id reads from cache"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(BlocksMemCache) {
|
||||||
|
1 * read(blockId) >> Mono.just(BlockContainer.from(blockJson, TestingCommons.objectMapper()))
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setBlockByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
def reader = new EthereumReader(Stub(Upstream), caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.blocksById().read(blockId).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == blockJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Block by Id reads from api if cache is empty"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(BlocksMemCache) {
|
||||||
|
1 * read(blockId) >> Mono.empty()
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setBlockByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
def rpcClient = Stub(ReactorRpcClient)
|
||||||
|
def api = TestingCommons.api(rpcClient)
|
||||||
|
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||||
|
|
||||||
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
|
def reader = new EthereumReader(upstream, caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.blocksById().read(blockId).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == blockJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Block by Id reads from api if cache failed"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(BlocksMemCache) {
|
||||||
|
1 * read(blockId) >> Mono.error(new IllegalStateException("Test error"))
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setBlockByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
def rpcClient = Stub(ReactorRpcClient)
|
||||||
|
def api = TestingCommons.api(rpcClient)
|
||||||
|
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||||
|
|
||||||
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
|
def reader = new EthereumReader(upstream, caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.blocksById().read(blockId).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == blockJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Block by Hash reads from cache"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(BlocksMemCache) {
|
||||||
|
1 * read(blockId) >> Mono.just(BlockContainer.from(blockJson, TestingCommons.objectMapper()))
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setBlockByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
def reader = new EthereumReader(Stub(Upstream), caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.blocksByHash().read(blockJson.hash).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == blockJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Block by Hash reads from api if cache is empty"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(BlocksMemCache) {
|
||||||
|
1 * read(blockId) >> Mono.empty()
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setBlockByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
def rpcClient = Stub(ReactorRpcClient)
|
||||||
|
def api = TestingCommons.api(rpcClient)
|
||||||
|
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
|
||||||
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
|
def reader = new EthereumReader(upstream, caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.blocksByHash().read(blockJson.hash).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == blockJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Tx by Hash reads from cache"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(TxMemCache) {
|
||||||
|
1 * read(txId) >> Mono.just(TxContainer.from(txJson, TestingCommons.objectMapper()))
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setTxByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
def reader = new EthereumReader(Stub(Upstream), caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.txByHash().read(txJson.hash).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == txJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Tx by Hash reads from api if cache is empty"() {
|
||||||
|
setup:
|
||||||
|
def memCache = Mock(TxMemCache) {
|
||||||
|
1 * read(txId) >> Mono.empty()
|
||||||
|
}
|
||||||
|
def caches = Caches.newBuilder()
|
||||||
|
.setTxByHash(memCache)
|
||||||
|
.setObjectMapper(TestingCommons.objectMapper())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
def rpcClient = Stub(ReactorRpcClient)
|
||||||
|
def api = TestingCommons.api(rpcClient)
|
||||||
|
api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson)
|
||||||
|
def upstream = TestingCommons.aggregatedUpstream(api)
|
||||||
|
def reader = new EthereumReader(upstream, caches, TestingCommons.objectMapper())
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.txByHash().read(txJson.hash).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == txJson
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Caches balance until block mined"() {
|
||||||
|
setup:
|
||||||
|
def rpcClient = Stub(ReactorRpcClient)
|
||||||
|
def api = TestingCommons.api(rpcClient)
|
||||||
|
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0x10")
|
||||||
|
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff")
|
||||||
|
def upstream = TestingCommons.upstream(api)
|
||||||
|
def reader = new EthereumReader(upstream, Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||||
|
reader.start()
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = reader.balance().read(Address.from("0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c")).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == Wei.from("0x10")
|
||||||
|
|
||||||
|
when:
|
||||||
|
//now it should use cached value, without actual request
|
||||||
|
act = reader.balance().read(Address.from("0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c")).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == Wei.from("0x10")
|
||||||
|
|
||||||
|
when:
|
||||||
|
//move head forward, which should erase cache
|
||||||
|
def block2 = blockJson.copy().tap {
|
||||||
|
it.number++
|
||||||
|
it.totalDifficulty = BigInteger.TWO
|
||||||
|
}
|
||||||
|
upstream.nextBlock(BlockContainer.from(block2, TestingCommons.objectMapper()))
|
||||||
|
act = reader.balance().read(Address.from("0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c")).block()
|
||||||
|
|
||||||
|
then:
|
||||||
|
act == Wei.from("0xff")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user