solution: use caching for WS head

This commit is contained in:
Igor Artamonov
2020-02-23 23:07:31 -05:00
parent 81ccfef068
commit b255bed3a1
9 changed files with 165 additions and 47 deletions

View File

@@ -89,7 +89,7 @@ open class ChainUpstreams (
upstream.setLag(0)
upstream.getHead()
} else {
val newHead = EthereumHeadMerge(upstreams.map { it.getHead().getFlux() }).apply {
val newHead = EthereumHeadMerge(upstreams.map { it.getHead() }).apply {
this.start()
}
val lagObserver = HeadLagObserver(newHead, upstreams).apply {

View File

@@ -35,6 +35,10 @@ abstract class EthereumApi(
abstract fun execute(id: Int, method: String, params: List<Any>): Mono<ByteArray>
fun <JS, RS> execute(rpcCall: RpcCall<JS, RS>): Mono<ByteArray> {
return execute(0, rpcCall.method, rpcCall.params as List<Any>)
}
fun <JS, RS> executeAndConvert(rpcCall: RpcCall<JS, RS>): Mono<RS> {
val convertToJS = java.util.function.Function<ByteArray, Mono<JS>> { resp ->
val inputStream: InputStream = resp.inputStream()
@@ -42,7 +46,7 @@ abstract class EthereumApi(
if (jsonValue == null) Mono.empty<JS>()
else Mono.just(jsonValue)
}
return execute(0, rpcCall.method, rpcCall.params as List<Any>)
return execute(rpcCall)
.flatMap(convertToJS)
.map(rpcCall.converter::apply)
.doOnError { err -> log.debug("Failed to read from upstream", err) }

View File

@@ -15,20 +15,15 @@
*/
package io.emeraldpay.dshackle.upstream.ethereum
import io.infinitape.etherjar.domain.TransactionId
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionRefJson
import org.reactivestreams.Publisher
import org.slf4j.LoggerFactory
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesEnabled
import org.springframework.context.Lifecycle
import reactor.core.Disposable
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.concurrent.atomic.AtomicReference
class EthereumHeadMerge(
private val fluxes: Iterable<Publisher<BlockJson<TransactionRefJson>>>
): DefaultEthereumHead(), Lifecycle {
private val sources: Iterable<EthereumHead>
): DefaultEthereumHead(), Lifecycle, CachesEnabled {
private var subscription: Disposable? = null
@@ -37,11 +32,19 @@ class EthereumHeadMerge(
}
override fun start() {
subscription = super.follow(Flux.merge(fluxes))
subscription = super.follow(Flux.merge(sources.map { it.getFlux() }))
}
override fun stop() {
subscription?.dispose()
}
override fun setCaches(caches: Caches) {
sources.forEach {
if (it is CachesEnabled) {
it.setCaches(caches)
}
}
}
}

View File

@@ -16,9 +16,17 @@
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.Defaults
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesEnabled
import io.emeraldpay.dshackle.reader.EmptyReader
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.CachingEthereumApi
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.rpc.Batch
import io.infinitape.etherjar.rpc.Commands
import io.infinitape.etherjar.rpc.ReactorBatch
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionRefJson
import org.slf4j.LoggerFactory
import org.springframework.context.Lifecycle
import org.springframework.scheduling.concurrent.CustomizableThreadFactory
@@ -52,6 +60,8 @@ class EthereumRpcHead(
.timeout(Defaults.timeout, Mono.error(Exception("Block number not received")))
}
.flatMap {
//fetching by Block Height here, critical to use same upstream,
//different upstreams may have different blocks on the same height
api.rpcClient
.execute(Commands.eth().getBlock(it))
.subscribeOn(scheduler)

View File

@@ -52,6 +52,9 @@ open class EthereumUpstream(
override fun setCaches(caches: Caches) {
api.caches = caches;
if (head is CachesEnabled) {
head.setCaches(caches)
}
}
override fun getId(): String {
@@ -92,7 +95,7 @@ open class EthereumUpstream(
val rpc = EthereumRpcHead(api, Duration.ofSeconds(30)).apply {
this.start()
}
EthereumHeadMerge(listOf(rpc.getFlux(), ws.getFlux())).apply {
EthereumHeadMerge(listOf(rpc, ws)).apply {
this.start()
}
} else {

View File

@@ -16,8 +16,12 @@
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.Defaults
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesEnabled
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.infinitape.etherjar.domain.TransactionId
import io.emeraldpay.dshackle.reader.EmptyReader
import io.emeraldpay.dshackle.reader.Reader
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.rpc.Commands
import io.infinitape.etherjar.rpc.json.BlockJson
import io.infinitape.etherjar.rpc.json.TransactionRefJson
@@ -34,7 +38,7 @@ class EthereumWs(
private val uri: URI,
private val origin: URI,
private val api: EthereumApi
) {
): CachesEnabled {
private val log = LoggerFactory.getLogger(EthereumWs::class.java)
private val topic = TopicProcessor
@@ -43,6 +47,8 @@ class EthereumWs(
.build()
var basicAuth: UpstreamsConfig.BasicAuth? = null
private var blockCache: Reader<BlockHash, BlockJson<TransactionRefJson>> = EmptyReader()
fun connect() {
log.info("Connecting to WebSocket: $uri")
val clientBuilder = WebsocketClient.newBuilder()
@@ -54,24 +60,27 @@ class EthereumWs(
val client = clientBuilder.build()
try {
client.connect()
client.onNewBlock(this::onNewBlock)
} catch (e: Exception) {
log.error("Failed to connect to websocket at $uri. Error: ${e.message}")
return
}
client.onNewBlock {
if (it.totalDifficulty == null || it.transactions == null) {
Mono.just(it.hash).flatMap { hash ->
api.executeAndConvert(Commands.eth().getBlock(hash))
}.repeatWhenEmpty { n ->
Repeat.times<Any>(10)
.exponentialBackoff(Duration.ofMillis(50), Duration.ofMillis(250))
.apply(n)
}
.timeout(Defaults.timeout, Mono.empty())
.subscribe(topic::onNext)
} else {
topic.onNext(it)
}
}
fun onNewBlock(block: BlockJson<TransactionRefJson>) {
if (block.totalDifficulty == null || block.transactions == null) {
Mono.just(block.hash).flatMap { hash ->
// first check in cache, if empty then check api
blockCache.read(hash)
.switchIfEmpty(api.executeAndConvert(Commands.eth().getBlock(hash)))
}.repeatWhenEmpty { n ->
Repeat.times<Any>(10)
.exponentialBackoff(Duration.ofMillis(50), Duration.ofMillis(250))
.apply(n)
}
.timeout(Defaults.timeout, Mono.empty())
.subscribe(topic::onNext)
} else {
topic.onNext(block)
}
}
@@ -79,4 +88,8 @@ class EthereumWs(
return Flux.from(this.topic)
.onBackpressureLatest()
}
override fun setCaches(caches: Caches) {
blockCache = caches.getBlocksByHash()
}
}

View File

@@ -15,13 +15,15 @@
*/
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesEnabled
import org.slf4j.LoggerFactory
import org.springframework.context.Lifecycle
import reactor.core.Disposable
class EthereumWsHead(
private val ws: EthereumWs
): DefaultEthereumHead(), Lifecycle {
): DefaultEthereumHead(), Lifecycle, CachesEnabled {
private val log = LoggerFactory.getLogger(EthereumWsHead::class.java)
@@ -40,4 +42,8 @@ class EthereumWsHead(
subscription = null
}
override fun setCaches(caches: Caches) {
ws.setCaches(caches)
}
}