use head scheduler instead of stock scheduler (#206)

This commit is contained in:
a10zn8
2023-04-21 15:53:21 +08:00
committed by GitHub
parent 39b110c3c0
commit f218e6e73b
38 changed files with 251 additions and 139 deletions

View File

@@ -21,18 +21,17 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory)
open fun allMultistreams( open fun allMultistreams(
cachesFactory: CachesFactory, cachesFactory: CachesFactory,
callTargetsHolder: CallTargetsHolder, callTargetsHolder: CallTargetsHolder,
@Qualifier("headMergedScheduler") @Qualifier("headScheduler")
headScheduler: Scheduler, headScheduler: Scheduler,
tracer: Tracer tracer: Tracer
): List<Multistream> { ): List<Multistream> {
return Chain.values() return Chain.values()
.filterNot { it == Chain.UNSPECIFIED } .filterNot { it == Chain.UNSPECIFIED }
.mapNotNull { chain -> .map { chain ->
when (BlockchainType.from(chain)) { when (BlockchainType.from(chain)) {
BlockchainType.EVM_POS -> ethereumPosMultistream(chain, cachesFactory, headScheduler, tracer) BlockchainType.EVM_POS -> ethereumPosMultistream(chain, cachesFactory, headScheduler, tracer)
BlockchainType.EVM_POW -> ethereumMultistream(chain, cachesFactory, headScheduler, tracer) BlockchainType.EVM_POW -> ethereumMultistream(chain, cachesFactory, headScheduler, tracer)
BlockchainType.BITCOIN -> bitcoinMultistream(chain, cachesFactory) BlockchainType.BITCOIN -> bitcoinMultistream(chain, cachesFactory, headScheduler)
else -> null
} }
} }
} }
@@ -73,14 +72,16 @@ open class MultistreamsConfig(val beanFactory: ConfigurableListableBeanFactory)
open fun bitcoinMultistream( open fun bitcoinMultistream(
chain: Chain, chain: Chain,
cachesFactory: CachesFactory cachesFactory: CachesFactory,
headScheduler: Scheduler
): BitcoinMultistream { ): BitcoinMultistream {
val name = "multi-bitcoin-$chain" val name = "multi-bitcoin-$chain"
return BitcoinMultistream( return BitcoinMultistream(
chain, chain,
ArrayList(), ArrayList(),
cachesFactory.getCaches(chain) cachesFactory.getCaches(chain),
headScheduler
).also { register(it, name) } ).also { register(it, name) }
} }

View File

@@ -26,8 +26,8 @@ open class SchedulersConfig {
} }
@Bean @Bean
open fun headMergedScheduler(monitoringConfig: MonitoringConfig): Scheduler { open fun headScheduler(monitoringConfig: MonitoringConfig): Scheduler {
return makeScheduler("head-scheduler", 5, monitoringConfig) return makeScheduler("head-scheduler", 4, monitoringConfig)
} }
@Bean @Bean

View File

