solution: cache latest blocks in memory

This commit is contained in:
Igor Artamonov
2019-08-20 23:14:49 -04:00
parent 887cf07e16
commit 8a99212fc5
28 changed files with 476 additions and 124 deletions

View File

@@ -0,0 +1,30 @@
package io.emeraldpay.dshackle.cache
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
import reactor.core.publisher.Mono
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
class BlocksMemCache(
val maxSize: Int = 64
) {
private val mapping = ConcurrentHashMap<BlockHash, BlockJson<TransactionId>>()
private val queue = ConcurrentLinkedQueue<BlockHash>()
fun get(hash: BlockHash): Mono<BlockJson<TransactionId>> {
return Mono.justOrEmpty(mapping[hash])
}
fun add(block: BlockJson<TransactionId>) {
mapping.put(block.hash, block)
queue.add(block.hash)
while (queue.size > maxSize) {
val old = queue.remove()
mapping.remove(old)
}
}
}

View File

@@ -0,0 +1,27 @@
package io.emeraldpay.dshackle.reader
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.Commands
import io.infinitape.etherjar.rpc.json.BlockJson
import reactor.core.publisher.Mono
import reactor.retry.Repeat
import java.time.Duration
class BlockApiReader(
val upstream: Upstream
): Reader<BlockHash, BlockJson<TransactionId>> {
override fun read(key: BlockHash): Mono<BlockJson<TransactionId>> {
return Mono.just(key)
.flatMap {
upstream.getApi(Selector.empty).executeAndConvert(Commands.eth().getBlock(it))
}.repeatWhenEmpty { n ->
Repeat.times<Any>(3)
.exponentialBackoff(Duration.ofMillis(100), Duration.ofMillis(500))
.apply(n)
}
}
}

View File

@@ -0,0 +1,16 @@
package io.emeraldpay.dshackle.reader
import io.emeraldpay.dshackle.cache.BlocksMemCache
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
import reactor.core.publisher.Mono
class BlockCacheReader(
val cache: BlocksMemCache
): Reader<BlockHash, BlockJson<TransactionId>> {
override fun read(key: BlockHash): Mono<BlockJson<TransactionId>> {
return cache.get(key)
}
}

View File

@@ -0,0 +1,23 @@
package io.emeraldpay.dshackle.reader
import reactor.core.publisher.Mono
class CompoundReader<K, D>(
private val readers: Collection<Reader<K, D>>
): Reader<K, D> {
override fun read(key: K): Mono<D> {
if (readers.isEmpty()) {
return Mono.empty()
}
var result = readers.first().read(key)
if (readers.size == 1) {
return result
}
readers.stream().skip(1).forEach {
result = result.switchIfEmpty(it.read(key))
}
return result
}
}

View File

@@ -0,0 +1,10 @@
package io.emeraldpay.dshackle.reader
import reactor.core.publisher.Mono
class EmptyReader<K, D>: Reader<K, D> {
override fun read(key: K): Mono<D> {
return Mono.empty()
}
}

View File

@@ -0,0 +1,9 @@
package io.emeraldpay.dshackle.reader
import reactor.core.publisher.Mono
interface Reader<K, D> {
fun read(key: K): Mono<D>
}

View File

