solution: use caching for WS head
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user