@@ -82,7 +82,9 @@ open class ConfiguredUpstreams(
private val grpcTracing: GrpcTracing, private val grpcTracing: GrpcTracing,
private val wsConnectionResubscribeScheduler: Scheduler, private val wsConnectionResubscribeScheduler: Scheduler,
@Autowired(required = false) @Autowired(required = false)
private val clientSpansInterceptor: ClientInterceptor? private val clientSpansInterceptor: ClientInterceptor?,
@Qualifier("headScheduler")
private val headScheduler: Scheduler
) : ApplicationRunner { ) : ApplicationRunner {
@Value("\${spring.application.max-metadata-size}") @Value("\${spring.application.max-metadata-size}")
private var maxMetadataSize: Int = Defaults.maxMetadataSize private var maxMetadataSize: Int = Defaults.maxMetadataSize
@@ -261,11 +263,11 @@ open class ConfiguredUpstreams(
} }
val extractBlock = ExtractBlock() val extractBlock = ExtractBlock()
val rpcHead = BitcoinRpcHead(directApi, extractBlock) val rpcHead = BitcoinRpcHead(directApi, extractBlock, headScheduler = headScheduler)
val head: Head = conn.zeroMq?.let { zeroMq -> val head: Head = conn.zeroMq?.let { zeroMq ->
val server = ZMQServer(zeroMq.host, zeroMq.port, "hashblock") val server = ZMQServer(zeroMq.host, zeroMq.port, "hashblock")
val zeroMqHead = BitcoinZMQHead(server, directApi, extractBlock) val zeroMqHead = BitcoinZMQHead(server, directApi, extractBlock, headScheduler)
MergedHead(listOf(rpcHead, zeroMqHead), MostWorkForkChoice()) MergedHead(listOf(rpcHead, zeroMqHead), MostWorkForkChoice(), headScheduler)
} ?: rpcHead } ?: rpcHead
val methods = buildMethods(config, chain) val methods = buildMethods(config, chain)
@@ -350,7 +352,8 @@ open class ConfiguredUpstreams(
chainsConfig, chainsConfig,
grpcTracing, grpcTracing,
clientSpansInterceptor, clientSpansInterceptor,
maxMetadataSize maxMetadataSize,
headScheduler
).apply { ).apply {
timeout = options.timeout timeout = options.timeout
} }
@@ -412,7 +415,13 @@ open class ConfiguredUpstreams(
log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}") log.info("Using ${chain.chainName} upstream, at ${urls.joinToString()}")
val connectorFactory = val connectorFactory =
EthereumConnectorFactory( EthereumConnectorFactory(
conn.resolveMode(), wsFactoryApi, httpFactory, forkChoice, blockValidator, wsConnectionResubscribeScheduler conn.resolveMode(),
wsFactoryApi,
httpFactory,
forkChoice,
blockValidator,
wsConnectionResubscribeScheduler,
headScheduler
) )
if (!connectorFactory.isValid()) { if (!connectorFactory.isValid()) {
log.warn("Upstream configuration is invalid (probably no http endpoint)") log.warn("Upstream configuration is invalid (probably no http endpoint)")

View File

@@ -26,7 +26,7 @@ import reactor.core.publisher.SignalType
import reactor.core.publisher.Sinks import reactor.core.publisher.Sinks
import reactor.core.publisher.Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER import reactor.core.publisher.Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER
import reactor.core.publisher.Sinks.EmitResult.OK import reactor.core.publisher.Sinks.EmitResult.OK
import reactor.core.scheduler.Schedulers import reactor.core.scheduler.Scheduler
import reactor.kotlin.core.publisher.toMono import reactor.kotlin.core.publisher.toMono
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.Future import java.util.concurrent.Future
@@ -37,9 +37,10 @@ import java.util.concurrent.locks.ReentrantLock
abstract class AbstractHead @JvmOverloads constructor( abstract class AbstractHead @JvmOverloads constructor(
private val forkChoice: ForkChoice, private val forkChoice: ForkChoice,
private val headScheduler: Scheduler,
private val blockValidator: BlockValidator = BlockValidator.ALWAYS_VALID, private val blockValidator: BlockValidator = BlockValidator.ALWAYS_VALID,
private val awaitHeadTimeoutMs: Long = 60_000, private val awaitHeadTimeoutMs: Long = 60_000,
private val upstreamId: String = "" private val upstreamId: String = "",
) : Head { ) : Head {
protected val log = LoggerFactory.getLogger(this::class.java) protected val log = LoggerFactory.getLogger(this::class.java)
@@ -83,7 +84,7 @@ abstract class AbstractHead @JvmOverloads constructor(
log.warn("Received signal $upstreamId $it, continue emit heads") log.warn("Received signal $upstreamId $it, continue emit heads")
} }
} }
.subscribeOn(Schedulers.boundedElastic()) .subscribeOn(headScheduler)
.subscribe { block -> .subscribe { block ->
val valid = runCatching { val valid = runCatching {
blockValidator.isValid(forkChoice.getHead(), block) blockValidator.isValid(forkChoice.getHead(), block)

View File

@@ -9,11 +9,11 @@ import reactor.core.scheduler.Scheduler
open class DynamicMergedHead( open class DynamicMergedHead(
forkChoice: ForkChoice, forkChoice: ForkChoice,
private val label: String = "", private val label: String = "",
scheduler: Scheduler headScheduler: Scheduler
) : AbstractHead(forkChoice, upstreamId = label), Lifecycle { ) : AbstractHead(forkChoice, headScheduler, upstreamId = label), Lifecycle {
private var subscription: Disposable? = null private var subscription: Disposable? = null
private val dynamicFlux: DynamicMergeFlux<String, BlockContainer> = DynamicMergeFlux(scheduler) private val dynamicFlux: DynamicMergeFlux<String, BlockContainer> = DynamicMergeFlux(headScheduler)
override fun isRunning(): Boolean { override fun isRunning(): Boolean {
return subscription != null return subscription != null

View File

@@ -21,7 +21,7 @@ import org.slf4j.LoggerFactory
import org.springframework.context.Lifecycle import org.springframework.context.Lifecycle
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.scheduler.Schedulers import reactor.core.scheduler.Scheduler
import reactor.util.function.Tuple2 import reactor.util.function.Tuple2
import reactor.util.function.Tuples import reactor.util.function.Tuples
import java.time.Duration import java.time.Duration
@@ -35,6 +35,7 @@ abstract class HeadLagObserver(
private val master: Head, private val master: Head,
private val followers: Collection<Upstream>, private val followers: Collection<Upstream>,
private val distanceExtractor: Extractor, private val distanceExtractor: Extractor,
private val lagObserverScheduler: Scheduler,
private val throttling: Duration = Duration.ofSeconds(5) private val throttling: Duration = Duration.ofSeconds(5)
) : Lifecycle { ) : Lifecycle {
@@ -44,7 +45,7 @@ abstract class HeadLagObserver(
override fun start() { override fun start() {
current?.dispose() current?.dispose()
current = subscription().subscribe { } current = subscription().subscribeOn(lagObserverScheduler).subscribe { }
} }
override fun isRunning(): Boolean { override fun isRunning(): Boolean {
@@ -68,7 +69,7 @@ abstract class HeadLagObserver(
fun probeFollowers(top: BlockContainer): Flux<Tuple2<Long, Upstream>> { fun probeFollowers(top: BlockContainer): Flux<Tuple2<Long, Upstream>> {
return Flux.fromIterable(followers) return Flux.fromIterable(followers)
.parallel(followers.size) .parallel(followers.size)
.flatMap { up -> mapLagging(top, up, getCurrentBlocks(up)).subscribeOn(Schedulers.boundedElastic()) } .flatMap { up -> mapLagging(top, up, getCurrentBlocks(up)).subscribeOn(lagObserverScheduler) }
.sequential() .sequential()
.onErrorContinue { t, _ -> log.warn("Failed to update lagging distance", t) } .onErrorContinue { t, _ -> log.warn("Failed to update lagging distance", t) }
} }

View File

@@ -22,12 +22,14 @@ import io.emeraldpay.dshackle.cache.CachesEnabled
import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.scheduler.Scheduler
class MergedHead @JvmOverloads constructor( class MergedHead @JvmOverloads constructor(
private val sources: Iterable<Head>, private val sources: Iterable<Head>,
forkChoice: ForkChoice, forkChoice: ForkChoice,
headScheduler: Scheduler,
private val label: String = "" private val label: String = ""
) : AbstractHead(forkChoice, upstreamId = label), Lifecycle, CachesEnabled { ) : AbstractHead(forkChoice, headScheduler, upstreamId = label), Lifecycle, CachesEnabled {
private var subscription: Disposable? = null private var subscription: Disposable? = null

View File

@@ -21,11 +21,13 @@ import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.HeadLagObserver
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import reactor.core.scheduler.Scheduler
class BitcoinHeadLagObserver( class BitcoinHeadLagObserver(
master: Head, master: Head,
followers: Collection<Upstream> followers: Collection<Upstream>,
) : HeadLagObserver(master, followers, DistanceExtractor::extractPowDistance) { headScheduler: Scheduler
) : HeadLagObserver(master, followers, DistanceExtractor::extractPowDistance, headScheduler) {
companion object { companion object {
private val log = LoggerFactory.getLogger(BitcoinHeadLagObserver::class.java) private val log = LoggerFactory.getLogger(BitcoinHeadLagObserver::class.java)

View File

@@ -33,12 +33,14 @@ import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.DefaultBitcoinMethods
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
open class BitcoinMultistream( open class BitcoinMultistream(
chain: Chain, chain: Chain,
private val sourceUpstreams: MutableList<BitcoinUpstream>, private val sourceUpstreams: MutableList<BitcoinUpstream>,
caches: Caches, caches: Caches,
private val headScheduler: Scheduler,
) : Multistream(chain, sourceUpstreams as MutableList<Upstream>, caches), Lifecycle { ) : Multistream(chain, sourceUpstreams as MutableList<Upstream>, caches), Lifecycle {
private var head: Head = EmptyHead() private var head: Head = EmptyHead()
@@ -86,7 +88,7 @@ open class BitcoinMultistream(
} }
} }
} else { } else {
val newHead = MergedHead(sourceUpstreams.map { it.getHead() }, MostWorkForkChoice()).apply { val newHead = MergedHead(sourceUpstreams.map { it.getHead() }, MostWorkForkChoice(), headScheduler).apply {
this.start() this.start()
} }
newHead newHead
@@ -152,7 +154,7 @@ open class BitcoinMultistream(
} }
override fun makeLagObserver(): HeadLagObserver { override fun makeLagObserver(): HeadLagObserver {
return BitcoinHeadLagObserver(head, sourceUpstreams) return BitcoinHeadLagObserver(head, sourceUpstreams, headScheduler)
} }
override fun start() { override fun start() {

View File

@@ -27,6 +27,7 @@ import org.springframework.scheduling.concurrent.CustomizableThreadFactory
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
import reactor.core.scheduler.Schedulers import reactor.core.scheduler.Schedulers
import java.time.Duration import java.time.Duration
import java.util.concurrent.Executors import java.util.concurrent.Executors
@@ -34,8 +35,9 @@ import java.util.concurrent.Executors
class BitcoinRpcHead( class BitcoinRpcHead(
private val api: JsonRpcReader, private val api: JsonRpcReader,
private val extractBlock: ExtractBlock, private val extractBlock: ExtractBlock,
private val interval: Duration = Duration.ofSeconds(15) private val interval: Duration = Duration.ofSeconds(15),
) : Head, AbstractHead(MostWorkForkChoice(), awaitHeadTimeoutMs = 1200_000), Lifecycle { headScheduler: Scheduler
) : Head, AbstractHead(MostWorkForkChoice(), headScheduler, awaitHeadTimeoutMs = 1200_000), Lifecycle {
companion object { companion object {
val scheduler = val scheduler =

View File

@@ -49,13 +49,6 @@ open class BitcoinRpcUpstream(
setOf(Capability.RPC) setOf(Capability.RPC)
} }
private fun createHead(): Head {
return BitcoinRpcHead(
directApi,
ExtractBlock()
)
}
override fun getHead(): Head { override fun getHead(): Head {
return head return head
} }

View File

@@ -13,6 +13,7 @@ import org.apache.commons.codec.binary.Hex
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
import reactor.util.retry.Retry import reactor.util.retry.Retry
import java.time.Duration import java.time.Duration
@@ -20,7 +21,8 @@ class BitcoinZMQHead(
private val server: ZMQServer, private val server: ZMQServer,
private val api: JsonRpcReader, private val api: JsonRpcReader,
private val extractBlock: ExtractBlock, private val extractBlock: ExtractBlock,
) : Head, AbstractHead(MostWorkForkChoice(), awaitHeadTimeoutMs = 1200_000), Lifecycle { headScheduler: Scheduler,
) : Head, AbstractHead(MostWorkForkChoice(), headScheduler, awaitHeadTimeoutMs = 1200_000), Lifecycle {
private var refreshSubscription: Disposable? = null private var refreshSubscription: Disposable? = null
fun connect(): Flux<BlockContainer> { fun connect(): Flux<BlockContainer> {

View File

@@ -25,16 +25,18 @@ import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.etherjar.hex.HexQuantity import io.emeraldpay.etherjar.hex.HexQuantity
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
open class DefaultEthereumHead( open class DefaultEthereumHead(
protected val upstreamId: String, protected val upstreamId: String,
forkChoice: ForkChoice, forkChoice: ForkChoice,
blockValidator: BlockValidator blockValidator: BlockValidator,
) : Head, AbstractHead(forkChoice, blockValidator, 60_000, upstreamId) { private val headScheduler: Scheduler,
) : Head, AbstractHead(forkChoice, headScheduler, blockValidator, 60_000, upstreamId) {
fun getLatestBlock(api: JsonRpcReader): Mono<BlockContainer> { fun getLatestBlock(api: JsonRpcReader): Mono<BlockContainer> {
return api.read(JsonRpcRequest("eth_blockNumber", emptyList())) return api.read(JsonRpcRequest("eth_blockNumber", emptyList()))
.subscribeOn(EthereumRpcHead.scheduler) .subscribeOn(headScheduler)
.timeout(Defaults.timeout, Mono.error(Exception("Block number not received"))) .timeout(Defaults.timeout, Mono.error(Exception("Block number not received")))
.flatMap { .flatMap {
if (it.error != null) { if (it.error != null) {
@@ -48,7 +50,7 @@ open class DefaultEthereumHead(
// fetching by Block Height here, critical to use the same upstream as in previous call, // fetching by Block Height here, critical to use the same upstream as in previous call,
// b/c different upstreams may have different blocks on the same height // b/c different upstreams may have different blocks on the same height
api.read(JsonRpcRequest("eth_getBlockByNumber", listOf(it.toHex(), false))) api.read(JsonRpcRequest("eth_getBlockByNumber", listOf(it.toHex(), false)))
.subscribeOn(EthereumRpcHead.scheduler) .subscribeOn(headScheduler)
.timeout(Defaults.timeout, Mono.error(Exception("Block data not received"))) .timeout(Defaults.timeout, Mono.error(Exception("Block data not received")))
} }
.map { .map {

View File

@@ -22,11 +22,13 @@ import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.HeadLagObserver
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import reactor.core.scheduler.Scheduler
class EthereumHeadLagObserver( class EthereumHeadLagObserver(
master: Head, master: Head,
followers: Collection<Upstream> followers: Collection<Upstream>,
) : HeadLagObserver(master, followers, DistanceExtractor::extractPowDistance) { headScheduler: Scheduler
) : HeadLagObserver(master, followers, DistanceExtractor::extractPowDistance, headScheduler) {
companion object { companion object {
private val log = LoggerFactory.getLogger(EthereumHeadLagObserver::class.java) private val log = LoggerFactory.getLogger(EthereumHeadLagObserver::class.java)

View File

@@ -49,8 +49,8 @@ open class EthereumMultistream(
chain: Chain, chain: Chain,
val upstreams: MutableList<EthereumUpstream>, val upstreams: MutableList<EthereumUpstream>,
caches: Caches, caches: Caches,
headScheduler: Scheduler, private val headScheduler: Scheduler,
tracer: Tracer tracer: Tracer,
) : Multistream(chain, upstreams as MutableList<Upstream>, caches), EthereumLikeMultistream { ) : Multistream(chain, upstreams as MutableList<Upstream>, caches), EthereumLikeMultistream {
private var head: DynamicMergedHead = DynamicMergedHead( private var head: DynamicMergedHead = DynamicMergedHead(
@@ -131,7 +131,7 @@ open class EthereumMultistream(
} }
override fun makeLagObserver(): HeadLagObserver { override fun makeLagObserver(): HeadLagObserver {
return EthereumHeadLagObserver(head, upstreams as Collection<Upstream>) return EthereumHeadLagObserver(head, upstreams as Collection<Upstream>, headScheduler)
} }
override fun isRunning(): Boolean { override fun isRunning(): Boolean {
@@ -192,7 +192,7 @@ open class EthereumMultistream(
when (it.size) { when (it.size) {
0 -> EmptyHead() 0 -> EmptyHead()
1 -> selected.first() 1 -> selected.first()
else -> MergedHead(selected, MostWorkForkChoice(), "Eth head ${it.map { it.getId() }}").apply { else -> MergedHead(selected, MostWorkForkChoice(), headScheduler, "Eth head ${it.map { it.getId() }}").apply {
start() start()
} }
} }

View File

@@ -20,25 +20,19 @@ import io.emeraldpay.dshackle.reader.JsonRpcReader
import io.emeraldpay.dshackle.upstream.BlockValidator import io.emeraldpay.dshackle.upstream.BlockValidator
import io.emeraldpay.dshackle.upstream.Lifecycle import io.emeraldpay.dshackle.upstream.Lifecycle
import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.ForkChoice
import org.springframework.scheduling.concurrent.CustomizableThreadFactory
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.scheduler.Schedulers import reactor.core.scheduler.Scheduler
import java.time.Duration import java.time.Duration
import java.util.concurrent.Executors
class EthereumRpcHead( class EthereumRpcHead(
private val api: JsonRpcReader, private val api: JsonRpcReader,
forkChoice: ForkChoice, forkChoice: ForkChoice,
upstreamId: String, upstreamId: String,
blockValidator: BlockValidator, blockValidator: BlockValidator,
private val headScheduler: Scheduler,
private val interval: Duration = Duration.ofSeconds(10), private val interval: Duration = Duration.ofSeconds(10),
) : DefaultEthereumHead(upstreamId, forkChoice, blockValidator), Lifecycle { ) : DefaultEthereumHead(upstreamId, forkChoice, blockValidator, headScheduler), Lifecycle {
companion object {
val scheduler =
Schedulers.fromExecutor(Executors.newCachedThreadPool(CustomizableThreadFactory("ethereum-rpc-head")))
}
private var refreshSubscription: Disposable? = null private var refreshSubscription: Disposable? = null
@@ -46,7 +40,7 @@ class EthereumRpcHead(
super.start() super.start()
refreshSubscription?.dispose() refreshSubscription?.dispose()
val base = Flux.interval(interval) val base = Flux.interval(interval)
.publishOn(scheduler) .publishOn(headScheduler)
.flatMap { .flatMap {
getLatestBlock(api) getLatestBlock(api)
} }

View File

@@ -44,8 +44,9 @@ class EthereumWsHead(
private val api: JsonRpcReader, private val api: JsonRpcReader,
private val wsSubscriptions: WsSubscriptions, private val wsSubscriptions: WsSubscriptions,
private val skipEnhance: Boolean, private val skipEnhance: Boolean,
private val wsConnectionResubscribeScheduler: Scheduler private val wsConnectionResubscribeScheduler: Scheduler,
) : DefaultEthereumHead(upstreamId, forkChoice, blockValidator), Lifecycle { headScheduler: Scheduler,
) : DefaultEthereumHead(upstreamId, forkChoice, blockValidator, headScheduler), Lifecycle {
private var connectionId: String? = null private var connectionId: String? = null
private var subscribed = false private var subscribed = false

View File

@@ -313,7 +313,7 @@ open class WsConnectionImpl(
) )
val sender = currentRequests.remove(msg.id.asNumber().toInt()) val sender = currentRequests.remove(msg.id.asNumber().toInt())
if (sender == null) { if (sender == null) {
log.warn("Unknown response received for ${msg.id}") log.warn("Unknown response received for ${msg.id} with body ${msg.value?.let { String(it) }}")
} else { } else {
try { try {
val emitResult = sender.tryEmitValue(rpcResponse) val emitResult = sender.tryEmitValue(rpcResponse)

View File

@@ -19,7 +19,8 @@ open class EthereumConnectorFactory(
private val httpFactory: HttpFactory?, private val httpFactory: HttpFactory?,
private val forkChoice: ForkChoice, private val forkChoice: ForkChoice,
private val blockValidator: BlockValidator, private val blockValidator: BlockValidator,
private val wsConnectionResubscribeScheduler: Scheduler private val wsConnectionResubscribeScheduler: Scheduler,
private val headScheduler: Scheduler
) : ConnectorFactory { ) : ConnectorFactory {
override fun isValid(): Boolean { override fun isValid(): Boolean {
@@ -50,7 +51,13 @@ open class EthereumConnectorFactory(
): EthereumConnector { ): EthereumConnector {
if (wsFactory != null && connectorType == WS_ONLY) { if (wsFactory != null && connectorType == WS_ONLY) {
return EthereumWsConnector( return EthereumWsConnector(
wsFactory, upstream, forkChoice, blockValidator, skipEnhance, wsConnectionResubscribeScheduler wsFactory,
upstream,
forkChoice,
blockValidator,
skipEnhance,
wsConnectionResubscribeScheduler,
headScheduler
) )
} }
if (httpFactory == null) { if (httpFactory == null) {
@@ -64,7 +71,8 @@ open class EthereumConnectorFactory(
forkChoice, forkChoice,
blockValidator, blockValidator,
skipEnhance, skipEnhance,
wsConnectionResubscribeScheduler wsConnectionResubscribeScheduler,
headScheduler
) )
} }

View File

@@ -33,7 +33,8 @@ class EthereumRpcConnector(
forkChoice: ForkChoice, forkChoice: ForkChoice,
blockValidator: BlockValidator, blockValidator: BlockValidator,
skipEnhance: Boolean, skipEnhance: Boolean,
wsConnectionResubscribeScheduler: Scheduler wsConnectionResubscribeScheduler: Scheduler,
headScheduler: Scheduler
) : EthereumConnector, CachesEnabled { ) : EthereumConnector, CachesEnabled {
private val pool: WsConnectionPool? private val pool: WsConnectionPool?
private val head: Head private val head: Head
@@ -43,35 +44,50 @@ class EthereumRpcConnector(
} }
init { init {
if (wsFactory != null) { pool = wsFactory?.create(null)
pool = wsFactory.create(null)
} else {
pool = null
}
head = when (connectorType) { head = when (connectorType) {
RPC_ONLY -> { RPC_ONLY -> {
log.warn("Setting up connector for $id upstream with RPC-only access, less effective than WS+RPC") log.warn("Setting up connector for $id upstream with RPC-only access, less effective than WS+RPC")
EthereumRpcHead(getIngressReader(), forkChoice, id, blockValidator) EthereumRpcHead(getIngressReader(), forkChoice, id, blockValidator, headScheduler)
} }
WS_ONLY -> { WS_ONLY -> {
throw IllegalStateException("WS-only mode is not supported in RPC connector") throw IllegalStateException("WS-only mode is not supported in RPC connector")
} }
RPC_REQUESTS_WITH_MIXED_HEAD -> { RPC_REQUESTS_WITH_MIXED_HEAD -> {
val wsHead = val wsHead =
EthereumWsHead( EthereumWsHead(
id, AlwaysForkChoice(), blockValidator, getIngressReader(), id,
WsSubscriptionsImpl(pool!!), skipEnhance, wsConnectionResubscribeScheduler AlwaysForkChoice(),
blockValidator,
getIngressReader(),
WsSubscriptionsImpl(pool!!),
skipEnhance,
wsConnectionResubscribeScheduler,
headScheduler
) )
// receive all new blocks through WebSockets, but also periodically verify with RPC in case if WS failed // receive all new blocks through WebSockets, but also periodically verify with RPC in case if WS failed
val rpcHead = val rpcHead =
EthereumRpcHead(getIngressReader(), AlwaysForkChoice(), id, blockValidator, Duration.ofSeconds(30)) EthereumRpcHead(
MergedHead(listOf(rpcHead, wsHead), forkChoice, "Merged for $id") getIngressReader(),
AlwaysForkChoice(),
id,
blockValidator,
headScheduler,
Duration.ofSeconds(30)
)
MergedHead(listOf(rpcHead, wsHead), forkChoice, headScheduler, "Merged for $id")
} }
RPC_REQUESTS_WITH_WS_HEAD -> { RPC_REQUESTS_WITH_WS_HEAD -> {
EthereumWsHead( EthereumWsHead(
id, AlwaysForkChoice(), blockValidator, getIngressReader(), id,
WsSubscriptionsImpl(pool!!), skipEnhance, wsConnectionResubscribeScheduler AlwaysForkChoice(),
blockValidator, getIngressReader(),
WsSubscriptionsImpl(pool!!), skipEnhance, wsConnectionResubscribeScheduler,
headScheduler
) )
} }
} }

View File

@@ -20,7 +20,8 @@ class EthereumWsConnector(
forkChoice: ForkChoice, forkChoice: ForkChoice,
blockValidator: BlockValidator, blockValidator: BlockValidator,
skipEnhance: Boolean, skipEnhance: Boolean,
wsConnectionResubscribeScheduler: Scheduler wsConnectionResubscribeScheduler: Scheduler,
headScheduler: Scheduler
) : EthereumConnector { ) : EthereumConnector {
private val pool: WsConnectionPool private val pool: WsConnectionPool
private val reader: JsonRpcReader private val reader: JsonRpcReader
@@ -32,8 +33,14 @@ class EthereumWsConnector(
reader = JsonRpcWsClient(pool) reader = JsonRpcWsClient(pool)
val wsSubscriptions = WsSubscriptionsImpl(pool) val wsSubscriptions = WsSubscriptionsImpl(pool)
head = EthereumWsHead( head = EthereumWsHead(
upstream.getId(), forkChoice, blockValidator, reader, upstream.getId(),
wsSubscriptions, skipEnhance, wsConnectionResubscribeScheduler forkChoice,
blockValidator,
reader,
wsSubscriptions,
skipEnhance,
wsConnectionResubscribeScheduler,
headScheduler
) )
subscriptions = EthereumWsIngressSubscription(wsSubscriptions) subscriptions = EthereumWsIngressSubscription(wsSubscriptions)
} }

View File

@@ -6,11 +6,13 @@ import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.HeadLagObserver
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import reactor.core.scheduler.Scheduler
class EthereumPosHeadLagObserver( class EthereumPosHeadLagObserver(
master: Head, master: Head,
followers: Collection<Upstream> followers: Collection<Upstream>,
) : HeadLagObserver(master, followers, DistanceExtractor::extractPriorityDistance) { headScheduler: Scheduler
) : HeadLagObserver(master, followers, DistanceExtractor::extractPriorityDistance, headScheduler) {
companion object { companion object {
private val log = LoggerFactory.getLogger(EthereumPosHeadLagObserver::class.java) private val log = LoggerFactory.getLogger(EthereumPosHeadLagObserver::class.java)

View File

@@ -47,7 +47,7 @@ open class EthereumPosMultiStream(
chain: Chain, chain: Chain,
val upstreams: MutableList<EthereumPosUpstream>, val upstreams: MutableList<EthereumPosUpstream>,
caches: Caches, caches: Caches,
headScheduler: Scheduler, private val headScheduler: Scheduler,
tracer: Tracer tracer: Tracer
) : Multistream(chain, upstreams as MutableList<Upstream>, caches), EthereumLikeMultistream { ) : Multistream(chain, upstreams as MutableList<Upstream>, caches), EthereumLikeMultistream {
@@ -104,7 +104,7 @@ open class EthereumPosMultiStream(
} }
override fun makeLagObserver(): HeadLagObserver = override fun makeLagObserver(): HeadLagObserver =
EthereumPosHeadLagObserver(head, ArrayList(upstreams)).apply { EthereumPosHeadLagObserver(head, ArrayList(upstreams), headScheduler).apply {
start() start()
} }
@@ -169,6 +169,7 @@ open class EthereumPosMultiStream(
else -> MergedHead( else -> MergedHead(
selected, selected,
PriorityForkChoice(), PriorityForkChoice(),
headScheduler,
"ETH head for ${it.map { it.getId() }}" "ETH head for ${it.map { it.getId() }}"
).apply { ).apply {
start() start()

View File

@@ -40,6 +40,7 @@ import io.emeraldpay.etherjar.rpc.RpcException
import org.reactivestreams.Publisher import org.reactivestreams.Publisher
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
import java.math.BigInteger import java.math.BigInteger
import java.time.Instant import java.time.Instant
import java.util.Locale import java.util.Locale
@@ -51,9 +52,10 @@ class BitcoinGrpcUpstream(
role: UpstreamsConfig.UpstreamRole, role: UpstreamsConfig.UpstreamRole,
chain: Chain, chain: Chain,
val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, val remote: ReactorBlockchainGrpc.ReactorBlockchainStub,
private val client: JsonRpcGrpcClient, client: JsonRpcGrpcClient,
overrideLabels: UpstreamsConfig.Labels?, overrideLabels: UpstreamsConfig.Labels?,
chainConfig: ChainsConfig.ChainConfig chainConfig: ChainsConfig.ChainConfig,
headScheduler: Scheduler,
) : BitcoinUpstream( ) : BitcoinUpstream(
"${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}",
chain, chain,
@@ -100,7 +102,10 @@ class BitcoinGrpcUpstream(
} }
} }
private val upstreamStatus = GrpcUpstreamStatus(overrideLabels) private val upstreamStatus = GrpcUpstreamStatus(overrideLabels)
private val grpcHead = GrpcHead(getId(), chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice()) private val grpcHead = GrpcHead(
getId(), chain, this, remote, blockConverter, reloadBlock,
MostWorkForkChoice(), headScheduler
)
private val timeout = Defaults.timeout private val timeout = Defaults.timeout
private var capabilities: Set<Capability> = emptySet() private var capabilities: Set<Capability> = emptySet()
private val buildInfo: BuildInfo = BuildInfo() private val buildInfo: BuildInfo = BuildInfo()

View File

@@ -45,6 +45,7 @@ import io.emeraldpay.etherjar.rpc.RpcException
import org.reactivestreams.Publisher import org.reactivestreams.Publisher
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
import java.math.BigInteger import java.math.BigInteger
import java.time.Instant import java.time.Instant
import java.util.Locale import java.util.Locale
@@ -59,7 +60,8 @@ open class EthereumGrpcUpstream(
private val remote: ReactorBlockchainStub, private val remote: ReactorBlockchainStub,
client: JsonRpcGrpcClient, client: JsonRpcGrpcClient,
overrideLabels: UpstreamsConfig.Labels?, overrideLabels: UpstreamsConfig.Labels?,
chainConfig: ChainsConfig.ChainConfig chainConfig: ChainsConfig.ChainConfig,
headScheduler: Scheduler,
) : EthereumUpstream( ) : EthereumUpstream(
"${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}",
hash, hash,
@@ -110,7 +112,10 @@ open class EthereumGrpcUpstream(
} }
private val upstreamStatus = GrpcUpstreamStatus(overrideLabels) private val upstreamStatus = GrpcUpstreamStatus(overrideLabels)
private val grpcHead = GrpcHead(getId(), chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice()) private val grpcHead = GrpcHead(
getId(), chain, this, remote,
blockConverter, reloadBlock, MostWorkForkChoice(), headScheduler
)
private var capabilities: Set<Capability> = emptySet() private var capabilities: Set<Capability> = emptySet()
private val buildInfo: BuildInfo = BuildInfo() private val buildInfo: BuildInfo = BuildInfo()

View File

@@ -39,6 +39,7 @@ import io.emeraldpay.dshackle.upstream.forkchoice.NoChoiceWithPriorityForkChoice
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
import io.emeraldpay.etherjar.domain.BlockHash import io.emeraldpay.etherjar.domain.BlockHash
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.scheduler.Scheduler
import java.math.BigInteger import java.math.BigInteger
import java.time.Instant import java.time.Instant
import java.util.Locale import java.util.Locale
@@ -53,7 +54,8 @@ open class EthereumPosGrpcUpstream(
client: JsonRpcGrpcClient, client: JsonRpcGrpcClient,
nodeRating: Int, nodeRating: Int,
overrideLabels: UpstreamsConfig.Labels?, overrideLabels: UpstreamsConfig.Labels?,
chainConfig: ChainsConfig.ChainConfig chainConfig: ChainsConfig.ChainConfig,
headScheduler: Scheduler,
) : EthereumPosUpstream( ) : EthereumPosUpstream(
"${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}", "${parentId}_${chain.chainCode.lowercase(Locale.getDefault())}",
hash, hash,
@@ -84,7 +86,10 @@ open class EthereumPosGrpcUpstream(
} }
private val upstreamStatus = GrpcUpstreamStatus(overrideLabels) private val upstreamStatus = GrpcUpstreamStatus(overrideLabels)
private val grpcHead = GrpcHead(getId(), chain, this, remote, blockConverter, null, NoChoiceWithPriorityForkChoice(nodeRating, parentId)) private val grpcHead = GrpcHead(
getId(), chain, this, remote, blockConverter, null,
NoChoiceWithPriorityForkChoice(nodeRating, parentId), headScheduler
)
private var capabilities: Set<Capability> = emptySet() private var capabilities: Set<Capability> = emptySet()
private val buildInfo: BuildInfo = BuildInfo() private val buildInfo: BuildInfo = BuildInfo()

View File

@@ -31,6 +31,7 @@ import org.reactivestreams.Publisher
import reactor.core.Disposable import reactor.core.Disposable
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Scheduler
import reactor.kotlin.extra.retry.retryExponentialBackoff import reactor.kotlin.extra.retry.retryExponentialBackoff
import java.time.Duration import java.time.Duration
import java.util.function.Function import java.util.function.Function
@@ -48,8 +49,9 @@ class GrpcHead(
* Populate block data with all missing details, of any * Populate block data with all missing details, of any
*/ */
private val enhancer: Function<BlockContainer, Publisher<BlockContainer>>?, private val enhancer: Function<BlockContainer, Publisher<BlockContainer>>?,
private val forkChoice: ForkChoice private val forkChoice: ForkChoice,
) : AbstractHead(forkChoice, upstreamId = id), Lifecycle { headScheduler: Scheduler,
) : AbstractHead(forkChoice, headScheduler, upstreamId = id), Lifecycle {
private var headSubscription: Disposable? = null private var headSubscription: Disposable? = null

View File

@@ -75,7 +75,8 @@ class GrpcUpstreams(
private val chainsConfig: ChainsConfig, private val chainsConfig: ChainsConfig,
private val grpcTracing: GrpcTracing, private val grpcTracing: GrpcTracing,
private val clientSpansInterceptor: ClientInterceptor?, private val clientSpansInterceptor: ClientInterceptor?,
private var maxMetadataSize: Int private var maxMetadataSize: Int,
private val headScheduler: Scheduler
) { ) {
private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java) private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java)
@@ -213,13 +214,34 @@ class GrpcUpstreams(
private val creators: Map<BlockchainType, (chain: Chain, client: JsonRpcGrpcClient) -> DefaultUpstream> = mapOf( private val creators: Map<BlockchainType, (chain: Chain, client: JsonRpcGrpcClient) -> DefaultUpstream> = mapOf(
BlockchainType.EVM_POW to { chain, rpcClient -> BlockchainType.EVM_POW to { chain, rpcClient ->
EthereumGrpcUpstream(id, hash, role, chain, client, rpcClient, labels, chainsConfig.resolve(chain)) EthereumGrpcUpstream(
id,
hash,
role,
chain,
client,
rpcClient,
labels,
chainsConfig.resolve(chain),
headScheduler
)
}, },
BlockchainType.EVM_POS to { chain, rpcClient -> BlockchainType.EVM_POS to { chain, rpcClient ->
EthereumPosGrpcUpstream(id, hash, role, chain, client, rpcClient, nodeRating, labels, chainsConfig.resolve(chain)) EthereumPosGrpcUpstream(
id,
hash,
role,
chain,
client,
rpcClient,
nodeRating,
labels,
chainsConfig.resolve(chain),
headScheduler
)
}, },
BlockchainType.BITCOIN to { chain, rpcClient -> BlockchainType.BITCOIN to { chain, rpcClient ->
BitcoinGrpcUpstream(id, role, chain, client, rpcClient, labels, chainsConfig.resolve(chain)) BitcoinGrpcUpstream(id, role, chain, client, rpcClient, labels, chainsConfig.resolve(chain), headScheduler)
} }
) )

View File

@@ -31,7 +31,8 @@ class ConfiguredUpstreamsSpec extends Specification {
ChainsConfig.default(), ChainsConfig.default(),
GrpcTracing.create(Tracing.newBuilder().build()), GrpcTracing.create(Tracing.newBuilder().build()),
Schedulers.boundedElastic(), Schedulers.boundedElastic(),
null null,
Schedulers.boundedElastic(),
) )
def methods = new UpstreamsConfig.Methods( def methods = new UpstreamsConfig.Methods(
[ [
@@ -62,7 +63,8 @@ class ConfiguredUpstreamsSpec extends Specification {
ChainsConfig.default(), ChainsConfig.default(),
GrpcTracing.create(Tracing.newBuilder().build()), GrpcTracing.create(Tracing.newBuilder().build()),
Schedulers.boundedElastic(), Schedulers.boundedElastic(),
null null,
Schedulers.boundedElastic(),
) )
def methods = new UpstreamsConfig.Methods( def methods = new UpstreamsConfig.Methods(
[ [
@@ -92,7 +94,8 @@ class ConfiguredUpstreamsSpec extends Specification {
ChainsConfig.default(), ChainsConfig.default(),
GrpcTracing.create(Tracing.newBuilder().build()), GrpcTracing.create(Tracing.newBuilder().build()),
Schedulers.boundedElastic(), Schedulers.boundedElastic(),
null null,
Schedulers.boundedElastic(),
) )
expect: expect:
configurer.getHash(node, src) == expected configurer.getHash(node, src) == expected
@@ -117,7 +120,8 @@ class ConfiguredUpstreamsSpec extends Specification {
ChainsConfig.default(), ChainsConfig.default(),
GrpcTracing.create(Tracing.newBuilder().build()), GrpcTracing.create(Tracing.newBuilder().build()),
Schedulers.boundedElastic(), Schedulers.boundedElastic(),
null null,
Schedulers.boundedElastic(),
) )
when: when:
def h1 = configurer.getHash(null, "hohoho") def h1 = configurer.getHash(null, "hohoho")
@@ -147,7 +151,8 @@ class ConfiguredUpstreamsSpec extends Specification {
ChainsConfig.default(), ChainsConfig.default(),
GrpcTracing.create(Tracing.newBuilder().build()), GrpcTracing.create(Tracing.newBuilder().build()),
Schedulers.boundedElastic(), Schedulers.boundedElastic(),
null null,
Schedulers.boundedElastic(),
) )
def methodsGroup = new UpstreamsConfig.MethodGroups( def methodsGroup = new UpstreamsConfig.MethodGroups(
["filter"] as Set, ["filter"] as Set,

View File

@@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull
import reactor.core.publisher.Sinks import reactor.core.publisher.Sinks
import reactor.test.StepVerifier import reactor.test.StepVerifier
import spock.lang.Specification import spock.lang.Specification
import reactor.core.scheduler.Schedulers
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@@ -137,7 +138,7 @@ class AbstractHeadSpec extends Specification {
BlockContainer getHead() { BlockContainer getHead() {
return null return null
} }
}, new BlockValidator.AlwaysValid(), 100_000) }, Schedulers.boundedElastic(), new BlockValidator.AlwaysValid() , 100_000)
} }
} }
} }

View File

@@ -51,12 +51,17 @@ class FilteredApisSpec extends Specification {
create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" } create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" }
} }
def connectorFactory = new EthereumConnectorFactory( def connectorFactory = new EthereumConnectorFactory(
EthereumConnectorFactory.ConnectorMode.RPC_ONLY, null, httpFactory, EthereumConnectorFactory.ConnectorMode.RPC_ONLY,
new MostWorkForkChoice(), BlockValidator.ALWAYS_VALID, Schedulers.boundedElastic() null,
httpFactory,
new MostWorkForkChoice(),
BlockValidator.ALWAYS_VALID,
Schedulers.boundedElastic(),
Schedulers.boundedElastic()
) )
new EthereumRpcUpstream( new EthereumRpcUpstream(
"test", "test",
(byte)123, (byte) 123,
Chain.ETHEREUM, Chain.ETHEREUM,
new UpstreamsConfig.PartialOptions().buildOptions(), new UpstreamsConfig.PartialOptions().buildOptions(),
UpstreamsConfig.UpstreamRole.PRIMARY, UpstreamsConfig.UpstreamRole.PRIMARY,
@@ -79,8 +84,8 @@ class FilteredApisSpec extends Specification {
.expectNext(upstreams[0]) .expectNext(upstreams[0])
.expectNext(upstreams[2]) .expectNext(upstreams[2])
.expectNext(upstreams[3]) .expectNext(upstreams[3])
.expectComplete() .expectComplete()
.verify(Duration.ofSeconds(1)) .verify(Duration.ofSeconds(1))
when: when:
iter = new FilteredApis(Chain.ETHEREUM, upstreams, matcher, 1, 1, 0) iter = new FilteredApis(Chain.ETHEREUM, upstreams, matcher, 1, 1, 0)
@@ -114,19 +119,19 @@ class FilteredApisSpec extends Specification {
expect: expect:
wait == apis.waitDuration(n).toMillis() as Integer wait == apis.waitDuration(n).toMillis() as Integer
where: where:
n | wait n | wait
0 | 100 0 | 100
1 | 100 1 | 100
2 | 400 2 | 400
3 | 900 3 | 900
4 | 1600 4 | 1600
5 | 2500 5 | 2500
6 | 3600 6 | 3600
7 | 4900 7 | 4900
8 | 5000 8 | 5000
9 | 5000 9 | 5000
10 | 5000 10 | 5000
-1 | 100 -1 | 100
} }
@Retry @Retry
@@ -169,8 +174,8 @@ class FilteredApisSpec extends Specification {
.expectNext(up1, up2).as("Batch 3") .expectNext(up1, up2).as("Batch 3")
.expectNoEvent(Duration.ofMillis(900)).as("Wait 3") .expectNoEvent(Duration.ofMillis(900)).as("Wait 3")
.expectNext(up1, up2).as("Batch 4") .expectNext(up1, up2).as("Batch 4")
.expectComplete() .expectComplete()
.verify(Duration.ofSeconds(10)) .verify(Duration.ofSeconds(10))
} }
def "Starts with right position"() { def "Starts with right position"() {

View File

@@ -22,10 +22,12 @@ import io.emeraldpay.etherjar.rpc.json.BlockJson
import org.jetbrains.annotations.NotNull import org.jetbrains.annotations.NotNull
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Sinks import reactor.core.publisher.Sinks
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier import reactor.test.StepVerifier
import reactor.util.function.Tuples import reactor.util.function.Tuples
import spock.lang.Specification import spock.lang.Specification
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
@@ -114,7 +116,8 @@ class HeadLagObserverSpec extends Specification {
class TestHeadLagObserver extends HeadLagObserver { class TestHeadLagObserver extends HeadLagObserver {
TestHeadLagObserver(@NotNull Head master, @NotNull Collection<? extends Upstream> followers) { TestHeadLagObserver(@NotNull Head master, @NotNull Collection<? extends Upstream> followers) {
super(master, followers, DistanceExtractor.@Companion::extractPowDistance, Duration.ofNanos(1)) super(master, followers, DistanceExtractor.@Companion::extractPowDistance,
Schedulers.boundedElastic(), Duration.ofNanos(1))
} }
@Override @Override

View File

@@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.scheduler.Schedulers
import spock.lang.Specification import spock.lang.Specification
class MergedHeadSpec extends Specification { class MergedHeadSpec extends Specification {
@@ -36,7 +37,7 @@ class MergedHeadSpec extends Specification {
} }
when: when:
def merged = new MergedHead([head1, head2, head3], new MostWorkForkChoice()) def merged = new MergedHead([head1, head2, head3], new MostWorkForkChoice(), Schedulers.boundedElastic())
merged.start() merged.start()
then: then:
@@ -45,14 +46,14 @@ class MergedHeadSpec extends Specification {
class TestHead1 extends AbstractHead { class TestHead1 extends AbstractHead {
TestHead1() { TestHead1() {
super(new MostWorkForkChoice(), new BlockValidator.AlwaysValid(), 100_000) super(new MostWorkForkChoice(), Schedulers.boundedElastic(), new BlockValidator.AlwaysValid(), 100_000)
} }
} }
class TestHead2 extends AbstractHead implements Lifecycle { class TestHead2 extends AbstractHead implements Lifecycle {
TestHead2() { TestHead2() {
super(new MostWorkForkChoice(), new BlockValidator.AlwaysValid(), 100_000) super(new MostWorkForkChoice(), Schedulers.boundedElastic(), new BlockValidator.AlwaysValid(), 100_000)
} }
@Override @Override

View File

@@ -19,6 +19,7 @@ import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier import reactor.test.StepVerifier
import spock.lang.Specification import spock.lang.Specification
@@ -85,7 +86,7 @@ class BitcoinRpcHeadSpec extends Specification {
_ * read(new JsonRpcRequest("getblock", [hash1])) >> Mono.just(new JsonRpcResponse(block1.bytes, null)) _ * read(new JsonRpcRequest("getblock", [hash1])) >> Mono.just(new JsonRpcResponse(block1.bytes, null))
_ * read(new JsonRpcRequest("getblock", [hash2])) >> Mono.just(new JsonRpcResponse(block2.bytes, null)) _ * read(new JsonRpcRequest("getblock", [hash2])) >> Mono.just(new JsonRpcResponse(block2.bytes, null))
} }
BitcoinRpcHead head = new BitcoinRpcHead(api, new ExtractBlock(), Duration.ofMillis(200)) BitcoinRpcHead head = new BitcoinRpcHead(api, new ExtractBlock(), Duration.ofMillis(200), Schedulers.boundedElastic())
when: when:
def act = head.flux.take(2) def act = head.flux.take(2)

View File

@@ -24,6 +24,7 @@ import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import io.emeraldpay.etherjar.domain.BlockHash import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.etherjar.rpc.json.BlockJson import io.emeraldpay.etherjar.rpc.json.BlockJson
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier import reactor.test.StepVerifier
import spock.lang.Specification import spock.lang.Specification
@@ -31,7 +32,7 @@ import java.time.Instant
class DefaultEthereumHeadSpec extends Specification { class DefaultEthereumHeadSpec extends Specification {
DefaultEthereumHead head = new DefaultEthereumHead("upstream", new MostWorkForkChoice(), BlockValidator.ALWAYS_VALID) DefaultEthereumHead head = new DefaultEthereumHead("upstream", new MostWorkForkChoice(), BlockValidator.ALWAYS_VALID, Schedulers.boundedElastic())
ObjectMapper objectMapper = Global.objectMapper ObjectMapper objectMapper = Global.objectMapper
BlockHash parent = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915210") BlockHash parent = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915210")

View File

@@ -66,7 +66,7 @@ class EthereumWsHeadSpec extends Specification {
1 * it.connectionInfoFlux() >> Flux.empty() 1 * it.connectionInfoFlux() >> Flux.empty()
} }
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, false, Schedulers.boundedElastic()) def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, false, Schedulers.boundedElastic(), Schedulers.boundedElastic())
when: when:
def act = head.listenNewHeads().blockFirst() def act = head.listenNewHeads().blockFirst()
@@ -107,7 +107,7 @@ class EthereumWsHeadSpec extends Specification {
] ]
} }
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic()) def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic())
when: when:
def act = head.getFlux() def act = head.getFlux()
@@ -161,7 +161,7 @@ class EthereumWsHeadSpec extends Specification {
] ]
} }
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic()) def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic())
when: when:
def act = head.getFlux() def act = head.getFlux()
@@ -201,7 +201,7 @@ class EthereumWsHeadSpec extends Specification {
] ]
} }
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic()) def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic())
when: when:
def act = head.getFlux() def act = head.getFlux()
@@ -240,7 +240,7 @@ class EthereumWsHeadSpec extends Specification {
] ]
} }
def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic()) def head = new EthereumWsHead("fake", new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic())
when: when:
def act = head.getFlux() def act = head.getFlux()

View File

@@ -37,6 +37,7 @@ import io.emeraldpay.etherjar.rpc.json.BlockJson
import io.grpc.stub.StreamObserver import io.grpc.stub.StreamObserver
import io.micrometer.core.instrument.Counter import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.Timer import io.micrometer.core.instrument.Timer
import reactor.core.scheduler.Schedulers
import spock.lang.Specification import spock.lang.Specification
import java.time.Duration import java.time.Duration
@@ -89,7 +90,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
) )
} }
}) })
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default()) def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.boundedElastic())
upstream.setLag(0) upstream.setLag(0)
upstream.update( upstream.update(
BlockchainOuterClass.DescribeChain.newBuilder() BlockchainOuterClass.DescribeChain.newBuilder()
@@ -160,7 +161,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
}).start() }).start()
} }
}) })
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, Chain.ETHEREUM, client, new JsonRpcGrpcClient(client, Chain.ETHEREUM, metrics), null, ChainsConfig.ChainConfig.default()) def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, Chain.ETHEREUM, client, new JsonRpcGrpcClient(client, Chain.ETHEREUM, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.boundedElastic())
upstream.setLag(0) upstream.setLag(0)
upstream.update( upstream.update(
BlockchainOuterClass.DescribeChain.newBuilder() BlockchainOuterClass.DescribeChain.newBuilder()
@@ -232,7 +233,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
finished.complete(true) finished.complete(true)
} }
}) })
def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default()) def upstream = new EthereumGrpcUpstream("test", hash, UpstreamsConfig.UpstreamRole.PRIMARY, chain, client, new JsonRpcGrpcClient(client, chain, metrics), null, ChainsConfig.ChainConfig.default(), Schedulers.boundedElastic())
upstream.setLag(0) upstream.setLag(0)
upstream.update( upstream.update(
BlockchainOuterClass.DescribeChain.newBuilder() BlockchainOuterClass.DescribeChain.newBuilder()

View File

@@ -24,6 +24,7 @@ import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.DefaultUpstream import io.emeraldpay.dshackle.upstream.DefaultUpstream
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import io.grpc.stub.StreamObserver import io.grpc.stub.StreamObserver
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier import reactor.test.StepVerifier
import spock.lang.Specification import spock.lang.Specification
@@ -66,7 +67,10 @@ class GrpcHeadSpec extends Specification {
Chain.BITCOIN, Chain.BITCOIN,
Stub(DefaultUpstream), Stub(DefaultUpstream),
client, client,
convert, null, new MostWorkForkChoice() convert,
null,
new MostWorkForkChoice(),
Schedulers.boundedElastic()
) )
when: when:
def act = head.getFlux() def act = head.getFlux()
@@ -130,7 +134,10 @@ class GrpcHeadSpec extends Specification {
Chain.BITCOIN, Chain.BITCOIN,
Stub(DefaultUpstream), Stub(DefaultUpstream),
client, client,
convert, null, new MostWorkForkChoice() convert,
null,
new MostWorkForkChoice(),
Schedulers.boundedElastic()
) )
when: when:
def act = head.getFlux() def act = head.getFlux()