@@ -31,16 +31,16 @@ class NativeCall(
return requestMono.flatMapMany(this::prepareCall)
.map(this::setupCallParams)
.parallel()
.flatMap(this::executeOnRemote)
.flatMap(this::fetch)
.sequential()
.map(this::buildResponse)
.doOnError { e -> log.warn("Error during native call", e) }
.onErrorResume(this::processException)
}
fun setupCallParams(it: CallContext<Tuple2<String, String>>): CallContext<Tuple2<String, List<Any>>> {
val params = extractParams(it.payload.t2)
return it.withPayload(Tuples.of(it.payload.t1, params))
fun setupCallParams(it: CallContext<RawCallDetails>): CallContext<ParsedCallDetails> {
val params = extractParams(it.payload.params)
return it.withPayload(ParsedCallDetails(it.payload.method, params))
}
fun buildResponse(it: CallContext<ByteArray>): BlockchainOuterClass.NativeCallReplyItem {
@@ -65,46 +65,57 @@ class NativeCall(
.toMono()
}
fun prepareCall(request: BlockchainOuterClass.NativeCallRequest): Flux<CallContext<Tuple2<String, String>>> {
fun prepareCall(request: BlockchainOuterClass.NativeCallRequest): Flux<CallContext<RawCallDetails>> {
val chain = Chain.byId(request.chain.number)
if (chain == Chain.UNSPECIFIED) {
return Flux.error<CallContext<Tuple2<String, String>>>(CallFailure(0, Exception("Invalid chain id: ${request.chain.number}")))
return Flux.error(CallFailure(0, Exception("Invalid chain id: ${request.chain.number}")))
}
val upstream = upstreams.getUpstream(chain)
?: return Flux.error<CallContext<Tuple2<String, String>>>(CallFailure(0, Exception("Chain ${chain.id} is unavailable")))
?: return Flux.error(CallFailure(0, Exception("Chain ${chain.id} is unavailable")))
return prepareCall(request, upstream)
}
fun prepareCall(request: BlockchainOuterClass.NativeCallRequest, upstream: AggregatedUpstream): Flux<CallContext<Tuple2<String, String>>> {
fun prepareCall(request: BlockchainOuterClass.NativeCallRequest, upstream: AggregatedUpstream): Flux<CallContext<RawCallDetails>> {
val matcher = Selector.convertToMatcher(request.selector)
val apis = upstream.getApis(matcher)
return request.itemsList.toFlux().map {
val method = it.method
val params = it.payload.toStringUtf8()
val callQuorum = upstream.targets?.getQuorumFor(method) ?: AlwaysQuorum()
callQuorum.init(upstream.getHead())
CallContext(it.id, apis, callQuorum, Tuples.of(method, params))
CallContext(it.id, upstream, matcher, callQuorum, RawCallDetails(method, params))
}
}
fun executeOnRemote(ctx: CallContext<Tuple2<String, List<Any>>>): Mono<CallContext<ByteArray>> {
fun fetch(ctx: CallContext<ParsedCallDetails>): Mono<CallContext<ByteArray>> {
return fetchFromCache(ctx)
.switchIfEmpty(
Mono.just(ctx).flatMap(this::executeOnRemote)
)
}
fun fetchFromCache(ctx: CallContext<ParsedCallDetails>): Mono<CallContext<ByteArray>> {
val cachingApi = ctx.upstream.cache
return cachingApi.execute(ctx.id, ctx.payload.method, ctx.payload.params).map { ctx.withPayload(it) }
}
fun executeOnRemote(ctx: CallContext<ParsedCallDetails>): Mono<CallContext<ByteArray>> {
val p: Predicate<Any> = CallQuorum.untilResolved(ctx.callQuorum)
val all = ctx.apis.toFlux().share()
val all = ctx.getApis().toFlux().share()
//execute on the first API immediately, and then make a delay between each call to not dos upstreams
val immediate = Flux.from(all).take(1)
val retries = Flux.from(all).delayElements(Duration.ofMillis(200))
return Flux.concat(immediate, retries)
.takeWhile(p)
.flatMap { api ->
api.execute(ctx.id, ctx.payload.t1, ctx.payload.t2).map { Tuples.of(it, api.upstream!!) }
api.execute(ctx.id, ctx.payload.method, ctx.payload.params).map { Tuples.of(it, api.upstream!!) }
}
.reduce(ctx.callQuorum, CallQuorum.asReducer())
.filter { it.isResolved() }
.map {
val result = it.getResult()
?: throw CallFailure(ctx.id, Exception("No response from upstream for ${ctx.payload.t1}"))
?: throw CallFailure(ctx.id, Exception("No response from upstream for ${ctx.payload.method}"))
ctx.withPayload(result)
}
.onErrorMap {
@@ -113,7 +124,7 @@ class NativeCall(
else CallFailure(ctx.id, it)
}
.switchIfEmpty(
Mono.error<CallContext<ByteArray>>(CallFailure(ctx.id, Exception("No response or no available upstream for ${ctx.payload.t1}")))
Mono.error<CallContext<ByteArray>>(CallFailure(ctx.id, Exception("No response or no available upstream for ${ctx.payload.method}")))
)
}
@@ -125,11 +136,18 @@ class NativeCall(
return req as List<Any>
}
open class CallContext<T>(val id: Int, val apis: Iterator<EthereumApi>, val callQuorum: CallQuorum, val payload: T) {
open class CallContext<T>(val id: Int, val upstream: AggregatedUpstream, val matcher: Selector.Matcher, val callQuorum: CallQuorum, val payload: T) {
fun <X> withPayload(payload: X): CallContext<X> {
return CallContext(id, apis, callQuorum, payload)
return CallContext(id, upstream, matcher, callQuorum, payload)
}
fun getApis(): Iterator<DirectEthereumApi> {
return upstream.getApis(matcher)
}
}
open class CallFailure(val id: Int, val reason: Throwable): Exception("Failed to call $id: ${reason.message}")
class RawCallDetails(val method: String, val params: String)
class ParsedCallDetails(val method: String, val params: List<Any>)
}

View File

@@ -1,19 +1,40 @@
package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.cache.BlocksMemCache
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.reader.BlockCacheReader
import io.emeraldpay.dshackle.reader.CompoundReader
import io.emeraldpay.dshackle.reader.Reader
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
import org.springframework.context.Lifecycle
import reactor.core.Disposable
import reactor.core.publisher.Flux
import java.time.Duration
import java.time.Instant
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.locks.ReentrantLock
import java.util.function.Predicate
import kotlin.concurrent.withLock
abstract class AggregatedUpstream(
val targets: CallMethods
): Upstream {
val targets: CallMethods,
val objectMapper: ObjectMapper
): Upstream, Lifecycle {
private val blocksCache = BlocksMemCache()
private var cacheSubscription: Disposable? = null
private val blockReader: Reader<BlockHash, BlockJson<TransactionId>> = CompoundReader(
listOf(BlockCacheReader(blocksCache))
)
var cache: CachingEthereumApi = CachingEthereumApi.empty()
private val reconfigLock = ReentrantLock()
abstract fun getAll(): List<Upstream>
abstract fun addUpstream(upstream: Upstream)
abstract fun getApis(matcher: Selector.Matcher): Iterator<EthereumApi>
abstract fun getApis(matcher: Selector.Matcher): Iterator<DirectEthereumApi>
override fun observeStatus(): Flux<UpstreamAvailability> {
val upstreamsFluxes = getAll().map { up -> up.observeStatus().map { UpstreamStatus(up, it) } }
@@ -61,4 +82,23 @@ abstract class AggregatedUpstream(
return changed
}
}
override fun start() {
}
override fun stop() {
cacheSubscription?.dispose()
cacheSubscription = null
}
fun onHeadUpdated(head: EthereumHead) {
reconfigLock.withLock {
cacheSubscription?.dispose()
cacheSubscription = head.getFlux().subscribe {
blocksCache.add(it)
}
cache = CachingEthereumApi(objectMapper, blockReader, head)
}
}
}

View File

@@ -0,0 +1,48 @@
package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.reader.EmptyReader
import io.emeraldpay.dshackle.reader.Reader
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.hex.HexQuantity
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.ResponseJson
import reactor.core.publisher.Mono
import java.util.function.Function
open class CachingEthereumApi(
private val objectMapper: ObjectMapper,
private val cache: Reader<BlockHash, BlockJson<TransactionId>>,
private val head: EthereumHead
): EthereumApi(objectMapper) {
companion object {
@JvmStatic
fun empty(): CachingEthereumApi {
return CachingEthereumApi(ObjectMapper(), EmptyReader(), EmptyEthereumHead())
}
}
override fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray> {
return when (method) {
"eth_blockNumber" -> head.getFlux().next()
.map { HexQuantity.from(it.number).toHex() }
.map(toJson(id))
"eth_getBlockByHash" -> Mono.just(params[0])
.map { BlockHash.from(it as String) }
.flatMap(cache::read)
.map(toJson(id))
else -> Mono.empty()
}
}
fun toJson(id: Int): Function<Any, ByteArray> {
return Function { data ->
val resp = ResponseJson<Any, Int>()
resp.id = id
resp.result = data
objectMapper.writer().writeValueAsBytes(resp)
}
}
}

View File

@@ -1,19 +1,19 @@
package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.grpc.Chain
import org.slf4j.LoggerFactory
import org.springframework.context.Lifecycle
import reactor.core.Disposable
import java.io.Closeable
import java.lang.IllegalStateException
import java.time.Duration
class ChainUpstreams (
open class ChainUpstreams (
val chain: Chain,
private val upstreams: MutableList<Upstream>,
targets: CallMethods
) : AggregatedUpstream(targets), Lifecycle {
targets: CallMethods,
objectMapper: ObjectMapper
) : AggregatedUpstream(targets, objectMapper), Lifecycle {
private val log = LoggerFactory.getLogger(ChainUpstreams::class.java)
private var seq = 0
@@ -30,12 +30,14 @@ class ChainUpstreams (
}
override fun start() {
super.start()
subscription = observeStatus()
.distinctUntilChanged()
.subscribe { printStatus() }
}
override fun stop() {
super.stop()
subscription?.dispose()
subscription = null
head?.let {
@@ -54,7 +56,7 @@ class ChainUpstreams (
}
lagObserver?.stop()
lagObserver = null
return if (upstreams.size == 1) {
val head = if (upstreams.size == 1) {
val upstream = upstreams.first()
upstream.setLag(0)
upstream.getHead()
@@ -68,6 +70,8 @@ class ChainUpstreams (
this.lagObserver = lagObserver
newHead
}
onHeadUpdated(head)
return head
}
override fun getAll(): List<Upstream> {
@@ -79,7 +83,7 @@ class ChainUpstreams (
head = updateHead()
}
override fun getApis(matcher: Selector.Matcher): Iterator<EthereumApi> {
override fun getApis(matcher: Selector.Matcher): Iterator<DirectEthereumApi> {
val i = seq++
if (seq >= Int.MAX_VALUE / 2) {
seq = 0
@@ -87,7 +91,7 @@ class ChainUpstreams (
return FilteringApiIterator(upstreams, i, matcher)
}
override fun getApi(matcher: Selector.Matcher): EthereumApi {
override fun getApi(matcher: Selector.Matcher): DirectEthereumApi {
return getApis(matcher).next()
}

View File

@@ -104,7 +104,7 @@ open class ConfiguredUpstreams(
chain: Chain,
options: UpstreamsConfig.Options,
labels: UpstreamsConfig.Labels) {
var rpcApi: EthereumApi? = null
var rpcApi: DirectEthereumApi? = null
val urls = ArrayList<URI>()
up.rpc?.let { endpoint ->
val rpcTransport = DefaultRpcTransport(endpoint.url)
@@ -117,10 +117,9 @@ open class ConfiguredUpstreams(
}
}
val rpcClient = DefaultRpcClient(rpcTransport)
rpcApi = EthereumApi(
rpcApi = DirectEthereumApi(
rpcClient,
objectMapper,
chain,
targetFor(chain)
)
urls.add(endpoint.url)
@@ -171,7 +170,7 @@ open class ConfiguredUpstreams(
override fun addUpstream(chain: Chain, up: Upstream): ChainUpstreams {
val current = chainMapping[chain]
if (current == null) {
val created = ChainUpstreams(chain, ArrayList<Upstream>(), targetFor(chain))
val created = ChainUpstreams(chain, ArrayList<Upstream>(), targetFor(chain), objectMapper)
created.addUpstream(up)
created.start()
chainMapping[chain] = created

View File

@@ -0,0 +1,58 @@
package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper
import io.infinitape.etherjar.rpc.RpcCall
import io.infinitape.etherjar.rpc.RpcClient
import io.infinitape.etherjar.rpc.RpcException
import io.infinitape.etherjar.rpc.json.ResponseJson
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
import java.time.Duration
open class DirectEthereumApi(
val rpcClient: RpcClient,
private val objectMapper: ObjectMapper,
val targets: CallMethods
): EthereumApi(objectMapper) {
private val timeout = Duration.ofSeconds(5)
private val log = LoggerFactory.getLogger(EthereumApi::class.java)
override fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray> {
val result: Mono<out Any> = when {
targets.isHardcoded(method) -> Mono.just(method).map { targets.hardcoded(it) }
targets.isAllowed(method) -> callUpstream(method, params)
else -> Mono.error(RpcException(-32601, "Method not allowed or not found"))
}
return result
.doOnError { t ->
log.warn("Upstream error: ${t.message} for ${method}")
}
.map {
val resp = ResponseJson<Any, Int>()
resp.id = id
resp.result = it
objectMapper.writer().writeValueAsBytes(resp)
}
.onErrorMap { t ->
if (RpcException::class.java.isAssignableFrom(t.javaClass)) {
t
} else {
log.warn("Convert to RPC error. Exception: ${t.message}")
RpcException(-32020, "Error reading from upstream", null, t)
}
}
.onErrorResume(RpcException::class.java) { t ->
val resp = ResponseJson<Any, Int>()
resp.id = id
resp.error = t.error
Mono.just(objectMapper.writer().writeValueAsBytes(resp))
}
}
private fun callUpstream(method: String, params: List<Any>): Mono<out Any> {
return Mono.fromCompletionStage(
rpcClient.execute(RpcCall.create(method, Any::class.java, params))
).timeout(timeout, Mono.error(RpcException(-32603, "Upstream timeout")))
}
}

View File

@@ -0,0 +1,12 @@
package io.emeraldpay.dshackle.upstream
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
import reactor.core.publisher.Flux
class EmptyEthereumHead : EthereumHead {
override fun getFlux(): Flux<BlockJson<TransactionId>> {
return Flux.empty()
}
}

View File

@@ -7,28 +7,22 @@ import io.infinitape.etherjar.rpc.*
import io.infinitape.etherjar.rpc.json.ResponseJson
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
import java.io.InputStream
import java.time.Duration
open class EthereumApi(
val rpcClient: RpcClient,
private val objectMapper: ObjectMapper,
private val chain: Chain,
val targets: CallMethods
abstract class EthereumApi(
objectMapper: ObjectMapper
) {
private val jacksonRpcConverter = JacksonRpcConverter(objectMapper)
var upstream: Upstream? = null
private val timeout = Duration.ofSeconds(5)
private val log = LoggerFactory.getLogger(EthereumApi::class.java)
var ws: EthereumWs? = null
set(value) {
field = value
}
abstract fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray>
open fun <JS, RS> executeAndConvert(rpcCall: RpcCall<JS, RS>): Mono<RS> {
fun <JS, RS> executeAndConvert(rpcCall: RpcCall<JS, RS>): Mono<RS> {
val convertToJS = java.util.function.Function<ByteArray, Mono<JS>> { resp ->
val jsonValue: JS? = jacksonRpcConverter.fromJson(resp.inputStream(), rpcCall.jsonType, Int::class.java)
val inputStream: InputStream = resp.inputStream()
val jsonValue: JS? = jacksonRpcConverter.fromJson(inputStream, rpcCall.jsonType, Int::class.java)
if (jsonValue == null) Mono.empty<JS>()
else Mono.just(jsonValue)
}
@@ -36,51 +30,4 @@ open class EthereumApi(
.flatMap(convertToJS)
.map(rpcCall.converter::apply)
}
open fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray> {
val result: Mono<out Any> = when {
targets.isHardcoded(method) -> Mono.just(method).map { targets.hardcoded(it) }
targets.isAllowed(method) -> callUpstream(method, params)
else -> Mono.error(RpcException(-32601, "Method not allowed or not found"))
}
return result
.doOnError { t ->
log.warn("Upstream error: ${t.message} for ${method} on $chain")
}
.map {
val resp = ResponseJson<Any, Int>()
resp.id = id
resp.result = it
objectMapper.writer().writeValueAsBytes(resp)
}
.onErrorMap { t ->
if (RpcException::class.java.isAssignableFrom(t.javaClass)) {
t
} else {
log.warn("Convert to RPC error. Exception: ${t.message}")
RpcException(-32020, "Error reading from upstream", null, t)
}
}
.onErrorResume(RpcException::class.java) { t ->
val resp = ResponseJson<Any, Int>()
resp.id = id
resp.error = t.error
Mono.just(objectMapper.writer().writeValueAsBytes(resp))
}
}
private fun callUpstream(method: String, params: List<Any>): Mono<out Any> {
if (method == "eth_blockNumber") {
val current = upstream?.getHead()?.getFlux()?.next()?.let { head ->
head.map { HexQuantity.from(it.number).toHex() }
}
if (current != null) {
return current
}
}
return Mono.fromCompletionStage(
rpcClient.execute(RpcCall.create(method, Any::class.java, params))
).timeout(timeout, Mono.error(RpcException(-32603, "Upstream timeout")))
}
}

View File

@@ -14,7 +14,7 @@ import java.time.Duration
import java.util.concurrent.atomic.AtomicReference
class EthereumRpcHead(
private val api: EthereumApi
private val api: DirectEthereumApi
): EthereumHead, Lifecycle {
private val log = LoggerFactory.getLogger(EthereumRpcHead::class.java)

View File

@@ -8,14 +8,14 @@ import reactor.core.Disposable
open class EthereumUpstream(
val chain: Chain,
private val api: EthereumApi,
private val api: DirectEthereumApi,
private val ethereumWs: EthereumWs? = null,
private val options: UpstreamsConfig.Options,
val node: NodeDetailsList.NodeDetails,
private val targets: CallMethods
): DefaultUpstream(), Lifecycle {
constructor(chain: Chain, api: EthereumApi): this(chain, api, null,
constructor(chain: Chain, api: DirectEthereumApi): this(chain, api, null,
UpstreamsConfig.Options.getDefaults(), NodeDetailsList.NodeDetails(1, UpstreamsConfig.Labels()),
DirectCallMethods())
@@ -84,11 +84,11 @@ open class EthereumUpstream(
return head
}
override fun getApi(matcher: Selector.Matcher): EthereumApi {
override fun getApi(matcher: Selector.Matcher): DirectEthereumApi {
return api
}
fun getApi(): EthereumApi {
fun getApi(): DirectEthereumApi {
return api
}

View File

@@ -5,7 +5,7 @@ class FilteringApiIterator(
private var pos: Int,
private val matcher: Selector.Matcher,
private val repeatLimit: Int = 3
): Iterator<EthereumApi> {
): Iterator<DirectEthereumApi> {
private var nextUpstream: Upstream? = null
private var consumed = 0
@@ -31,7 +31,7 @@ class FilteringApiIterator(
return nextInternal()
}
override fun next(): EthereumApi {
override fun next(): DirectEthereumApi {
if (nextInternal()) {
val curr = nextUpstream!!
nextUpstream = null

View File

@@ -44,9 +44,9 @@ open class GrpcUpstream(
private var headSubscription: Disposable? = null
open fun createApi(matcher: Selector.Matcher): EthereumApi {
open fun createApi(matcher: Selector.Matcher): DirectEthereumApi {
val rpcClient = DefaultRpcClient(grpcTransport.withMatcher(matcher))
return EthereumApi(rpcClient, objectMapper, chain, targets).let {
return DirectEthereumApi(rpcClient, objectMapper, targets).let {
it.upstream = this
it
}
@@ -156,7 +156,7 @@ open class GrpcUpstream(
return head
}
override fun getApi(matcher: Selector.Matcher): EthereumApi {
override fun getApi(matcher: Selector.Matcher): DirectEthereumApi {
return createApi(matcher)
}

View File

@@ -8,7 +8,8 @@ interface Upstream {
fun getStatus(): UpstreamAvailability
fun observeStatus(): Flux<UpstreamAvailability>
fun getHead(): EthereumHead
fun getApi(matcher: Selector.Matcher): EthereumApi
fun getApi(matcher: Selector.Matcher): DirectEthereumApi
// fun getCache(): CachingEthereumApi
fun getOptions(): UpstreamsConfig.Options
fun getSupportedTargets(): Set<String>
fun setLag(lag: Long)