problem: quorum is not applied for internal reads

This commit is contained in:
Igor Artamonov
2020-05-26 22:43:58 -04:00
parent 5584858e44
commit 948c34a0c7
10 changed files with 353 additions and 136 deletions

View File

@@ -20,7 +20,7 @@ import io.emeraldpay.dshackle.upstream.ApiSource
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
// creates instance of a Quorum based reader // creates instance of a Quorum based reader
interface QuorumReaderFactory { open interface QuorumReaderFactory {
companion object { companion object {
fun default(): QuorumReaderFactory { fun default(): QuorumReaderFactory {
@@ -28,7 +28,7 @@ interface QuorumReaderFactory {
} }
} }
fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result> open fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result>
class Default : QuorumReaderFactory { class Default : QuorumReaderFactory {
override fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result> { override fun create(apis: ApiSource, quorum: CallQuorum): Reader<JsonRpcRequest, QuorumRpcReader.Result> {

View File

@@ -16,9 +16,6 @@
*/ */
package io.emeraldpay.dshackle.upstream package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import org.reactivestreams.Publisher import org.reactivestreams.Publisher
interface ApiSource : Publisher<Upstream> { interface ApiSource : Publisher<Upstream> {

View File

@@ -21,6 +21,7 @@ import io.emeraldpay.dshackle.BlockchainType
import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.cache.CachesEnabled
import io.emeraldpay.dshackle.cache.CachesFactory import io.emeraldpay.dshackle.cache.CachesFactory
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.startup.UpstreamChange import io.emeraldpay.dshackle.startup.UpstreamChange
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinMultistream
import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream

View File

@@ -24,6 +24,8 @@ import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.grpc.Chain import io.emeraldpay.grpc.Chain
import org.apache.commons.collections4.Factory
import org.apache.commons.collections4.FunctorException
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.context.Lifecycle import org.springframework.context.Lifecycle
import reactor.core.Disposable import reactor.core.Disposable
@@ -52,6 +54,9 @@ abstract class Multistream(
private var cacheSubscription: Disposable? = null private var cacheSubscription: Disposable? = null
private val reconfigLock = ReentrantLock() private val reconfigLock = ReentrantLock()
private var callMethods: CallMethods? = null private var callMethods: CallMethods? = null
private var callMethodsFactory: Factory<CallMethods> = Factory {
return@Factory callMethods ?: throw FunctorException("Not initialized yet")
}
private var seq = 0 private var seq = 0
protected var lagObserver: HeadLagObserver? = null protected var lagObserver: HeadLagObserver? = null
private var subscription: Disposable? = null private var subscription: Disposable? = null
@@ -86,7 +91,7 @@ abstract class Multistream(
/** /**
* Get a source for direct APIs * Get a source for direct APIs
*/ */
fun getApiSource(matcher: Selector.Matcher): ApiSource { open fun getApiSource(matcher: Selector.Matcher): ApiSource {
val i = seq++ val i = seq++
if (seq >= Int.MAX_VALUE / 2) { if (seq >= Int.MAX_VALUE / 2) {
seq = 0 seq = 0
@@ -117,6 +122,7 @@ abstract class Multistream(
fun onUpstreamsUpdated() { fun onUpstreamsUpdated() {
reconfigLock.withLock { reconfigLock.withLock {
getAll().map { it.getMethods() }.let { getAll().map { it.getMethods() }.let {
//TODO made list of uniq instances, and then if only one, just use it directly
callMethods = AggregatedCallMethods(it) callMethods = AggregatedCallMethods(it)
} }
} }
@@ -147,6 +153,10 @@ abstract class Multistream(
return callMethods ?: throw IllegalStateException("Methods are not initialized yet") return callMethods ?: throw IllegalStateException("Methods are not initialized yet")
} }
fun getMethodsFactory(): Factory<CallMethods> {
return callMethodsFactory
}
override fun start() { override fun start() {
subscription = observeStatus() subscription = observeStatus()
.distinctUntilChanged() .distinctUntilChanged()

View File

@@ -0,0 +1,134 @@
package io.emeraldpay.dshackle.upstream.ethereum
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.Defaults
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CurrentBlockCache
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.data.TxContainer
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.infinitape.etherjar.domain.Address
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.domain.Wei
import io.infinitape.etherjar.hex.HexQuantity
import io.infinitape.etherjar.rpc.RpcException
import io.infinitape.etherjar.rpc.RpcResponseError
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionJson
import io.infinitape.etherjar.rpc.json.TransactionRefJson
import org.apache.commons.collections4.Factory
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
import reactor.util.retry.Retry
import java.time.Duration
import java.util.concurrent.TimeoutException
/**
* Common reads from upstream, makes actual calls with applying quorum and retries
*/
class EthereumDirectReader(
private val up: Multistream,
private val caches: Caches,
private val balanceCache: CurrentBlockCache<Address, Wei>,
private val callMethodsFactory: Factory<CallMethods>
) {
companion object {
private val log = LoggerFactory.getLogger(EthereumDirectReader::class.java)
}
private val objectMapper: ObjectMapper = Global.objectMapper
open var quorumReaderFactory: QuorumReaderFactory = QuorumReaderFactory.default()
val blockReader: Reader<BlockHash, BlockContainer>
val blockByHeightReader: Reader<Long, BlockContainer>
val txReader: Reader<TransactionId, TxContainer>
val balanceReader: Reader<Address, Wei>
init {
blockReader = object : Reader<BlockHash, BlockContainer> {
override fun read(key: BlockHash): Mono<BlockContainer> {
val request = JsonRpcRequest("eth_getBlockByHash", listOf(key.toHex(), false))
return readBlock(request, key.toHex())
}
}
blockByHeightReader = object : Reader<Long, BlockContainer> {
override fun read(key: Long): Mono<BlockContainer> {
val request = JsonRpcRequest("eth_getBlockByNumber", listOf(HexQuantity.from(key).toHex(), false))
return readBlock(request, key.toString())
}
}
txReader = object : Reader<TransactionId, TxContainer> {
override fun read(key: TransactionId): Mono<TxContainer> {
val request = JsonRpcRequest("eth_getTransactionByHash", listOf(key.toHex()))
return readWithQuorum(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Tx not read $key")))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.flatMap { txbytes ->
val tx = objectMapper.readValue(txbytes, TransactionJson::class.java)
if (tx == null) {
Mono.empty()
} else {
Mono.just(TxContainer.from(tx, txbytes))
}
}
.doOnNext { tx ->
if (tx.blockId != null) {
caches.cache(Caches.Tag.REQUESTED, tx)
}
}
}
}
balanceReader = object : Reader<Address, Wei> {
override fun read(key: Address): Mono<Wei> {
val request = JsonRpcRequest("eth_getBalance", listOf(key.toHex(), "latest"))
return readWithQuorum(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key")))
.map {
val str = String(it)
// it's a json string, i.e. wrapped with quotes, ex. _"0x1234"_
if (str.startsWith("\"") && str.endsWith("\"")) {
Wei.from(str.substring(1, str.length - 1))
} else {
throw RpcException(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Not Wei value")
}
}
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.doOnNext { value ->
balanceCache.put(key, value)
}
}
}
}
private fun readBlock(request: JsonRpcRequest, id: String): Mono<BlockContainer> {
return readWithQuorum(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $id")))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.map { blockbytes ->
val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson<TransactionRefJson>
BlockContainer.from(block, blockbytes)
}
.doOnNext { block ->
caches.cache(Caches.Tag.REQUESTED, block)
}
}
/**
* Read from an Upstream applying a Quorum specific for that request
*/
private fun readWithQuorum(request: JsonRpcRequest): Mono<ByteArray> {
return quorumReaderFactory
.create(up.getApiSource(Selector.empty), callMethodsFactory.create().getQuorumFor(request.method))
.read(request)
.map { it.value }
}
}

View File

@@ -16,8 +16,6 @@
*/ */
package io.emeraldpay.dshackle.upstream.ethereum package io.emeraldpay.dshackle.upstream.ethereum
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.reader.Reader
@@ -39,10 +37,9 @@ open class EthereumMultistream(
private val log = LoggerFactory.getLogger(EthereumMultistream::class.java) private val log = LoggerFactory.getLogger(EthereumMultistream::class.java)
} }
private val objectMapper: ObjectMapper = Global.objectMapper
private var head: Head? = null private var head: Head? = null
private val reader: EthereumReader = EthereumReader(this, this.caches) private val reader: EthereumReader = EthereumReader(this, this.caches, getMethodsFactory())
init { init {
this.init() this.init()

View File

@@ -16,38 +16,32 @@
package io.emeraldpay.dshackle.upstream.ethereum package io.emeraldpay.dshackle.upstream.ethereum
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.Defaults
import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CurrentBlockCache import io.emeraldpay.dshackle.cache.CurrentBlockCache
import io.emeraldpay.dshackle.data.* import io.emeraldpay.dshackle.data.*
import io.emeraldpay.dshackle.reader.* import io.emeraldpay.dshackle.reader.*
import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.infinitape.etherjar.domain.Address import io.infinitape.etherjar.domain.Address
import io.infinitape.etherjar.domain.BlockHash import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.domain.Wei import io.infinitape.etherjar.domain.Wei
import io.infinitape.etherjar.hex.HexQuantity
import io.infinitape.etherjar.rpc.RpcException
import io.infinitape.etherjar.rpc.RpcResponseError
import io.infinitape.etherjar.rpc.json.BlockJson import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionJson import io.infinitape.etherjar.rpc.json.TransactionJson
import io.infinitape.etherjar.rpc.json.TransactionRefJson import io.infinitape.etherjar.rpc.json.TransactionRefJson
import org.apache.commons.collections4.Factory
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.context.Lifecycle import org.springframework.context.Lifecycle
import reactor.core.Disposable
import reactor.core.publisher.Mono
import reactor.util.retry.Retry
import java.time.Duration
import java.util.concurrent.TimeoutException
import java.util.function.Function import java.util.function.Function
/**
* Reader for the common operations, that wraps caches + native call with quorum verification
*/
open class EthereumReader( open class EthereumReader(
private val up: Multistream, private val up: Multistream,
private val caches: Caches private val caches: Caches,
private val callMethodsFactory: Factory<CallMethods>
) : Lifecycle { ) : Lifecycle {
companion object { companion object {
@@ -56,6 +50,7 @@ open class EthereumReader(
private val objectMapper: ObjectMapper = Global.objectMapper private val objectMapper: ObjectMapper = Global.objectMapper
private val balanceCache = CurrentBlockCache<Address, Wei>() private val balanceCache = CurrentBlockCache<Address, Wei>()
private val directReader = EthereumDirectReader(up, caches, balanceCache, callMethodsFactory)
val extractBlock = Function<BlockContainer, BlockJson<TransactionRefJson>> { block -> val extractBlock = Function<BlockContainer, BlockJson<TransactionRefJson>> { block ->
val existing = block.getParsed(BlockJson::class.java) val existing = block.getParsed(BlockJson::class.java)
@@ -87,115 +82,17 @@ open class EthereumReader(
TxContainer.from(tx) TxContainer.from(tx)
} }
private val blocksDirect: Reader<BlockHash, BlockContainer>
private val blocksByHeightDirect: Reader<Long, BlockContainer>
private val txDirect: Reader<TransactionId, TxContainer>
private val balanceDirect: Reader<Address, Wei>
private val idToBlockHash = Function<BlockId, BlockHash> { id -> BlockHash.from(id.value) } private val idToBlockHash = Function<BlockId, BlockHash> { id -> BlockHash.from(id.value) }
private val blockHashToId = Function<BlockHash, BlockId> { hash -> BlockId.from(hash) } private val blockHashToId = Function<BlockHash, BlockId> { hash -> BlockId.from(hash) }
private val txHashToId = Function<TransactionId, TxId> { hash -> TxId.from(hash) } private val txHashToId = Function<TransactionId, TxId> { hash -> TxId.from(hash) }
private val idToTxHash = Function<TxId, TransactionId> { id -> TransactionId.from(id.value) } private val idToTxHash = Function<TxId, TransactionId> { id -> TransactionId.from(id.value) }
private val directResponseBytes = Function<JsonRpcResponse, ByteArray> { resp ->
if (resp.error != null) {
throw resp.error.asException()
} else {
resp.getResult()
}
}
init {
blocksDirect = object : Reader<BlockHash, BlockContainer> {
override fun read(key: BlockHash): Mono<BlockContainer> {
return up.getDirectApi(Selector.empty).flatMap { api ->
val request = JsonRpcRequest("eth_getBlockByHash", listOf(key.toHex(), false))
api.read(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $key")))
.map(directResponseBytes)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.map { blockbytes ->
val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson<TransactionRefJson>
BlockContainer.from(block, blockbytes)
}
.doOnNext { block ->
caches.cache(Caches.Tag.REQUESTED, block)
}
}
}
}
blocksByHeightDirect = object : Reader<Long, BlockContainer> {
override fun read(key: Long): Mono<BlockContainer> {
return up.getDirectApi(Selector.empty).flatMap { api ->
val request = JsonRpcRequest("eth_getBlockByNumber", listOf(HexQuantity.from(key).toHex(), false))
api.read(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $key")))
.map(directResponseBytes)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.map { blockbytes ->
val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson<TransactionRefJson>
BlockContainer.from(block, blockbytes)
}
.doOnNext { block ->
caches.cache(Caches.Tag.REQUESTED, block)
}
}
}
}
txDirect = object : Reader<TransactionId, TxContainer> {
override fun read(key: TransactionId): Mono<TxContainer> {
return up.getDirectApi(Selector.empty).flatMap { api ->
val request = JsonRpcRequest("eth_getTransactionByHash", listOf(key.toHex()))
api.read(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Tx not read $key")))
.map(directResponseBytes)
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.flatMap { txbytes ->
val tx = objectMapper.readValue(txbytes, TransactionJson::class.java)
if (tx == null) {
Mono.empty()
} else {
Mono.just(TxContainer.from(tx, txbytes))
}
}
.doOnNext { tx ->
if (tx.blockId != null) {
caches.cache(Caches.Tag.REQUESTED, tx)
}
}
}
}
}
balanceDirect = object : Reader<Address, Wei> {
override fun read(key: Address): Mono<Wei> {
return up.getDirectApi(Selector.empty).flatMap { api ->
val request = JsonRpcRequest("eth_getBalance", listOf(key.toHex(), "latest"))
api.read(request)
.timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Balance not read $key")))
.map(directResponseBytes)
.map {
val str = String(it)
if (str.startsWith("\"") && str.endsWith("\"")) {
Wei.from(str.substring(1, str.length - 1))
} else {
throw RpcException(RpcResponseError.CODE_UPSTREAM_INVALID_RESPONSE, "Not Wei value")
}
}
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
.doOnNext { value ->
balanceCache.put(key, value)
}
}
}
}
}
fun blocksByHash(): Reader<BlockHash, BlockJson<TransactionRefJson>> { fun blocksByHash(): Reader<BlockHash, BlockJson<TransactionRefJson>> {
return TransformingReader( return TransformingReader(
CompoundReader( CompoundReader(
RekeyingReader(blockHashToId, caches.getBlocksByHash()), RekeyingReader(blockHashToId, caches.getBlocksByHash()),
blocksDirect directReader.blockReader
), ),
extractBlock extractBlock
) )
@@ -205,7 +102,7 @@ open class EthereumReader(
return TransformingReader( return TransformingReader(
CompoundReader( CompoundReader(
caches.getBlocksByHash(), caches.getBlocksByHash(),
RekeyingReader(idToBlockHash, blocksDirect) RekeyingReader(idToBlockHash, directReader.blockReader)
), ),
extractBlock extractBlock
) )
@@ -228,7 +125,7 @@ open class EthereumReader(
fun blocksByHeightAsCont(): Reader<Long, BlockContainer> { fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
return CompoundReader( return CompoundReader(
caches.getBlocksByHeight(), caches.getBlocksByHeight(),
blocksByHeightDirect directReader.blockByHeightReader
) )
} }
@@ -236,7 +133,7 @@ open class EthereumReader(
return TransformingReader( return TransformingReader(
CompoundReader( CompoundReader(
RekeyingReader(txHashToId, caches.getTxByHash()), RekeyingReader(txHashToId, caches.getTxByHash()),
txDirect directReader.txReader
), ),
extractTx extractTx
) )
@@ -245,13 +142,13 @@ open class EthereumReader(
fun txByHashAsCont(): Reader<TxId, TxContainer> { fun txByHashAsCont(): Reader<TxId, TxContainer> {
return CompoundReader( return CompoundReader(
caches.getTxByHash(), caches.getTxByHash(),
RekeyingReader(idToTxHash, txDirect) RekeyingReader(idToTxHash, directReader.txReader)
) )
} }
fun balance(): Reader<Address, Wei> { fun balance(): Reader<Address, Wei> {
return CompoundReader( return CompoundReader(
balanceCache, balanceDirect balanceCache, directReader.balanceReader
) )
} }

View File

@@ -0,0 +1,174 @@
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CurrentBlockCache
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.quorum.QuorumRpcReader
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.ApiSource
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.domain.Address
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.domain.Wei
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionJson
import org.apache.commons.collections4.Factory
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
import java.time.Instant
class EthereumDirectReaderSpec extends Specification {
String hash1 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
String address1 = "0xe0aadb0a012dbcdc529c4c743d3e0385a0b54d3d"
def "Reads block by hash"() {
setup:
def json = new BlockJson().tap {
number = 100
hash = BlockHash.from(hash1)
timestamp = Instant.now()
totalDifficulty = BigInteger.ONE
transactions = []
}
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
}
EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls
)
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), 1
)
)
}
}
when:
def act = reader.blockReader.read(BlockHash.from(hash1))
then:
StepVerifier.create(act)
.expectNextMatches { block ->
block.hash.toHexWithPrefix() == hash1
}
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "Reads block by height"() {
setup:
def json = new BlockJson().tap {
number = 100
hash = BlockHash.from(hash1)
timestamp = Instant.now()
totalDifficulty = BigInteger.ONE
transactions = []
}
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
}
EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls
)
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just(
new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), 1
)
)
}
}
when:
def act = reader.blockByHeightReader.read(100)
then:
StepVerifier.create(act)
.expectNextMatches { block ->
block.hash.toHexWithPrefix() == hash1
}
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "Reads tx"() {
setup:
def json = new TransactionJson().tap {
hash = TransactionId.from(hash1)
blockNumber = 100
blockHash = BlockHash.from(hash1)
}
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
}
EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls
)
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), 1
)
)
}
}
when:
def act = reader.txReader.read(TransactionId.from(hash1))
then:
StepVerifier.create(act)
.expectNextMatches { block ->
block.hash.toHexWithPrefix() == hash1
}
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "Reads balance"() {
setup:
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM)
}
EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls
)
reader.quorumReaderFactory = Mock(QuorumReaderFactory) {
1 * create(_, _) >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just(
new QuorumRpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), 1
)
)
}
}
when:
def act = reader.balanceReader.read(Address.from(address1))
then:
StepVerifier.create(act)
.expectNext(Wei.from("0x100"))
.expectComplete()
.verify(Duration.ofSeconds(1))
}
}

View File

@@ -24,6 +24,8 @@ import io.emeraldpay.dshackle.data.TxContainer
import io.emeraldpay.dshackle.test.EthereumUpstreamMock import io.emeraldpay.dshackle.test.EthereumUpstreamMock
import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.grpc.Chain import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.domain.Address import io.infinitape.etherjar.domain.Address
import io.infinitape.etherjar.domain.BlockHash import io.infinitape.etherjar.domain.BlockHash
@@ -32,6 +34,8 @@ import io.infinitape.etherjar.domain.Wei
import io.infinitape.etherjar.rpc.json.BlockJson import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionJson import io.infinitape.etherjar.rpc.json.TransactionJson
import io.infinitape.etherjar.rpc.json.TransactionRefJson import io.infinitape.etherjar.rpc.json.TransactionRefJson
import org.apache.commons.collections4.Factory
import org.apache.commons.collections4.functors.ConstantFactory
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import spock.lang.Specification import spock.lang.Specification
@@ -54,6 +58,7 @@ class EthereumReaderSpec extends Specification {
json.blockHash = blockJson.hash json.blockHash = blockJson.hash
json.blockNumber = blockJson.number json.blockNumber = blockJson.number
} }
Factory<CallMethods> calls = ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
def "Block by Id reads from cache"() { def "Block by Id reads from cache"() {
setup: setup:
@@ -63,7 +68,7 @@ class EthereumReaderSpec extends Specification {
def caches = Caches.newBuilder() def caches = Caches.newBuilder()
.setBlockByHash(memCache) .setBlockByHash(memCache)
.build() .build()
def reader = new EthereumReader(Stub(Multistream), caches) def reader = new EthereumReader(Stub(Multistream), caches, calls)
when: when:
def act = reader.blocksById().read(blockId).block() def act = reader.blocksById().read(blockId).block()
@@ -84,7 +89,7 @@ class EthereumReaderSpec extends Specification {
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson) api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
def upstream = TestingCommons.aggregatedUpstream(api) def upstream = TestingCommons.aggregatedUpstream(api)
def reader = new EthereumReader(upstream, caches) def reader = new EthereumReader(upstream, caches, calls)
when: when:
def act = reader.blocksById().read(blockId).block() def act = reader.blocksById().read(blockId).block()
@@ -105,7 +110,7 @@ class EthereumReaderSpec extends Specification {
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson) api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
def upstream = TestingCommons.aggregatedUpstream(api) def upstream = TestingCommons.aggregatedUpstream(api)
def reader = new EthereumReader(upstream, caches) def reader = new EthereumReader(upstream, caches, calls)
when: when:
def act = reader.blocksById().read(blockId).block() def act = reader.blocksById().read(blockId).block()
@@ -122,7 +127,7 @@ class EthereumReaderSpec extends Specification {
def caches = Caches.newBuilder() def caches = Caches.newBuilder()
.setBlockByHash(memCache) .setBlockByHash(memCache)
.build() .build()
def reader = new EthereumReader(Stub(Multistream), caches) def reader = new EthereumReader(Stub(Multistream), caches, calls)
when: when:
def act = reader.blocksByHash().read(blockJson.hash).block() def act = reader.blocksByHash().read(blockJson.hash).block()
@@ -142,7 +147,7 @@ class EthereumReaderSpec extends Specification {
def api = TestingCommons.api() def api = TestingCommons.api()
api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson) api.answer("eth_getBlockByHash", ["0xf85b826fdf98ee0f48f7db001be00472e63ceb056846f4ecac5f0c32878b8ab2", false], blockJson)
def upstream = TestingCommons.aggregatedUpstream(api) def upstream = TestingCommons.aggregatedUpstream(api)
def reader = new EthereumReader(upstream, caches) def reader = new EthereumReader(upstream, caches, calls)
when: when:
def act = reader.blocksByHash().read(blockJson.hash).block() def act = reader.blocksByHash().read(blockJson.hash).block()
@@ -159,7 +164,7 @@ class EthereumReaderSpec extends Specification {
def caches = Caches.newBuilder() def caches = Caches.newBuilder()
.setTxByHash(memCache) .setTxByHash(memCache)
.build() .build()
def reader = new EthereumReader(Stub(Multistream), caches) def reader = new EthereumReader(Stub(Multistream), caches, calls)
when: when:
def act = reader.txByHash().read(txJson.hash).block() def act = reader.txByHash().read(txJson.hash).block()
@@ -180,7 +185,7 @@ class EthereumReaderSpec extends Specification {
def api = TestingCommons.api() def api = TestingCommons.api()
api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson) api.answer("eth_getTransactionByHash", [txJson.hash.toHex()], txJson)
def upstream = TestingCommons.aggregatedUpstream(api) def upstream = TestingCommons.aggregatedUpstream(api)
def reader = new EthereumReader(upstream, caches) def reader = new EthereumReader(upstream, caches, calls)
when: when:
def act = reader.txByHash().read(txJson.hash).block() def act = reader.txByHash().read(txJson.hash).block()
@@ -196,7 +201,7 @@ class EthereumReaderSpec extends Specification {
api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff") api.answerOnce("eth_getBalance", ["0x70b91ff87a902b53dc6e2f6bda8bb9b330ccd30c", "latest"], "0xff")
EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api) EthereumUpstreamMock upstream = new EthereumUpstreamMock(Chain.ETHEREUM, api)
def upstreams = TestingCommons.aggregatedUpstream(upstream) def upstreams = TestingCommons.aggregatedUpstream(upstream)
def reader = new EthereumReader(upstreams, Caches.default()) def reader = new EthereumReader(upstreams, Caches.default(), calls)
reader.start() reader.start()
when: when:

View File

@@ -5,6 +5,7 @@ import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.grpc.Chain import io.emeraldpay.grpc.Chain
import org.apache.commons.collections4.functors.ConstantFactory
import spock.lang.Specification import spock.lang.Specification
import java.time.Duration import java.time.Duration
@@ -17,7 +18,8 @@ class NativeCallRouterSpec extends Specification {
def router = new NativeCallRouter( def router = new NativeCallRouter(
new EthereumReader( new EthereumReader(
TestingCommons.aggregatedUpstream(TestingCommons.api()), TestingCommons.aggregatedUpstream(TestingCommons.api()),
Caches.default() Caches.default(),
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
), ),
methods methods
) )