Add BroadcastReader instead of BroadcastQuorum (#263)

This commit is contained in:
KirillPamPam
2023-08-01 17:26:31 +04:00
committed by GitHub
parent 6f576fc20c
commit 674c69f4d3
12 changed files with 497 additions and 207 deletions

View File

@@ -7,16 +7,15 @@ const val SPAN_STATUS_MESSAGE = "status.message"
const val SPAN_READER_RESULT = "reader.result" const val SPAN_READER_RESULT = "reader.result"
const val SPAN_REQUEST_API_TYPE = "request.api.type" const val SPAN_REQUEST_API_TYPE = "request.api.type"
const val SPAN_REQUEST_UPSTREAM_ID = "request.upstreamId" const val SPAN_REQUEST_UPSTREAM_ID = "request.upstreamId"
const val SPAN_RESPONSE_UPSTREAM_ID = "response.upstreamId"
const val SPAN_REQUEST_ID = "request.id" const val SPAN_REQUEST_ID = "request.id"
const val SPAN_NO_RESPONSE_MESSAGE = "no-response.message" const val SPAN_NO_RESPONSE_MESSAGE = "no-response.message"
const val LOCAL_READER = "localReader" const val LOCAL_READER = "localReader"
const val REMOTE_QUORUM_RPC_READER = "remoteQuorumRpcReader" const val RPC_READER = "rpcReader"
const val API_READER = "apiReader" const val API_READER = "apiReader"
const val CACHE_BLOCK_BY_HASH_READER = "cacheBlockByHashReader" const val CACHE_BLOCK_BY_HASH_READER = "cacheBlockByHashReader"
const val DIRECT_QUORUM_RPC_READER = "directQuorumRpcReader" const val DIRECT_QUORUM_RPC_READER = "directQuorumRpcReader"
const val CACHE_HEIGHT_BY_HASH_READER = "cacheHeightByHashReader"
const val CACHE_BLOCK_BY_HEIGHT_READER = "cacheBlockByHeightReader" const val CACHE_BLOCK_BY_HEIGHT_READER = "cacheBlockByHeightReader"
const val CACHE_TX_BY_HASH_READER = "cacheTxByHashReader" const val CACHE_TX_BY_HASH_READER = "cacheTxByHashReader"
const val CACHE_RECEIPTS_READER = "cacheReceiptsReader" const val CACHE_RECEIPTS_READER = "cacheReceiptsReader"
const val BROADCAST_READER = "broadcastReader"

View File

@@ -1,45 +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.quorum
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.ApiSource
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
import org.springframework.cloud.sleuth.Tracer
import java.util.concurrent.atomic.AtomicInteger
interface QuorumReader : Reader<JsonRpcRequest, QuorumRpcReader.Result> {
fun attempts(): AtomicInteger
}
// creates instance of a Quorum based reader
interface QuorumReaderFactory {
companion object {
fun default(): QuorumReaderFactory {
return Default()
}
}
fun create(apis: ApiSource, quorum: CallQuorum, signer: ResponseSigner?, tracer: Tracer): QuorumReader
class Default : QuorumReaderFactory {
override fun create(apis: ApiSource, quorum: CallQuorum, signer: ResponseSigner?, tracer: Tracer): QuorumReader {
return QuorumRpcReader(apis, quorum, signer, tracer)
}
}
}

View File

@@ -20,10 +20,10 @@ import io.emeraldpay.dshackle.commons.API_READER
import io.emeraldpay.dshackle.commons.SPAN_NO_RESPONSE_MESSAGE import io.emeraldpay.dshackle.commons.SPAN_NO_RESPONSE_MESSAGE
import io.emeraldpay.dshackle.commons.SPAN_REQUEST_API_TYPE import io.emeraldpay.dshackle.commons.SPAN_REQUEST_API_TYPE
import io.emeraldpay.dshackle.commons.SPAN_REQUEST_UPSTREAM_ID import io.emeraldpay.dshackle.commons.SPAN_REQUEST_UPSTREAM_ID
import io.emeraldpay.dshackle.reader.RpcReader
import io.emeraldpay.dshackle.reader.SpannedReader import io.emeraldpay.dshackle.reader.SpannedReader
import io.emeraldpay.dshackle.upstream.ApiSource import io.emeraldpay.dshackle.upstream.ApiSource
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
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
@@ -47,9 +47,9 @@ import java.util.function.Function
class QuorumRpcReader( class QuorumRpcReader(
private val apiControl: ApiSource, private val apiControl: ApiSource,
private val quorum: CallQuorum, private val quorum: CallQuorum,
private val signer: ResponseSigner?, signer: ResponseSigner?,
private val tracer: Tracer private val tracer: Tracer
) : QuorumReader { ) : RpcReader(signer) {
companion object { companion object {
private val log = LoggerFactory.getLogger(QuorumRpcReader::class.java) private val log = LoggerFactory.getLogger(QuorumRpcReader::class.java)
@@ -158,12 +158,7 @@ class QuorumRpcReader(
private fun withSignatureAndUpstream(api: Upstream, key: JsonRpcRequest, response: JsonRpcResponse): Function<Mono<ByteArray>, Mono<Tuple2<ByteArray, Optional<ResponseSigner.Signature>>>> { private fun withSignatureAndUpstream(api: Upstream, key: JsonRpcRequest, response: JsonRpcResponse): Function<Mono<ByteArray>, Mono<Tuple2<ByteArray, Optional<ResponseSigner.Signature>>>> {
return Function { src -> return Function { src ->
src.map { src.map {
val signature = response.providedSignature val signature = getSignature(key, response, api.getId())
?: if (key.nonce != null) {
signer?.sign(key.nonce, response.getResult(), api.getId())
} else {
null
}
Tuples.of(it, Optional.ofNullable(signature)) Tuples.of(it, Optional.ofNullable(signature))
} }
} }
@@ -176,14 +171,7 @@ class QuorumRpcReader(
// when the call failed with an error we want to notify the quorum because // when the call failed with an error we want to notify the quorum because
// it may use the error message or other details // it may use the error message or other details
// //
val cleanErr: JsonRpcException = when (err) { val cleanErr: JsonRpcException = getError(key, err)
is RpcException -> JsonRpcException.from(err)
is JsonRpcException -> err
else -> JsonRpcException(
JsonRpcResponse.NumberId(key.id),
JsonRpcError(-32603, "Unhandled internal error: ${err.javaClass}: ${err.message}")
)
}
quorum.record(cleanErr, null, api,) quorum.record(cleanErr, null, api,)
// if it's failed after that, then we don't need more calls, stop api source // if it's failed after that, then we don't need more calls, stop api source
if (quorum.isFailed()) { if (quorum.isFailed()) {
@@ -202,8 +190,7 @@ class QuorumRpcReader(
return Mono.just(quorum).flatMap { q -> return Mono.just(quorum).flatMap { q ->
if (q.isFailed()) { if (q.isFailed()) {
val resolvedBy = resolvedBy()?.getId() val resolvedBy = resolvedBy()?.getId()
val err = q.getError()?.asException(JsonRpcResponse.NumberId(key.id), resolvedBy) val err = handleError(q.getError(), key.id, resolvedBy)
?: JsonRpcException(JsonRpcResponse.NumberId(key.id), JsonRpcError(-32603, "Unhandled Upstream error"), resolvedBy)
log.warn("Quorum is failed. Method ${key.method}, message ${err.message}") log.warn("Quorum is failed. Method ${key.method}, message ${err.message}")
Mono.error(err) Mono.error(err)
} else { } else {
@@ -229,11 +216,4 @@ class QuorumRpcReader(
} }
} ?: Mono.error(RpcException(1, "Quorum [$q] is not resolved [isResolved - ${q.isResolved()}]")) } ?: Mono.error(RpcException(1, "Quorum [$q] is not resolved [isResolved - ${q.isResolved()}]"))
} }
class Result(
val value: ByteArray,
val signature: ResponseSigner.Signature?,
val quorum: Int,
val resolvedBy: Upstream?
)
} }

View File

@@ -0,0 +1,118 @@
package io.emeraldpay.dshackle.reader
import io.emeraldpay.dshackle.commons.BROADCAST_READER
import io.emeraldpay.dshackle.commons.SPAN_REQUEST_UPSTREAM_ID
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
import org.slf4j.LoggerFactory
import org.springframework.cloud.sleuth.Tracer
import reactor.core.publisher.Mono
import java.util.concurrent.atomic.AtomicInteger
class BroadcastReader(
private val upstreams: List<Upstream>,
matcher: Selector.Matcher,
signer: ResponseSigner?,
private val tracer: Tracer
) : RpcReader(signer) {
private val internalMatcher = Selector.MultiMatcher(
listOf(Selector.AvailabilityMatcher(), matcher)
)
companion object {
private val log = LoggerFactory.getLogger(BroadcastReader::class.java)
}
override fun attempts(): AtomicInteger {
return AtomicInteger(1)
}
override fun read(key: JsonRpcRequest): Mono<Result> {
return Mono.just(upstreams)
.map { ups ->
ups.filter { internalMatcher.matches(it) }.map { execute(key, it) }
}.flatMap {
Mono.zip(it) { responses ->
analyzeResponses(
key,
getJsonRpcResponses(key.method, responses)
)
}.onErrorResume { err ->
log.error("Broadcast error: ${err.message}")
Mono.error(handleError(null, 0, null))
}.flatMap { broadcastResult ->
if (broadcastResult.result != null) {
Mono.just(
Result(broadcastResult.result, broadcastResult.signature, 0, null)
)
} else {
val err = handleError(broadcastResult.error, key.id, null)
Mono.error(err)
}
}
}
}
private fun analyzeResponses(key: JsonRpcRequest, jsonRpcResponses: List<BroadcastResponse>): BroadcastResult {
val errors = mutableListOf<JsonRpcError>()
jsonRpcResponses.forEach {
val response = it.jsonRpcResponse
if (response.hasResult()) {
val signature = getSignature(key, response, it.upstreamId)
return BroadcastResult(response.getResult(), null, signature)
} else if (response.hasError()) {
errors.add(response.error!!)
}
}
val error = errors.takeIf { it.isNotEmpty() }?.get(0)
return BroadcastResult(error)
}
private fun getJsonRpcResponses(method: String, responses: Array<Any>) =
responses
.map { response ->
(response as BroadcastResponse)
.also { r ->
if (r.jsonRpcResponse.hasResult()) {
log.info(
"Response for $method from upstream ${r.upstreamId}: ${String(r.jsonRpcResponse.getResult())}"
)
}
}
}
private fun execute(
key: JsonRpcRequest,
upstream: Upstream
): Mono<BroadcastResponse> =
SpannedReader(
upstream.getIngressReader(), tracer, BROADCAST_READER, mapOf(SPAN_REQUEST_UPSTREAM_ID to upstream.getId())
)
.read(key)
.map { BroadcastResponse(it, upstream.getId()) }
.onErrorResume {
log.warn("Error during execution ${key.method} from upstream ${upstream.getId()} with message - ${it.message}")
Mono.just(
BroadcastResponse(JsonRpcResponse(null, getError(key, it).error), upstream.getId())
)
}
private class BroadcastResponse(
val jsonRpcResponse: JsonRpcResponse,
val upstreamId: String
)
private class BroadcastResult(
val result: ByteArray?,
val error: JsonRpcError?,
val signature: ResponseSigner.Signature?
) {
constructor(error: JsonRpcError?) : this(null, error, null)
}
}

View File

@@ -0,0 +1,81 @@
package io.emeraldpay.dshackle.reader
import io.emeraldpay.dshackle.quorum.CallQuorum
import io.emeraldpay.dshackle.quorum.QuorumRpcReader
import io.emeraldpay.dshackle.reader.RpcReader.Result
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
import io.emeraldpay.etherjar.rpc.RpcException
import org.springframework.cloud.sleuth.Tracer
import java.util.concurrent.atomic.AtomicInteger
abstract class RpcReader(
private val signer: ResponseSigner?,
) : Reader<JsonRpcRequest, Result> {
abstract fun attempts(): AtomicInteger
protected fun getError(key: JsonRpcRequest, err: Throwable) =
when (err) {
is RpcException -> JsonRpcException.from(err)
is JsonRpcException -> err
else -> JsonRpcException(
JsonRpcResponse.NumberId(key.id),
JsonRpcError(-32603, "Unhandled internal error: ${err.javaClass}: ${err.message}")
)
}
protected fun handleError(error: JsonRpcError?, id: Int, resolvedBy: String?) =
error?.asException(JsonRpcResponse.NumberId(id), resolvedBy)
?: JsonRpcException(JsonRpcResponse.NumberId(id), JsonRpcError(-32603, "Unhandled Upstream error"), resolvedBy)
protected fun getSignature(key: JsonRpcRequest, response: JsonRpcResponse, upstreamId: String) =
response.providedSignature
?: if (key.nonce != null) {
signer?.sign(key.nonce, response.getResult(), upstreamId)
} else {
null
}
class Result(
val value: ByteArray,
val signature: ResponseSigner.Signature?,
val quorum: Int,
val resolvedBy: Upstream?
)
}
interface RpcReaderFactory {
companion object {
fun default(): RpcReaderFactory {
return Default()
}
}
fun create(data: RpcReaderData): RpcReader
class Default : RpcReaderFactory {
override fun create(data: RpcReaderData): RpcReader {
if (data.method == "eth_sendRawTransaction") {
return BroadcastReader(data.multistream.getAll(), data.matcher, data.signer, data.tracer)
}
val apis = data.multistream.getApiSource(data.matcher)
return QuorumRpcReader(apis, data.quorum, data.signer, data.tracer)
}
}
data class RpcReaderData(
val multistream: Multistream,
val method: String,
val matcher: Selector.Matcher,
val quorum: CallQuorum,
val signer: ResponseSigner?,
val tracer: Tracer
)
}

View File

@@ -25,15 +25,16 @@ import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.Global.Companion.nullValue import io.emeraldpay.dshackle.Global.Companion.nullValue
import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.SilentException
import io.emeraldpay.dshackle.commons.LOCAL_READER import io.emeraldpay.dshackle.commons.LOCAL_READER
import io.emeraldpay.dshackle.commons.REMOTE_QUORUM_RPC_READER import io.emeraldpay.dshackle.commons.RPC_READER
import io.emeraldpay.dshackle.commons.SPAN_ERROR import io.emeraldpay.dshackle.commons.SPAN_ERROR
import io.emeraldpay.dshackle.commons.SPAN_REQUEST_ID import io.emeraldpay.dshackle.commons.SPAN_REQUEST_ID
import io.emeraldpay.dshackle.commons.SPAN_STATUS_MESSAGE import io.emeraldpay.dshackle.commons.SPAN_STATUS_MESSAGE
import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.config.MainConfig
import io.emeraldpay.dshackle.quorum.CallQuorum import io.emeraldpay.dshackle.quorum.CallQuorum
import io.emeraldpay.dshackle.quorum.NotLaggingQuorum import io.emeraldpay.dshackle.quorum.NotLaggingQuorum
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory import io.emeraldpay.dshackle.reader.RpcReader
import io.emeraldpay.dshackle.quorum.QuorumRpcReader import io.emeraldpay.dshackle.reader.RpcReaderFactory
import io.emeraldpay.dshackle.reader.RpcReaderFactory.RpcReaderData
import io.emeraldpay.dshackle.reader.SpannedReader import io.emeraldpay.dshackle.reader.SpannedReader
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
import io.emeraldpay.dshackle.upstream.ApiSource import io.emeraldpay.dshackle.upstream.ApiSource
@@ -79,7 +80,7 @@ open class NativeCall(
private val localRouterEnabled = config.cache?.requestsCacheEnabled ?: true private val localRouterEnabled = config.cache?.requestsCacheEnabled ?: true
private val passthrough = config.passthrough private val passthrough = config.passthrough
var quorumReaderFactory: QuorumReaderFactory = QuorumReaderFactory.default() var rpcReaderFactory: RpcReaderFactory = RpcReaderFactory.default()
private val ethereumCallSelectors = EnumMap<Chain, EthereumCallSelector>(Chain::class.java) private val ethereumCallSelectors = EnumMap<Chain, EthereumCallSelector>(Chain::class.java)
companion object { companion object {
@@ -391,15 +392,18 @@ open class NativeCall(
if (!ctx.upstream.getMethods().isCallable(ctx.payload.method)) { if (!ctx.upstream.getMethods().isCallable(ctx.payload.method)) {
return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method")) return Mono.error(RpcException(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unsupported method"))
} }
val reader = quorumReaderFactory.create(ctx.getApis(), ctx.callQuorum, signer, tracer) val reader = rpcReaderFactory.create(
RpcReaderData(ctx.upstream, ctx.payload.method, ctx.matcher, ctx.callQuorum, signer, tracer)
)
val counter = reader.attempts() val counter = reader.attempts()
return SpannedReader(reader, tracer, REMOTE_QUORUM_RPC_READER) return SpannedReader(reader, tracer, RPC_READER)
.read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector)) .read(JsonRpcRequest(ctx.payload.method, ctx.payload.params, ctx.nonce, ctx.forwardedSelector))
.map { .map {
val bytes = ctx.resultDecorator.processResult(it) val bytes = ctx.resultDecorator.processResult(it)
validateResult(bytes, "remote", ctx) validateResult(bytes, "remote", ctx)
CallResult.ok(ctx.id, ctx.nonce, bytes, it.signature, it.resolvedBy?.getId(), ctx) val upId = it.resolvedBy?.getId() ?: ctx.upstream.getId()
CallResult.ok(ctx.id, ctx.nonce, bytes, it.signature, upId, ctx)
} }
.onErrorResume { t -> .onErrorResume { t ->
Mono.just(CallResult.fail(ctx.id, ctx.nonce, t, ctx)) Mono.just(CallResult.fail(ctx.id, ctx.nonce, t, ctx))
@@ -470,11 +474,11 @@ open class NativeCall(
} }
interface ResultDecorator { interface ResultDecorator {
fun processResult(result: QuorumRpcReader.Result): ByteArray fun processResult(result: RpcReader.Result): ByteArray
} }
open class NoneResultDecorator : ResultDecorator { open class NoneResultDecorator : ResultDecorator {
override fun processResult(result: QuorumRpcReader.Result): ByteArray = result.value override fun processResult(result: RpcReader.Result): ByteArray = result.value
} }
open class CreateFilterDecorator : ResultDecorator { open class CreateFilterDecorator : ResultDecorator {
@@ -482,7 +486,7 @@ open class NativeCall(
companion object { companion object {
const val quoteCode = '"'.code.toByte() const val quoteCode = '"'.code.toByte()
} }
override fun processResult(result: QuorumRpcReader.Result): ByteArray { override fun processResult(result: RpcReader.Result): ByteArray {
val bytes = result.value val bytes = result.value
if (bytes.last() == quoteCode && result.resolvedBy != null) { if (bytes.last() == quoteCode && result.resolvedBy != null) {
val suffix = result.resolvedBy.nodeId() val suffix = result.resolvedBy.nodeId()

View File

@@ -19,7 +19,6 @@ package io.emeraldpay.dshackle.upstream.calls
import io.emeraldpay.dshackle.Chain import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.Global import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.quorum.AlwaysQuorum import io.emeraldpay.dshackle.quorum.AlwaysQuorum
import io.emeraldpay.dshackle.quorum.BroadcastQuorum
import io.emeraldpay.dshackle.quorum.CallQuorum import io.emeraldpay.dshackle.quorum.CallQuorum
import io.emeraldpay.dshackle.quorum.NotLaggingQuorum import io.emeraldpay.dshackle.quorum.NotLaggingQuorum
import io.emeraldpay.dshackle.quorum.NotNullQuorum import io.emeraldpay.dshackle.quorum.NotNullQuorum
@@ -198,7 +197,6 @@ class DefaultEthereumMethods(
when (method) { when (method) {
"eth_getTransactionCount" -> AlwaysQuorum() "eth_getTransactionCount" -> AlwaysQuorum()
"eth_getBalance" -> AlwaysQuorum() "eth_getBalance" -> AlwaysQuorum()
"eth_sendRawTransaction" -> BroadcastQuorum()
"eth_blockNumber" -> NotLaggingQuorum(0) "eth_blockNumber" -> NotLaggingQuorum(0)
else -> AlwaysQuorum() else -> AlwaysQuorum()
} }

View File

@@ -10,8 +10,8 @@ import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.data.DefaultContainer 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.data.TxId
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.reader.RpcReaderFactory
import io.emeraldpay.dshackle.upstream.Multistream import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.CallMethods
@@ -51,7 +51,7 @@ class EthereumDirectReader(
} }
private val objectMapper: ObjectMapper = Global.objectMapper private val objectMapper: ObjectMapper = Global.objectMapper
var quorumReaderFactory: QuorumReaderFactory = QuorumReaderFactory.default() var rpcReaderFactory: RpcReaderFactory = RpcReaderFactory.default()
val blockReader: Reader<BlockHash, Result<BlockContainer>> val blockReader: Reader<BlockHash, Result<BlockContainer>>
val blockByHeightReader: Reader<Long, Result<BlockContainer>> val blockByHeightReader: Reader<Long, Result<BlockContainer>>
@@ -183,19 +183,17 @@ class EthereumDirectReader(
request: JsonRpcRequest, request: JsonRpcRequest,
matcher: Selector.Matcher = Selector.empty matcher: Selector.Matcher = Selector.empty
): Mono<Result<ByteArray>> { ): Mono<Result<ByteArray>> {
return Mono.just(quorumReaderFactory) return Mono.just(rpcReaderFactory)
.map { .map {
val requestMatcher = Selector.Builder()
.withMatcher(matcher)
.forMethod(request.method)
.build()
it.create( it.create(
up.getApiSource( RpcReaderFactory.RpcReaderData(
Selector.Builder() up, request.method, requestMatcher,
.withMatcher(matcher) callMethodsFactory.create().createQuorumFor(request.method), null, tracer
.forMethod(request.method) )
.build()
),
callMethodsFactory.create().createQuorumFor(request.method),
// we do not use Signer for internal requests because it doesn't make much sense
null,
tracer
) )
}.flatMap { }.flatMap {
it.read(request) it.read(request)

View File

@@ -39,6 +39,7 @@ class QuorumRpcReaderSpec extends Specification {
setup: setup:
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getId() >> "id"
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
1 * getIngressReader() >> Mock(Reader) { 1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_test", [])) >> Mono.just(JsonRpcResponse.ok("1")) 1 * read(new JsonRpcRequest("eth_test", [])) >> Mono.just(JsonRpcResponse.ok("1"))
@@ -72,6 +73,7 @@ class QuorumRpcReaderSpec extends Specification {
} }
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getId() >> "id"
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
_ * getIngressReader() >> api _ * getIngressReader() >> api
} }
@@ -109,6 +111,7 @@ class QuorumRpcReaderSpec extends Specification {
} }
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getId() >> "id"
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
_ * getIngressReader() >> api _ * getIngressReader() >> api
} }
@@ -136,6 +139,7 @@ class QuorumRpcReaderSpec extends Specification {
setup: setup:
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getId() >> "id"
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
_ * getIngressReader() >> Mock(Reader) { _ * getIngressReader() >> Mock(Reader) {
2 * read(new JsonRpcRequest("eth_test", [])) >>> [ 2 * read(new JsonRpcRequest("eth_test", [])) >>> [
@@ -168,6 +172,7 @@ class QuorumRpcReaderSpec extends Specification {
setup: setup:
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getId() >> "id"
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
_ * getIngressReader() >> Mock(Reader) { _ * getIngressReader() >> Mock(Reader) {
2 * read(new JsonRpcRequest("eth_test", [])) >>> [ 2 * read(new JsonRpcRequest("eth_test", [])) >>> [
@@ -236,6 +241,7 @@ class QuorumRpcReaderSpec extends Specification {
} }
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getId() >> "id"
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
_ * getIngressReader() >> api _ * getIngressReader() >> api
} }
@@ -264,6 +270,7 @@ class QuorumRpcReaderSpec extends Specification {
setup: setup:
def up = Mock(Upstream) { def up = Mock(Upstream) {
_ * getLag() >> 0 _ * getLag() >> 0
_ * getId() >> "id"
_ * isAvailable() >> true _ * isAvailable() >> true
_ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY _ * getRole() >> UpstreamsConfig.UpstreamRole.PRIMARY
_ * getIngressReader() >> Mock(Reader) { _ * getIngressReader() >> Mock(Reader) {

View File

@@ -0,0 +1,192 @@
package io.emeraldpay.dshackle.reader
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import org.springframework.cloud.sleuth.Tracer
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import spock.lang.Specification
import java.time.Duration
class BroadcastReaderSpec extends Specification {
def "Return responses from all upstreams"() {
setup:
def result = "123".getBytes()
def up = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.just(new JsonRpcResponse(result, null))
}
}
def up1 = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.just(new JsonRpcResponse(result, null))
}
}
def up2 = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.just(new JsonRpcResponse(result, null))
}
}
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
when:
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
then:
StepVerifier.create(act)
.expectNextMatches {
it.value == result
}
.expectComplete()
.verify(Duration.ofSeconds(3))
}
def "Return response if at least one upstream responds"() {
setup:
def result = "123".getBytes()
def up = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.just(new JsonRpcResponse(result, null))
}
}
def up1 = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.error(new JsonRpcException(1, "too low"))
}
}
def up2 = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.error(new JsonRpcException(1, "too low")) }
}
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
when:
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
then:
StepVerifier.create(act)
.expectNextMatches {
it.value == result
}
.expectComplete()
.verify(Duration.ofSeconds(3))
}
def "Return response from matched upstream"() {
setup:
def result = "123".getBytes()
def up = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.just(new JsonRpcResponse(result, null))
}
}
def up1 = Mock(Upstream) {
1 * isAvailable() >> false
0 * getId() >> "id"
0 * getIngressReader() >> Mock(Reader)
}
def up2 = Mock(Upstream) {
1 * isAvailable() >> false
0 * getId() >> "id"
0 * getIngressReader() >> Mock(Reader)
}
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
when:
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
then:
StepVerifier.create(act)
.expectNextMatches {
it.value == result
}
.expectComplete()
.verify(Duration.ofSeconds(3))
}
def "Return error if all upstreams return error"() {
setup:
def up = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.error(new JsonRpcException(1, "too low"))
}
}
def up1 = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.error(new JsonRpcException(1, "too low"))
}
}
def up2 = Mock(Upstream) {
1 * isAvailable() >> true
_ * getId() >> "id"
1 * getIngressReader() >> Mock(Reader) {
1 * read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"])) >>
Mono.error(new JsonRpcException(1, "too low"))
}
}
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
when:
def act = reader.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
then:
StepVerifier.create(act)
.expectError(JsonRpcException.class)
.verify(Duration.ofSeconds(3))
}
def "No response if no available upstreams"() {
setup:
def up = Mock(Upstream) {
1 * isAvailable() >> false
0 * getId() >> "id"
0 * getIngressReader() >> Mock(Reader)
}
def up1 = Mock(Upstream) {
1 * isAvailable() >> false
0 * getId() >> "id"
0 * getIngressReader() >> Mock(Reader)
}
def up2 = Mock(Upstream) {
1 * isAvailable() >> false
0 * getId() >> "id"
0 * getIngressReader() >> Mock(Reader)
}
def reader = new BroadcastReader([up, up1, up2], new Selector.EmptyMatcher(), null, Stub(Tracer))
when:
def act = reader
.read(new JsonRpcRequest("eth_sendRawTransaction", ["0x1"]))
.switchIfEmpty(Mono.just(new RpcReader.Result(new byte[0], null, 0, null)))
then:
StepVerifier.create(act)
.expectNextMatches {
it.value == new byte[0]
}
.expectComplete()
.verify(Duration.ofSeconds(3))
}
}

View File

@@ -25,17 +25,12 @@ import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.config.CacheConfig import io.emeraldpay.dshackle.config.CacheConfig
import io.emeraldpay.dshackle.config.MainConfig import io.emeraldpay.dshackle.config.MainConfig
import io.emeraldpay.dshackle.quorum.AlwaysQuorum import io.emeraldpay.dshackle.quorum.AlwaysQuorum
import io.emeraldpay.dshackle.quorum.QuorumReader
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory
import io.emeraldpay.dshackle.quorum.QuorumRpcReader
import io.emeraldpay.dshackle.reader.Reader import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.reader.RpcReader
import io.emeraldpay.dshackle.reader.RpcReaderFactory
import io.emeraldpay.dshackle.test.MultistreamHolderMock import io.emeraldpay.dshackle.test.MultistreamHolderMock
import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.*
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.MultistreamHolder
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
@@ -132,9 +127,9 @@ class NativeCallSpec extends Specification {
} }
def nativeCall = nativeCall() def nativeCall = nativeCall()
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"foo\"".bytes, null, 1, ups)) 1 * read(_) >> Mono.just(new RpcReader.Result("\"foo\"".bytes, null, 1, ups))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum,
@@ -153,8 +148,8 @@ class NativeCallSpec extends Specification {
def quorum = new AlwaysQuorum() def quorum = new AlwaysQuorum()
def nativeCall = nativeCall() def nativeCall = nativeCall()
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * attempts() >> new AtomicInteger(1) 1 * attempts() >> new AtomicInteger(1)
1 * read(new JsonRpcRequest("eth_test", [], 10)) >> Mono.empty() 1 * read(new JsonRpcRequest("eth_test", [], 10)) >> Mono.empty()
} }
@@ -178,8 +173,8 @@ class NativeCallSpec extends Specification {
def quorum = new AlwaysQuorum() def quorum = new AlwaysQuorum()
def nativeCall = nativeCall() def nativeCall = nativeCall()
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_test", [], 10)) >> Mono.error( 1 * read(new JsonRpcRequest("eth_test", [], 10)) >> Mono.error(
new JsonRpcException(JsonRpcResponse.Id.from(12), new JsonRpcError(-32123, "Foo Bar", "Foo Bar Baz"), null, true) new JsonRpcException(JsonRpcResponse.Id.from(12), new JsonRpcError(-32123, "Foo Bar", "Foo Bar Baz"), null, true)
) )
@@ -615,9 +610,9 @@ class NativeCallSpec extends Specification {
_ * it.observeChains() >> Flux.empty() _ * it.observeChains() >> Flux.empty()
} }
def nativeCall = nativeCall(multistreamHolder) def nativeCall = nativeCall(multistreamHolder)
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, ups)) 1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum, def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,
@@ -650,9 +645,9 @@ class NativeCallSpec extends Specification {
_ * it.observeChains() >> Flux.empty() _ * it.observeChains() >> Flux.empty()
} }
def nativeCall = nativeCall(multistreamHolder) def nativeCall = nativeCall(multistreamHolder)
nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { nativeCall.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(_) >> Mono.just(new QuorumRpcReader.Result("\"0xab\"".bytes, null, 1, ups)) 1 * read(_) >> Mono.just(new RpcReader.Result("\"0xab\"".bytes, null, 1, ups))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum, def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,

View File

@@ -5,22 +5,17 @@ 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.data.DefaultContainer
import io.emeraldpay.dshackle.quorum.QuorumReader import io.emeraldpay.dshackle.reader.RpcReader
import io.emeraldpay.dshackle.quorum.QuorumReaderFactory import io.emeraldpay.dshackle.reader.RpcReaderFactory
import io.emeraldpay.dshackle.quorum.QuorumRpcReader
import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.ApiSource import io.emeraldpay.dshackle.upstream.*
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
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
import io.emeraldpay.etherjar.domain.Wei import io.emeraldpay.etherjar.domain.Wei
import io.emeraldpay.dshackle.upstream.ethereum.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.TransactionReceiptJson
import org.apache.commons.collections4.Factory import org.apache.commons.collections4.Factory
@@ -47,19 +42,16 @@ class EthereumDirectReaderSpec extends Specification {
parentHash = BlockHash.from(hash1) parentHash = BlockHash.from(hash1)
transactions = [] transactions = []
} }
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _,) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver) Global.objectMapper.writeValueAsBytes(json), null, 1, resolver)
) )
} }
@@ -77,19 +69,16 @@ class EthereumDirectReaderSpec extends Specification {
def "Produce empty result on non-existing block"() { def "Produce empty result on non-existing block"() {
setup: setup:
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver Global.objectMapper.writeValueAsBytes(null), null, 1, resolver
) )
) )
@@ -113,21 +102,16 @@ class EthereumDirectReaderSpec extends Specification {
parentHash = BlockHash.from(hash1) parentHash = BlockHash.from(hash1)
transactions = [] transactions = []
} }
def up = Mock(Multistream) {
1 * getApiSource(
new Selector.Builder().withMatcher(new Selector.HeightMatcher(100)).forMethod("eth_getBlockByNumber").build()
) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
) )
) )
@@ -151,19 +135,16 @@ class EthereumDirectReaderSpec extends Specification {
blockNumber = 100 blockNumber = 100
blockHash = BlockHash.from(hash1) blockHash = BlockHash.from(hash1)
} }
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
) )
) )
@@ -187,19 +168,16 @@ class EthereumDirectReaderSpec extends Specification {
blockNumber = 100 blockNumber = 100
blockHash = BlockHash.from(hash1) blockHash = BlockHash.from(hash1)
} }
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
) )
) )
@@ -220,9 +198,6 @@ class EthereumDirectReaderSpec extends Specification {
blockNumber = 100 blockNumber = 100
blockHash = BlockHash.from(hash1) blockHash = BlockHash.from(hash1)
} }
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
@@ -231,12 +206,12 @@ class EthereumDirectReaderSpec extends Specification {
1 * cacheReceipt(Caches.Tag.REQUESTED, { DefaultContainer data -> data.txId.toHex() == hash1.substring(2) && data.height == 100 }) 1 * cacheReceipt(Caches.Tag.REQUESTED, { DefaultContainer data -> data.txId.toHex() == hash1.substring(2) && data.height == 100 })
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, caches, new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), caches, new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionReceipt", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver Global.objectMapper.writeValueAsBytes(json), null, 1, resolver
) )
) )
@@ -252,19 +227,16 @@ class EthereumDirectReaderSpec extends Specification {
def "Produce empty on non-existing tx"() { def "Produce empty on non-existing tx"() {
setup: setup:
def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver Global.objectMapper.writeValueAsBytes(null), null, 1, resolver
) )
) )
@@ -281,7 +253,6 @@ class EthereumDirectReaderSpec extends Specification {
def "Reads balance - height is unknown"() { def "Reads balance - height is unknown"() {
setup: setup:
def up = Mock(Multistream) { def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
1 * getHead() >> Mock(Head) { 1 * getHead() >> Mock(Head) {
1 * getCurrentHeight() >> null 1 * getCurrentHeight() >> null
} }
@@ -292,10 +263,10 @@ class EthereumDirectReaderSpec extends Specification {
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver
) )
) )
@@ -313,7 +284,6 @@ class EthereumDirectReaderSpec extends Specification {
def "Reads balance - height is known"() { def "Reads balance - height is known"() {
setup: setup:
def up = Mock(Multistream) { def up = Mock(Multistream) {
1 * getApiSource(_) >> Stub(ApiSource)
1 * getHead() >> Mock(Head) { 1 * getHead() >> Mock(Head) {
1 * getCurrentHeight() >> 11_061_691 1 * getCurrentHeight() >> 11_061_691
} }
@@ -324,10 +294,10 @@ class EthereumDirectReaderSpec extends Specification {
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just( 1 * read(new JsonRpcRequest("eth_getBalance", [address1, "0xa8c9bb"])) >> Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver
) )
) )
@@ -352,25 +322,22 @@ class EthereumDirectReaderSpec extends Specification {
totalDifficulty = BigInteger.ONE totalDifficulty = BigInteger.ONE
transactions = [] transactions = []
} }
def up = Mock(Multistream) {
3 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
def result = Mono.just( def result = Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver) Global.objectMapper.writeValueAsBytes(json), null, 1, resolver)
) )
EthereumDirectReader ethereumDirectReader = new EthereumDirectReader( EthereumDirectReader ethereumDirectReader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
ethereumDirectReader.quorumReaderFactory = Mock(QuorumReaderFactory) { ethereumDirectReader.rpcReaderFactory = Mock(RpcReaderFactory) {
2 * create(_, _, _, _) >> Mock(QuorumReader) { 2 * create(_) >> Mock(RpcReader) {
2 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >>> 2 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >>>
[Mono.error(new RuntimeException()), Mono.error(new RuntimeException())] [Mono.error(new RuntimeException()), Mono.error(new RuntimeException())]
} }
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> result 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> result
} }
} }
@@ -395,25 +362,22 @@ class EthereumDirectReaderSpec extends Specification {
parentHash = BlockHash.from(hash1) parentHash = BlockHash.from(hash1)
transactions = [] transactions = []
} }
def up = Mock(Multistream) {
3 * getApiSource(_) >> Stub(ApiSource)
}
def calls = Mock(Factory) { def calls = Mock(Factory) {
3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET) 3 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET)
} }
def result = Mono.just( def result = Mono.just(
new QuorumRpcReader.Result( new RpcReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver) Global.objectMapper.writeValueAsBytes(json), null, 1, resolver)
) )
EthereumDirectReader ethereumDirectReader = new EthereumDirectReader( EthereumDirectReader ethereumDirectReader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
ethereumDirectReader.quorumReaderFactory = Mock(QuorumReaderFactory) { ethereumDirectReader.rpcReaderFactory = Mock(RpcReaderFactory) {
2 * create(_, _, _, _) >> Mock(QuorumReader) { 2 * create(_) >> Mock(RpcReader) {
2 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >>> 2 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >>>
[Mono.error(new RuntimeException()), Mono.error(new RuntimeException())] [Mono.error(new RuntimeException()), Mono.error(new RuntimeException())]
} }
1 * create(_, _, _, _) >> Mock(QuorumReader) { 1 * create(_) >> Mock(RpcReader) {
1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> result 1 * read(new JsonRpcRequest("eth_getBlockByNumber", ["0x64", false])) >> result
} }
} }
@@ -431,7 +395,6 @@ class EthereumDirectReaderSpec extends Specification {
def "Reads balance with retries - expects an error within 1 sec"() { def "Reads balance with retries - expects an error within 1 sec"() {
setup: setup:
def up = Mock(Multistream) { def up = Mock(Multistream) {
4 * getApiSource(_) >> Stub(ApiSource)
1 * getHead() >> Mock(Head) { 1 * getHead() >> Mock(Head) {
1 * getCurrentHeight() >> null 1 * getCurrentHeight() >> null
} }
@@ -442,8 +405,8 @@ class EthereumDirectReaderSpec extends Specification {
EthereumDirectReader reader = new EthereumDirectReader( EthereumDirectReader reader = new EthereumDirectReader(
up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock() up, Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
) )
reader.quorumReaderFactory = Mock(QuorumReaderFactory) { reader.rpcReaderFactory = Mock(RpcReaderFactory) {
4 * create(_, _, _, _) >> Mock(QuorumReader) { 4 * create(_) >> Mock(RpcReader) {
4 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >>> 4 * read(new JsonRpcRequest("eth_getBalance", [address1, "latest"])) >>>
[Mono.error(new RuntimeException()), Mono.error(new RuntimeException()), [Mono.error(new RuntimeException()), Mono.error(new RuntimeException()),
Mono.error(new RuntimeException()), Mono.error(new RuntimeException())] Mono.error(new RuntimeException()), Mono.error(new RuntimeException())]