Merge pull request #77 from p2p-org/upstream-get-transaction-receipt

changes from upstream: removed RpcReader and use better flow for tx r…
This commit is contained in:
Vyacheslav Shebanov
2022-12-10 16:03:49 +02:00
committed by GitHub
26 changed files with 153 additions and 661 deletions

View File

@@ -92,7 +92,7 @@ open class Caches(
open fun cacheReceipt(tag: Tag, data: DefaultContainer<TransactionReceiptJson>) { open fun cacheReceipt(tag: Tag, data: DefaultContainer<TransactionReceiptJson>) {
val currentHeight = head?.getCurrentHeight() val currentHeight = head?.getCurrentHeight()
if (currentHeight != null && data.height != null && memReceipts.acceptsRecentBlocks(currentHeight - data.height)) { if (currentHeight != null && data.height != null && memReceipts.acceptsRecentBlocks(currentHeight - data.height)) {
memReceipts.add(data) memReceipts.add(data).subscribe()
} }
// TODO move subscription to the caller // TODO move subscription to the caller
redisReceipts?.add(data)?.subscribe() redisReceipts?.add(data)?.subscribe()

View File

@@ -18,11 +18,11 @@ package io.emeraldpay.dshackle.data
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
class DefaultContainer<T>( class DefaultContainer<T>(
val txId: TxId?, val txId: TxId? = null,
val blockId: BlockId?, val blockId: BlockId? = null,
val height: Long?, val height: Long? = null,
json: ByteArray, json: ByteArray,
parsed: T parsed: T? = null
) : SourceContainer(json, parsed) { ) : SourceContainer(json, parsed) {
companion object { companion object {

View File

@@ -1,54 +0,0 @@
/**
* Copyright (c) 2021 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.reader
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
/**
* Reader that requests data through upstream RPC using provided JSON RPC request builder
*/
class RpcReader<T>(
private val up: Multistream,
private val paramsBuilder: (T) -> JsonRpcRequest
) : Reader<T, ByteArray> {
companion object {
private val log = LoggerFactory.getLogger(RpcReader::class.java)
/**
* Common reader that just passes key as a parameter with the specified method. The key must be serializable to JSON.
* @param method RPC method to use
*/
fun <T> basicRequest(up: Multistream, method: String): RpcReader<T> {
return RpcReader(up) { key ->
JsonRpcRequest(method, listOf(key))
}
}
}
override fun read(key: T): Mono<ByteArray> {
return up.getDirectApi(Selector.empty)
.flatMap { rdr ->
rdr.read(paramsBuilder(key)).flatMap {
it.requireResult()
}
}
}
}

View File

@@ -52,7 +52,6 @@ abstract class Multistream(
val chain: Chain, val chain: Chain,
private val upstreams: MutableList<Upstream>, private val upstreams: MutableList<Upstream>,
val caches: Caches, val caches: Caches,
val postprocessor: RequestPostprocessor
) : Upstream, Lifecycle { ) : Upstream, Lifecycle {
companion object { companion object {
@@ -144,23 +143,6 @@ abstract class Multistream(
return FilteredApis(chain, upstreams, matcher, i) return FilteredApis(chain, upstreams, matcher, i)
} }
/**
* Finds an API that executed directly on a remote.
*/
open fun getDirectApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> {
val apis = getApiSource(matcher)
apis.request(1)
return Mono.from(apis)
.map(Upstream::getApi)
.map {
RequestPostprocessor.wrap(
it,
postprocessor
)
} // TODO do it on upstream init, not each time it's called
.switchIfEmpty(Mono.error(Exception("No API available for $chain")))
}
abstract fun getFeeEstimation(): ChainFees abstract fun getFeeEstimation(): ChainFees
/** /**

View File

@@ -1,40 +0,0 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import reactor.core.publisher.Mono
interface RequestPostprocessor {
fun onReceive(method: String, params: List<Any?>, json: ByteArray)
class Empty : RequestPostprocessor {
override fun onReceive(method: String, params: List<Any?>, json: ByteArray) {}
}
companion object {
fun wrap(
reader: Reader<JsonRpcRequest, JsonRpcResponse>,
processor: RequestPostprocessor
): Reader<JsonRpcRequest, JsonRpcResponse> {
return Wrapper(reader, processor)
}
}
class Wrapper(
private val reader: Reader<JsonRpcRequest, JsonRpcResponse>,
private val processor: RequestPostprocessor
) : Reader<JsonRpcRequest, JsonRpcResponse> {
override fun read(key: JsonRpcRequest): Mono<JsonRpcResponse> {
return reader.read(key)
.doOnNext {
if (it.hasResult()) {
val result = it.getResult()
processor.onReceive(key.method, key.params, result)
}
}
}
}
}

View File

@@ -32,8 +32,8 @@ import reactor.core.publisher.Mono
open class BitcoinMultistream( open class BitcoinMultistream(
chain: Chain, chain: Chain,
private val sourceUpstreams: MutableList<BitcoinUpstream>, private val sourceUpstreams: MutableList<BitcoinUpstream>,
caches: Caches caches: Caches,
) : Multistream(chain, sourceUpstreams as MutableList<Upstream>, caches, RequestPostprocessor.Empty()), Lifecycle { ) : Multistream(chain, sourceUpstreams as MutableList<Upstream>, caches), Lifecycle {
companion object { companion object {
private val log = LoggerFactory.getLogger(BitcoinMultistream::class.java) private val log = LoggerFactory.getLogger(BitcoinMultistream::class.java)
@@ -95,6 +95,16 @@ open class BitcoinMultistream(
onHeadUpdated(head) onHeadUpdated(head)
return head return head
} }
/**
* Finds an API that executed directly on a remote.
*/
open fun getDirectApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> {
val apis = getApiSource(matcher)
apis.request(1)
return Mono.from(apis)
.map(Upstream::getApi)
.switchIfEmpty(Mono.error(Exception("No API available for $chain")))
}
override fun getRoutedApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> { override fun getRoutedApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> {
return Mono.just(callRouter) return Mono.just(callRouter)

View File

@@ -1,60 +0,0 @@
/**
* 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.Global
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.data.DefaultContainer
import io.emeraldpay.dshackle.data.TxId
import io.emeraldpay.dshackle.upstream.RequestPostprocessor
import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson
import org.slf4j.LoggerFactory
class CacheRequested(
private val caches: Caches
) : RequestPostprocessor {
companion object {
private val log = LoggerFactory.getLogger(CacheRequested::class.java)
}
override fun onReceive(method: String, params: List<Any?>, json: ByteArray) {
try {
if (method == "eth_getTransactionReceipt") {
cacheTxReceipt(params, json)
}
} catch (e: Throwable) {
log.warn("Failed to cache result", e)
}
}
fun cacheTxReceipt(params: List<Any?>, json: ByteArray) {
if (params.size != 1) {
return
}
// note: json could be a `null` value
val parsed = Global.objectMapper.readValue(json, TransactionReceiptJson::class.java) ?: return
val value = DefaultContainer<TransactionReceiptJson>(
TxId.from(parsed.transactionHash),
BlockId.from(parsed.blockHash),
parsed.blockNumber,
json,
parsed
)
caches.cacheReceipt(Caches.Tag.REQUESTED, value)
}
}

View File

@@ -28,7 +28,6 @@ import io.emeraldpay.dshackle.data.TxId
import io.emeraldpay.dshackle.reader.CompoundReader import io.emeraldpay.dshackle.reader.CompoundReader
import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.reader.RekeyingReader import io.emeraldpay.dshackle.reader.RekeyingReader
import io.emeraldpay.dshackle.reader.RpcReader
import io.emeraldpay.dshackle.reader.TransformingReader import io.emeraldpay.dshackle.reader.TransformingReader
import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.Lifecycle
import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Multistream
@@ -45,16 +44,16 @@ import org.slf4j.LoggerFactory
import java.util.function.Function import java.util.function.Function
/** /**
* Reader for the common operations, that wraps caches + native call with quorum verification * Reader for the common operations, that use the cache when data is available or a native call with quorum verification
*/ */
open class EthereumReader( open class EthereumCachingReader(
private val up: Multistream, private val up: Multistream,
private val caches: Caches, private val caches: Caches,
private val callMethodsFactory: Factory<CallMethods> private val callMethodsFactory: Factory<CallMethods>
) : Lifecycle { ) : Lifecycle {
companion object { companion object {
private val log = LoggerFactory.getLogger(EthereumReader::class.java) private val log = LoggerFactory.getLogger(EthereumCachingReader::class.java)
} }
private val objectMapper: ObjectMapper = Global.objectMapper private val objectMapper: ObjectMapper = Global.objectMapper
@@ -157,10 +156,9 @@ open class EthereumReader(
} }
fun receipts(): Reader<TxId, ByteArray> { fun receipts(): Reader<TxId, ByteArray> {
// TODO put into cache
val requested = RekeyingReader( val requested = RekeyingReader(
{ txid: TxId -> txid.toHexWithPrefix() }, { txid: TxId -> TransactionId.from(txid.value) },
RpcReader.basicRequest(up, "eth_getTransactionReceipt") directReader.receiptReader
) )
return CompoundReader( return CompoundReader(
caches.getReceipts(), caches.getReceipts(),

View File

@@ -6,7 +6,10 @@ 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.BlockContainer import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.data.DefaultContainer
import io.emeraldpay.dshackle.data.TxContainer import io.emeraldpay.dshackle.data.TxContainer
import io.emeraldpay.dshackle.data.TxId
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Multistream
@@ -22,6 +25,7 @@ import io.emeraldpay.etherjar.rpc.RpcException
import io.emeraldpay.etherjar.rpc.RpcResponseError import io.emeraldpay.etherjar.rpc.RpcResponseError
import io.emeraldpay.etherjar.rpc.json.BlockJson import io.emeraldpay.etherjar.rpc.json.BlockJson
import io.emeraldpay.etherjar.rpc.json.TransactionJson import io.emeraldpay.etherjar.rpc.json.TransactionJson
import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
import org.apache.commons.collections4.Factory import org.apache.commons.collections4.Factory
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -51,6 +55,7 @@ class EthereumDirectReader(
val blockByHeightReader: Reader<Long, BlockContainer> val blockByHeightReader: Reader<Long, BlockContainer>
val txReader: Reader<TransactionId, TxContainer> val txReader: Reader<TransactionId, TxContainer>
val balanceReader: Reader<Address, Wei> val balanceReader: Reader<Address, Wei>
val receiptReader: Reader<TransactionId, ByteArray>
init { init {
blockReader = object : Reader<BlockHash, BlockContainer> { blockReader = object : Reader<BlockHash, BlockContainer> {
@@ -107,6 +112,32 @@ class EthereumDirectReader(
} }
} }
} }
receiptReader = object : Reader<TransactionId, ByteArray> {
override fun read(key: TransactionId): Mono<ByteArray> {
val request = JsonRpcRequest("eth_getTransactionReceipt", listOf(key.toHex()))
return readWithQuorum(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Receipt not read $key")))
.doOnNext { json ->
try {
// Caching needs some additional data (ex. Height) to make a decision on how long and where to cache
// So we have to parse the JSON here and extract reference data
val receipt = objectMapper.readValue(json, TransactionReceiptJson::class.java)
caches.cacheReceipt(
Caches.Tag.REQUESTED,
DefaultContainer(
txId = TxId.from(key),
blockId = BlockId.from(receipt.blockHash),
height = receipt.blockNumber,
json = json,
parsed = receipt
)
)
} catch (t: Throwable) {
log.warn("Failed to cache Tx Receipt", t)
}
}
}
}
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@@ -30,7 +30,7 @@ import java.util.function.Function
abstract class EthereumFees( abstract class EthereumFees(
upstreams: Multistream, upstreams: Multistream,
private val reader: EthereumReader, private val reader: EthereumCachingReader,
heightLimit: Int, heightLimit: Int,
) : AbstractChainFees<EthereumFees.EthereumFee, BlockJson<TransactionRefJson>, TransactionRefJson, TransactionJson>(heightLimit, upstreams, extractTx), ChainFees { ) : AbstractChainFees<EthereumFees.EthereumFee, BlockJson<TransactionRefJson>, TransactionRefJson, TransactionJson>(heightLimit, upstreams, extractTx), ChainFees {

View File

@@ -23,7 +23,7 @@ import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.function.Function import java.util.function.Function
class EthereumLegacyFees(upstreams: EthereumMultistream, reader: EthereumReader, heightLimit: Int) : class EthereumLegacyFees(upstreams: EthereumMultistream, reader: EthereumCachingReader, heightLimit: Int) :
EthereumFees(upstreams, reader, heightLimit) { EthereumFees(upstreams, reader, heightLimit) {
companion object { companion object {

View File

@@ -7,7 +7,7 @@ import io.emeraldpay.dshackle.upstream.Upstream
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
interface EthereumLikeMultistream : Upstream { interface EthereumLikeMultistream : Upstream {
fun getReader(): EthereumReader fun getReader(): EthereumCachingReader
fun getSubscribe(): EthereumSubscribe fun getSubscribe(): EthereumSubscribe
fun getHead(mather: Selector.Matcher): Head fun getHead(mather: Selector.Matcher): Head

View File

@@ -37,7 +37,7 @@ open class EthereumMultistream(
chain: Chain, chain: Chain,
val upstreams: MutableList<EthereumUpstream>, val upstreams: MutableList<EthereumUpstream>,
caches: Caches caches: Caches
) : Multistream(chain, upstreams as MutableList<Upstream>, caches, CacheRequested(caches)), EthereumLikeMultistream { ) : Multistream(chain, upstreams as MutableList<Upstream>, caches), EthereumLikeMultistream {
companion object { companion object {
private val log = LoggerFactory.getLogger(EthereumMultistream::class.java) private val log = LoggerFactory.getLogger(EthereumMultistream::class.java)
@@ -47,8 +47,9 @@ open class EthereumMultistream(
private val filteredHeads: MutableMap<String, Head> = private val filteredHeads: MutableMap<String, Head> =
ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.WEAK) ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.WEAK)
private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory()) private val reader: EthereumCachingReader = EthereumCachingReader(this, this.caches, getMethodsFactory())
private val subscribe = EthereumSubscribe(this) private val subscribe = EthereumSubscribe(this)
private val supportsEIP1559 = when (chain) { private val supportsEIP1559 = when (chain) {
Chain.ETHEREUM, Chain.TESTNET_ROPSTEN, Chain.TESTNET_GOERLI, Chain.TESTNET_RINKEBY -> true Chain.ETHEREUM, Chain.TESTNET_ROPSTEN, Chain.TESTNET_GOERLI, Chain.TESTNET_RINKEBY -> true
else -> false else -> false
@@ -82,7 +83,7 @@ open class EthereumMultistream(
return super.isRunning() || reader.isRunning() return super.isRunning() || reader.isRunning()
} }
override fun getReader(): EthereumReader { override fun getReader(): EthereumCachingReader {
return reader return reader
} }

View File

@@ -24,7 +24,7 @@ import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.util.function.Function import java.util.function.Function
class EthereumPriorityFees(upstreams: Multistream, reader: EthereumReader, heightLimit: Int) : class EthereumPriorityFees(upstreams: Multistream, reader: EthereumCachingReader, heightLimit: Int) :
EthereumFees(upstreams, reader, heightLimit) { EthereumFees(upstreams, reader, heightLimit) {
companion object { companion object {

View File

@@ -34,10 +34,10 @@ import java.math.BigInteger
* It provides data only if it's available through the router (cached, head, etc). * It provides data only if it's available through the router (cached, head, etc).
* If data is not available locally then it returns `empty`; at this case the caller should call the remote node for actual data. * If data is not available locally then it returns `empty`; at this case the caller should call the remote node for actual data.
* *
* @see EthereumReader * @see EthereumCachingReader
*/ */
class LocalCallRouter( class LocalCallRouter(
private val reader: EthereumReader, private val reader: EthereumCachingReader,
private val methods: CallMethods, private val methods: CallMethods,
private val head: Head private val head: Head
) : Reader<JsonRpcRequest, JsonRpcResponse> { ) : Reader<JsonRpcRequest, JsonRpcResponse> {

View File

@@ -37,7 +37,7 @@ open class EthereumPosMultiStream(
chain: Chain, chain: Chain,
val upstreams: MutableList<EthereumPosUpstream>, val upstreams: MutableList<EthereumPosUpstream>,
caches: Caches caches: Caches
) : Multistream(chain, upstreams as MutableList<Upstream>, caches, CacheRequested(caches)), EthereumLikeMultistream { ) : Multistream(chain, upstreams as MutableList<Upstream>, caches), EthereumLikeMultistream {
companion object { companion object {
private val log = LoggerFactory.getLogger(EthereumPosMultiStream::class.java) private val log = LoggerFactory.getLogger(EthereumPosMultiStream::class.java)
@@ -45,7 +45,7 @@ open class EthereumPosMultiStream(
private var head: Head? = null private var head: Head? = null
private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory()) private val reader: EthereumCachingReader = EthereumCachingReader(this, this.caches, getMethodsFactory())
private val feeEstimation = EthereumPriorityFees(this, reader, 256) private val feeEstimation = EthereumPriorityFees(this, reader, 256)
private val subscribe = EthereumSubscribe(this) private val subscribe = EthereumSubscribe(this)
private val filteredHeads: MutableMap<String, Head> = private val filteredHeads: MutableMap<String, Head> =
@@ -77,7 +77,7 @@ open class EthereumPosMultiStream(
return super.isRunning() || reader.isRunning() return super.isRunning() || reader.isRunning()
} }
override fun getReader(): EthereumReader { override fun getReader(): EthereumCachingReader {
return reader return reader
} }

View File

@@ -262,6 +262,6 @@ class CachesSpec extends Specification {
then: then:
1 * head.getCurrentHeight() >> 0xccf6e2 1 * head.getCurrentHeight() >> 0xccf6e2
1 * receiptMemCache.acceptsRecentBlocks(0) >> true 1 * receiptMemCache.acceptsRecentBlocks(0) >> true
1 * receiptMemCache.add(receiptContainer) 1 * receiptMemCache.add(receiptContainer) >> Mono.empty().then()
} }
} }

View File

@@ -29,9 +29,10 @@ import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.MultistreamHolder
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosMultiStream
import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosRpcUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumPosRpcUpstream
import io.emeraldpay.dshackle.upstream.ethereum.EthereumReader import io.emeraldpay.dshackle.upstream.ethereum.EthereumCachingReader
import io.emeraldpay.dshackle.BlockchainType import io.emeraldpay.dshackle.BlockchainType
import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Chain
import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.NotNull
class MultistreamHolderMock implements MultistreamHolder { class MultistreamHolderMock implements MultistreamHolder {
@@ -87,7 +88,7 @@ class MultistreamHolderMock implements MultistreamHolder {
static class EthereumMultistreamMock extends EthereumPosMultiStream { static class EthereumMultistreamMock extends EthereumPosMultiStream {
EthereumReader customReader = null EthereumCachingReader customReader = null
CallMethods customMethods = null CallMethods customMethods = null
Head customHead = null Head customHead = null
@@ -104,7 +105,7 @@ class MultistreamHolderMock implements MultistreamHolder {
} }
@Override @Override
EthereumReader getReader() { EthereumCachingReader getReader() {
if (customReader != null) { if (customReader != null) {
return customReader return customReader
} }

View File

@@ -180,26 +180,6 @@ class MultistreamSpec extends Specification {
!act !act
} }
def "Call postprocess after api use"() {
setup:
def request = new JsonRpcRequest("test_foo", [1], 1, null)
def api = TestingCommons.api()
api.answer("test_foo", [1], "test")
def postprocessor = Mock(RequestPostprocessor)
def up = TestingCommons.upstream(api)
def multistream = new TestMultistream([up], postprocessor)
when:
def rdr = multistream.getDirectApi(Selector.empty).block(Duration.ofSeconds(1))
def act = rdr.read(request).block(Duration.ofSeconds(1))
then:
act != null
act.hasResult()
act.resultAsProcessedString == "test"
1 * postprocessor.onReceive("test_foo", [1], "\"test\"".bytes)
}
def "Filter upstream matching selector single"() { def "Filter upstream matching selector single"() {
setup: setup:
@@ -341,48 +321,6 @@ class MultistreamSpec extends Specification {
.expectComplete() .expectComplete()
.verify(Duration.ofSeconds(1)) .verify(Duration.ofSeconds(1))
} }
class TestMultistream extends Multistream {
TestMultistream(List<Upstream> upstreams, @NotNull RequestPostprocessor postprocessor) {
super(Chain.ETHEREUM, upstreams, Caches.default(), postprocessor)
}
@Override
Mono<Reader<JsonRpcRequest, JsonRpcResponse>> getRoutedApi(@NotNull Selector.Matcher matcher) {
return null
}
@Override
Head updateHead() {
return null
}
@Override
void setHead(@NotNull Head head) {
}
@Override
Head getHead() {
return null
}
@Override
Collection<UpstreamsConfig.Labels> getLabels() {
return null
}
public <T extends Upstream> T cast(Class<T> selfType) {
return this
}
@Override
ChainFees getFeeEstimation() {
return null
}
}
class TestEthereumPosMultistream extends EthereumPosMultiStream { class TestEthereumPosMultistream extends EthereumPosMultiStream {
TestEthereumPosMultistream(@NotNull Chain chain, @NotNull List<EthereumPosUpstream> upstreams, @NotNull Caches caches) { TestEthereumPosMultistream(@NotNull Chain chain, @NotNull List<EthereumPosUpstream> upstreams, @NotNull Caches caches) {

View File

@@ -1,47 +0,0 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import reactor.core.publisher.Mono
import spock.lang.Specification
import java.time.Duration
class RequestPostprocessorSpec extends Specification {
def "Wrappers calls onReceive for a value"() {
setup:
def request = new JsonRpcRequest("test_foo", [1], 1, null)
def processor = Mock(RequestPostprocessor)
def api = TestingCommons.api()
api.answer("test_foo", [1], "test")
def wrapped = new RequestPostprocessor.Wrapper(api, processor)
when:
def act = wrapped.read(request).block(Duration.ofSeconds(1))
then:
act.hasResult()
act.resultAsProcessedString == "test"
1 * processor.onReceive("test_foo", [1], "\"test\"".bytes)
}
def "Wrappers doesn't call onReceive for no value"() {
setup:
def request = new JsonRpcRequest("test_foo", [1], 1, null)
def processor = Mock(RequestPostprocessor)
Reader<JsonRpcRequest, JsonRpcResponse> reader = Mock(Reader) {
1 * it.read(request) >> Mono.empty()
}
def wrapped = new RequestPostprocessor.Wrapper(reader, processor)
when:
def act = wrapped.read(request).block(Duration.ofSeconds(1))
then:
act == null
0 * processor.onReceive(_, _, _)
}
}

View File

@@ -1,58 +0,0 @@
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.data.DefaultContainer
import spock.lang.Specification
class CacheRequestedSpec extends Specification {
def "Do nothing if unsupported method"() {
setup:
def caches = Mock(Caches)
CacheRequested instance = new CacheRequested(caches)
when:
instance.onReceive("eth_hashrate", [], '"0x38a"'.bytes)
then:
0 * caches._(*_)
}
def "Caches tx receipt"() {
setup:
def caches = Mock(Caches)
CacheRequested instance = new CacheRequested(caches)
def json = '''{
"blockHash": "0x2c3cfd4c7f2b58859371f5795eaf8524caa6e63145ac7e9df23c8d63aab891ae",
"blockNumber": "0x213b8a",
"contractAddress": null,
"cumulativeGasUsed": "0x5208",
"gasUsed": "0x5208",
"logs": [],
"transactionHash": "0x5929b36be4586c57bd87dfb7ea6be3b985c1f527fa3d69d221604b424aeb4197",
"transactionIndex": "0x00"
}'''.bytes
when:
instance.onReceive("eth_getTransactionReceipt", ["0x5929b36be4586c57bd87dfb7ea6be3b985c1f527fa3d69d221604b424aeb4197"], json)
then:
1 * caches.cacheReceipt(Caches.Tag.REQUESTED, { DefaultContainer it ->
it.height == 0x213b8a &&
it.txId.toHex() == "5929b36be4586c57bd87dfb7ea6be3b985c1f527fa3d69d221604b424aeb4197" &&
it.json == json
})
}
def "Ignore null"() {
setup:
def caches = Mock(Caches)
CacheRequested instance = new CacheRequested(caches)
def json = 'null'.bytes
when:
instance.cacheTxReceipt(["0x5929b36be4586c57bd87dfb7ea6be3b985c1f527fa3d69d221604b424aeb4197"], json)
then:
0 * caches.cacheReceipt(_, _)
}
}

View File

@@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.upstream.ethereum
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.DefaultContainer
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.quorum.QuorumRpcReader import io.emeraldpay.dshackle.quorum.QuorumRpcReader
import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.reader.Reader
@@ -12,6 +13,7 @@ import io.emeraldpay.dshackle.upstream.Multistream
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.dshackle.Chain import io.emeraldpay.dshackle.Chain
import io.emeraldpay.etherjar.rpc.json.TransactionReceiptJson
import io.emeraldpay.etherjar.domain.Address import io.emeraldpay.etherjar.domain.Address
import io.emeraldpay.etherjar.domain.BlockHash import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.etherjar.domain.TransactionId import io.emeraldpay.etherjar.domain.TransactionId
@@ -22,7 +24,6 @@ import org.apache.commons.collections4.Factory
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.test.StepVerifier import reactor.test.StepVerifier
import spock.lang.Specification import spock.lang.Specification
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@@ -171,6 +172,76 @@ class EthereumDirectReaderSpec extends Specification {
.verify(Duration.ofSeconds(1)) .verify(Duration.ofSeconds(1))
} }
def "Reads tx receipt"() {
setup:
def json = new TransactionReceiptJson().tap {
transactionHash = 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_getTransactionReceipt", [hash1])) >> Mono.just(
new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, new ArrayList<Byte>()
)
)
}
}
when:
def act = reader.receiptReader.read(TransactionId.from(hash1))
.block(Duration.ofSeconds(1))
.with { new String(it) }
then:
act == '{"blockHash":"0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5","blockNumber":"0x64","transactionHash":"0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5","logs":[]}'
}
def "Puts tx receipt in cache after reading"() {
setup:
def json = new TransactionReceiptJson().tap {
transactionHash = 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)
}
def caches = Mock(Caches) {
// note that the Caches needs a Height value, otherwise it's not cached
1 * cacheReceipt(Caches.Tag.REQUESTED, { DefaultContainer data -> data.txId.toHex() == hash1.substring(2) && data.height == 100 })
}
EthereumDirectReader reader = new EthereumDirectReader(
up, caches, new CurrentBlockCache(), calls
)
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, new ArrayList<Byte>()
)
)
}
}
when:
def act = reader.receiptReader.read(TransactionId.from(hash1))
.block(Duration.ofSeconds(1))
.with { new String(it) }
then:
act == '{"blockHash":"0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5","blockNumber":"0x64","transactionHash":"0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5","logs":[]}'
}
def "Produce empty on non-existing tx"() { def "Produce empty on non-existing tx"() {
setup: setup:
def up = Mock(Multistream) { def up = Mock(Multistream) {

View File

@@ -30,7 +30,7 @@ class EthereumLegacyFeesSpec extends Specification {
it.gasPrice = Wei.ofUnits(8, Wei.Unit.GWEI) it.gasPrice = Wei.ofUnits(8, Wei.Unit.GWEI)
} }
def fees = new EthereumLegacyFees(Stub(EthereumMultistream), Stub(EthereumReader), 10) def fees = new EthereumLegacyFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
when: when:
def act = fees.extractFee(block, tx) def act = fees.extractFee(block, tx)
then: then:

View File

@@ -44,7 +44,7 @@ class EthereumPriorityFeesSpec extends Specification {
it.maxPriorityFeePerGas = Wei.ofUnits(5.0001, Wei.Unit.GWEI) it.maxPriorityFeePerGas = Wei.ofUnits(5.0001, Wei.Unit.GWEI)
} }
def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumReader), 10) def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
when: when:
def act = fees.extractFee(block, tx) def act = fees.extractFee(block, tx)
then: then:
@@ -66,7 +66,7 @@ class EthereumPriorityFeesSpec extends Specification {
it.gasPrice = Wei.from("0x198286458f") it.gasPrice = Wei.from("0x198286458f")
} }
def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumReader), 10) def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
when: when:
def act = fees.extractFee(block, tx) def act = fees.extractFee(block, tx)
then: then:
@@ -83,7 +83,7 @@ class EthereumPriorityFeesSpec extends Specification {
new EthereumFees.EthereumFee(Wei.ofEthers(0.75), Wei.ofEthers(0.5), Wei.ofEthers(0.75), Wei.ofEthers(0.5)), new EthereumFees.EthereumFee(Wei.ofEthers(0.75), Wei.ofEthers(0.5), Wei.ofEthers(0.75), Wei.ofEthers(0.5)),
new EthereumFees.EthereumFee(Wei.ofEthers(0.6), Wei.ofEthers(0.2), Wei.ofEthers(0.6), Wei.ofEthers(0.5)), new EthereumFees.EthereumFee(Wei.ofEthers(0.6), Wei.ofEthers(0.2), Wei.ofEthers(0.6), Wei.ofEthers(0.5)),
] ]
def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumReader), 10) def fees = new EthereumPriorityFees(Stub(EthereumMultistream), Stub(EthereumCachingReader), 10)
when: when:
def act = Flux.fromIterable(inputs) def act = Flux.fromIterable(inputs)
.transform(fees.feeAggregation(ChainFees.Mode.AVG_LAST)) .transform(fees.feeAggregation(ChainFees.Mode.AVG_LAST))
@@ -127,7 +127,7 @@ class EthereumPriorityFeesSpec extends Specification {
1 * getCurrentHeight() >> 13756007 1 * getCurrentHeight() >> 13756007
} }
} }
def reader = Mock(EthereumReader) { def reader = Mock(EthereumCachingReader) {
_ * it.blocksByHeightParsed() >> Mock(Reader) { _ * it.blocksByHeightParsed() >> Mock(Reader) {
1 * it.read(13756006) >> Mono.just(block1) 1 * it.read(13756006) >> Mono.just(block1)
1 * it.read(13756007) >> Mono.just(block2) 1 * it.read(13756007) >> Mono.just(block2)

View File

@@ -1,281 +0,0 @@
/**
* 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.ReceiptRedisCache
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.data.TxId
import io.emeraldpay.dshackle.test.EthereumPosRpcUpstreamMock
import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.etherjar.domain.Address
import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.etherjar.domain.TransactionId
import io.emeraldpay.etherjar.domain.Wei
import io.emeraldpay.etherjar.rpc.json.BlockJson
import io.emeraldpay.etherjar.rpc.json.TransactionJson
import io.emeraldpay.etherjar.rpc.json.TransactionRefJson
import org.apache.commons.collections4.Factory
import org.apache.commons.collections4.functors.ConstantFactory
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
}
Factory<CallMethods> calls = ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
def "Block by Id reads from cache"() {
setup:
def memCache = Mock(BlocksMemCache) {
1 * read(blockId) >> Mono.just(BlockContainer.from(blockJson))
}
def caches = Caches.newBuilder()
.setBlockByHash(memCache)
.build()
def reader = new EthereumReader(Stub(Multistream), caches, calls)
when:
def act = reader.blocksByIdParsed().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)
.build()
def api = TestingCommons.api()
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
def upstream = TestingCommons.multistream(api)
def reader = new EthereumReader(upstream, caches, calls)
when:
def act = reader.blocksByIdParsed().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)
.build()
def api = TestingCommons.api()
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
def upstream = TestingCommons.multistream(api)
def reader = new EthereumReader(upstream, caches, calls)
when:
def act = reader.blocksByIdParsed().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))
}
def caches = Caches.newBuilder()
.setBlockByHash(memCache)
.build()
def reader = new EthereumReader(Stub(Multistream), caches, calls)
when:
def act = reader.blocksByHashParsed().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)
.build()
def api = TestingCommons.api()
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
def upstream = TestingCommons.multistream(api)
def reader = new EthereumReader(upstream, caches, calls)
when:
def act = reader.blocksByHashParsed().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))
}
def caches = Caches.newBuilder()
.setTxByHash(memCache)
.build()
def reader = new EthereumReader(Stub(Multistream), caches, calls)
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)
.build()
def api = TestingCommons.api()
api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson)
def upstream = TestingCommons.multistream(api)
def reader = new EthereumReader(upstream, caches, calls)
when:
def act = reader.txByHash().read(txJson.hash).block()
then:
act == txJson
}
def "Caches balance until block mined"() {
setup:
def api = TestingCommons.api()
// no height
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0x10")
// height 101 + 1 => 102 => 0x66
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "0x66"], "0xff")
EthereumPosRpcUpstreamMock upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM, api)
def upstreams = TestingCommons.multistream(upstream)
def reader = new EthereumReader(upstreams, Caches.default(), calls)
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))
Thread.sleep(50)
act = reader.balance().read(Address.from("0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c")).block()
then:
act == Wei.from("0xff")
}
def "Read receipt from upstream if cache is empty"() {
setup:
def api = TestingCommons.api()
api.answerOnce("eth_getTransactionReceipt", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"], [
transactionHash: "0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"
])
EthereumPosRpcUpstreamMock upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM, api)
def upstreams = TestingCommons.multistream(upstream)
def reader = new EthereumReader(upstreams, Caches.default(), calls)
reader.start()
when:
def act = reader.receipts().read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")).block()
then:
act != null
new String(act) == '{"transactionHash":"0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"}'
}
def "Read receipt from cache if available"() {
setup:
def api = TestingCommons.api()
EthereumPosRpcUpstreamMock upstream = new EthereumPosRpcUpstreamMock(Chain.ETHEREUM, api)
def upstreams = TestingCommons.multistream(upstream)
def receiptCache = Mock(ReceiptRedisCache) {
1 * it.read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")) >>
Mono.just('{"transactionHash":"0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"}'.bytes)
}
def cashes = Caches.newBuilder()
.setReceipts(receiptCache)
.build()
def reader = new EthereumReader(upstreams, cashes, calls)
reader.start()
when:
def act = reader.receipts().read(TxId.from("0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2")).block()
then:
act != null
new String(act) == '{"transactionHash":"0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2"}'
api.calls.get() == 0
}
}

View File

@@ -23,7 +23,7 @@ class LocalCallRouterSpec extends Specification {
setup: setup:
def methods = new DefaultEthereumMethods(Chain.ETHEREUM) def methods = new DefaultEthereumMethods(Chain.ETHEREUM)
def router = new LocalCallRouter( def router = new LocalCallRouter(
new EthereumReader( new EthereumCachingReader(
TestingCommons.multistream(TestingCommons.api()), TestingCommons.multistream(TestingCommons.api()),
Caches.default(), Caches.default(),
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM)) ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
@@ -41,7 +41,7 @@ class LocalCallRouterSpec extends Specification {
setup: setup:
def methods = new DefaultEthereumMethods(Chain.ETHEREUM) def methods = new DefaultEthereumMethods(Chain.ETHEREUM)
def router = new LocalCallRouter( def router = new LocalCallRouter(
new EthereumReader( new EthereumCachingReader(
TestingCommons.multistream(TestingCommons.api()), TestingCommons.multistream(TestingCommons.api()),
Caches.default(), Caches.default(),
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM)) ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
@@ -61,7 +61,7 @@ class LocalCallRouterSpec extends Specification {
def head = Mock(Head) { def head = Mock(Head) {
1 * getCurrentHeight() >> 101L 1 * getCurrentHeight() >> 101L
} }
def reader = Mock(EthereumReader) { def reader = Mock(EthereumCachingReader) {
_ * blocksByIdAsCont() >> new EmptyReader<>() _ * blocksByIdAsCont() >> new EmptyReader<>()
_ * txByHashAsCont() >> new EmptyReader<>() _ * txByHashAsCont() >> new EmptyReader<>()
1 * blocksByHeightAsCont() >> Mock(Reader) { 1 * blocksByHeightAsCont() >> Mock(Reader) {
@@ -87,7 +87,7 @@ class LocalCallRouterSpec extends Specification {
def "getBlockByNumber with earliest uses 0 block"() { def "getBlockByNumber with earliest uses 0 block"() {
setup: setup:
def head = Stub(Head) {} def head = Stub(Head) {}
def reader = Mock(EthereumReader) { def reader = Mock(EthereumCachingReader) {
_ * blocksByIdAsCont() >> new EmptyReader<>() _ * blocksByIdAsCont() >> new EmptyReader<>()
_ * txByHashAsCont() >> new EmptyReader<>() _ * txByHashAsCont() >> new EmptyReader<>()
1 * blocksByHeightAsCont() >> Mock(Reader) { 1 * blocksByHeightAsCont() >> Mock(Reader) {
@@ -113,7 +113,7 @@ class LocalCallRouterSpec extends Specification {
def "getBlockByNumber fetches the block"() { def "getBlockByNumber fetches the block"() {
setup: setup:
def head = Stub(Head) {} def head = Stub(Head) {}
def reader = Mock(EthereumReader) { def reader = Mock(EthereumCachingReader) {
_ * blocksByIdAsCont() >> new EmptyReader<>() _ * blocksByIdAsCont() >> new EmptyReader<>()
_ * txByHashAsCont() >> new EmptyReader<>() _ * txByHashAsCont() >> new EmptyReader<>()
1 * blocksByHeightAsCont() >> Mock(Reader) { 1 * blocksByHeightAsCont() >> Mock(Reader) {