Merge pull request #110 from p2p-org/stable-multistreams
Stabilisation of upstreams
This commit is contained in:
@@ -22,33 +22,30 @@ import io.emeraldpay.dshackle.Chain
|
|||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class SubscribeStatus(
|
class SubscribeStatus(
|
||||||
@Autowired private val multistreamHolder: MultistreamHolder
|
private val multistreamHolder: MultistreamHolder
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun subscribeStatus(requestMono: Mono<BlockchainOuterClass.StatusRequest>): Flux<BlockchainOuterClass.ChainStatus> {
|
fun subscribeStatus(requestMono: Mono<BlockchainOuterClass.StatusRequest>): Flux<BlockchainOuterClass.ChainStatus> {
|
||||||
return requestMono.flatMapMany { req ->
|
return requestMono.flatMapMany { req ->
|
||||||
// check status for all requested chains
|
if (req.chainsCount == 0) {
|
||||||
val all = req.chainsList.map {
|
Flux.error(RuntimeException("empty chains list"))
|
||||||
val chain = Chain.byId(it.number)
|
} else {
|
||||||
val up = multistreamHolder.getUpstream(chain)
|
// check status for all requested chains
|
||||||
if (up == null) {
|
val all = req.chainsList.map {
|
||||||
// when the chain is not configured return just UNAVAILABLE
|
val chain = Chain.byId(it.number)
|
||||||
Mono.just(chainUnavailable(chain)).flux()
|
val up = multistreamHolder.getUpstream(chain)
|
||||||
} else {
|
|
||||||
// when configured subscribe to its updates
|
|
||||||
up.observeStatus().map { availability ->
|
up.observeStatus().map { availability ->
|
||||||
chainStatus(chain, availability, up)
|
chainStatus(chain, availability, up)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Flux.merge(all)
|
||||||
}
|
}
|
||||||
Flux.merge(all)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +71,4 @@ class SubscribeStatus(
|
|||||||
.setQuorum(quorum)
|
.setQuorum(quorum)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChainSubscription(val chain: Chain, val up: Multistream, val avail: UpstreamAvailability)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ import org.springframework.boot.ApplicationArguments
|
|||||||
import org.springframework.boot.ApplicationRunner
|
import org.springframework.boot.ApplicationRunner
|
||||||
import org.springframework.context.ApplicationEventPublisher
|
import org.springframework.context.ApplicationEventPublisher
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.scheduler.Scheduler
|
||||||
import reactor.core.publisher.Mono
|
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -68,13 +68,13 @@ open class ConfiguredUpstreams(
|
|||||||
|
|
||||||
private val log = LoggerFactory.getLogger(ConfiguredUpstreams::class.java)
|
private val log = LoggerFactory.getLogger(ConfiguredUpstreams::class.java)
|
||||||
private var seq = AtomicInteger(0)
|
private var seq = AtomicInteger(0)
|
||||||
|
|
||||||
private val hashes: MutableMap<Byte, Boolean> = HashMap()
|
private val hashes: MutableMap<Byte, Boolean> = HashMap()
|
||||||
|
|
||||||
|
lateinit var grpcUpstreamsScheduler: Scheduler
|
||||||
|
|
||||||
override fun run(args: ApplicationArguments) {
|
override fun run(args: ApplicationArguments) {
|
||||||
log.debug("Starting upstreams")
|
log.debug("Starting upstreams")
|
||||||
val defaultOptions = buildDefaultOptions(config)
|
val defaultOptions = buildDefaultOptions(config)
|
||||||
val observerScheduler = Schedulers.newParallel("status-observer", 3)
|
|
||||||
config.upstreams.forEach { up ->
|
config.upstreams.forEach { up ->
|
||||||
if (!up.isEnabled) {
|
if (!up.isEnabled) {
|
||||||
log.debug("Upstream ${up.id} is disabled")
|
log.debug("Upstream ${up.id} is disabled")
|
||||||
@@ -111,20 +111,8 @@ open class ConfiguredUpstreams(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
upstream?.let {
|
upstream?.let {
|
||||||
Flux.concat(Mono.just(UpstreamChangeEvent.ChangeType.ADDED), upstream.observeStatus())
|
val event = UpstreamChangeEvent(chain, upstream, UpstreamChangeEvent.ChangeType.ADDED)
|
||||||
.distinctUntilChanged()
|
eventPublisher.publishEvent(event)
|
||||||
.subscribeOn(observerScheduler)
|
|
||||||
.subscribe { status ->
|
|
||||||
when (status) {
|
|
||||||
UpstreamAvailability.UNAVAILABLE -> UpstreamChangeEvent.ChangeType.REMOVED
|
|
||||||
else -> UpstreamChangeEvent.ChangeType.REVALIDATED
|
|
||||||
}.let { eventType ->
|
|
||||||
if (eventType == UpstreamChangeEvent.ChangeType.REMOVED) {
|
|
||||||
log.warn("Remove upstream ${it::class.java.simpleName}:${upstream.getId()} due to $status")
|
|
||||||
}
|
|
||||||
eventPublisher.publishEvent(UpstreamChangeEvent(chain, upstream, eventType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,6 +289,12 @@ open class ConfiguredUpstreams(
|
|||||||
config: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>,
|
config: UpstreamsConfig.Upstream<UpstreamsConfig.GrpcConnection>,
|
||||||
options: UpstreamsConfig.Options
|
options: UpstreamsConfig.Options
|
||||||
) {
|
) {
|
||||||
|
if (!this::grpcUpstreamsScheduler.isInitialized) {
|
||||||
|
grpcUpstreamsScheduler = Schedulers.fromExecutorService(
|
||||||
|
Executors.newFixedThreadPool(2),
|
||||||
|
"GrpcUpstreamsStatuses"
|
||||||
|
)
|
||||||
|
}
|
||||||
val endpoint = config.connection!!
|
val endpoint = config.connection!!
|
||||||
val hash = getHash(nodeId, "${endpoint.host}:${endpoint.port}")
|
val hash = getHash(nodeId, "${endpoint.host}:${endpoint.port}")
|
||||||
val ds = GrpcUpstreams(
|
val ds = GrpcUpstreams(
|
||||||
@@ -313,6 +307,7 @@ open class ConfiguredUpstreams(
|
|||||||
fileResolver,
|
fileResolver,
|
||||||
endpoint.upstreamRating,
|
endpoint.upstreamRating,
|
||||||
config.labels,
|
config.labels,
|
||||||
|
grpcUpstreamsScheduler,
|
||||||
channelExecutor
|
channelExecutor
|
||||||
).apply {
|
).apply {
|
||||||
timeout = options.timeout
|
timeout = options.timeout
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ abstract class DefaultUpstream(
|
|||||||
|
|
||||||
fun onStatus(value: BlockchainOuterClass.ChainStatus) {
|
fun onStatus(value: BlockchainOuterClass.ChainStatus) {
|
||||||
val available = value.availability
|
val available = value.availability
|
||||||
val quorum = value.quorum
|
|
||||||
setStatus(
|
setStatus(
|
||||||
if (available != null) UpstreamAvailability.fromGrpc(available.number) else UpstreamAvailability.UNAVAILABLE
|
if (available != null) UpstreamAvailability.fromGrpc(available.number) else UpstreamAvailability.UNAVAILABLE
|
||||||
)
|
)
|
||||||
@@ -98,7 +97,7 @@ abstract class DefaultUpstream(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statusByLag(lag: Long, proposed: UpstreamAvailability): UpstreamAvailability {
|
private fun statusByLag(lag: Long, proposed: UpstreamAvailability): UpstreamAvailability {
|
||||||
if (options.disableValidation == true) {
|
if (options.disableValidation == true) {
|
||||||
// if we specifically told that this upstream should be _always valid_ then skip
|
// if we specifically told that this upstream should be _always valid_ then skip
|
||||||
// the status calculation and trust the proposed value as is
|
// the status calculation and trust the proposed value as is
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ open class DynamicMergedHead(
|
|||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
super.stop()
|
super.stop()
|
||||||
|
log.info("Stop dynamic merged head for [$label]")
|
||||||
dynamicFlux.stop()
|
dynamicFlux.stop()
|
||||||
subscription?.dispose()
|
subscription?.dispose()
|
||||||
subscription = null
|
subscription = null
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ open class EthereumMultistream(
|
|||||||
|
|
||||||
private var head: DynamicMergedHead = DynamicMergedHead(
|
private var head: DynamicMergedHead = DynamicMergedHead(
|
||||||
PriorityForkChoice(),
|
PriorityForkChoice(),
|
||||||
"ETH Multistream",
|
"ETH Multistream of ${chain.chainCode}",
|
||||||
headScheduler
|
headScheduler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ open class EthereumPosMultiStream(
|
|||||||
|
|
||||||
private var head: DynamicMergedHead = DynamicMergedHead(
|
private var head: DynamicMergedHead = DynamicMergedHead(
|
||||||
PriorityForkChoice(),
|
PriorityForkChoice(),
|
||||||
"ETH Pos Multistream",
|
"ETH Pos Multistream of ${chain.chainCode}",
|
||||||
headScheduler
|
headScheduler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class BitcoinGrpcUpstream(
|
|||||||
}
|
}
|
||||||
private val upstreamStatus = GrpcUpstreamStatus(overrideLabels)
|
private val upstreamStatus = GrpcUpstreamStatus(overrideLabels)
|
||||||
private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice())
|
private val grpcHead = GrpcHead(chain, this, remote, blockConverter, reloadBlock, MostWorkForkChoice())
|
||||||
var timeout = Defaults.timeout
|
private val timeout = Defaults.timeout
|
||||||
private var capabilities: Set<Capability> = emptySet()
|
private var capabilities: Set<Capability> = emptySet()
|
||||||
|
|
||||||
override fun getBlockchainApi(): ReactorBlockchainGrpc.ReactorBlockchainStub {
|
override fun getBlockchainApi(): ReactorBlockchainGrpc.ReactorBlockchainStub {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ open class EthereumPosGrpcUpstream(
|
|||||||
private var capabilities: Set<Capability> = emptySet()
|
private var capabilities: Set<Capability> = emptySet()
|
||||||
|
|
||||||
private val defaultReader: JsonRpcReader = client.getReader()
|
private val defaultReader: JsonRpcReader = client.getReader()
|
||||||
var timeout = Defaults.timeout
|
private val timeout = Defaults.timeout
|
||||||
private val ethereumSubscriptions = EthereumDshackleIngressSubscription(chain, remote)
|
private val ethereumSubscriptions = EthereumDshackleIngressSubscription(chain, remote)
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.grpc
|
package io.emeraldpay.dshackle.upstream.grpc
|
||||||
|
|
||||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
import io.emeraldpay.api.proto.BlockchainOuterClass.*
|
||||||
|
import io.emeraldpay.api.proto.Common
|
||||||
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
|
||||||
import io.emeraldpay.dshackle.BlockchainType
|
import io.emeraldpay.dshackle.BlockchainType
|
||||||
import io.emeraldpay.dshackle.Chain
|
import io.emeraldpay.dshackle.Chain
|
||||||
@@ -26,6 +27,7 @@ import io.emeraldpay.dshackle.config.AuthConfig
|
|||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
import io.emeraldpay.dshackle.startup.UpstreamChangeEvent
|
||||||
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
import io.emeraldpay.dshackle.upstream.DefaultUpstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.Lifecycle
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcGrpcClient
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics
|
import io.emeraldpay.dshackle.upstream.rpcclient.RpcMetrics
|
||||||
@@ -43,6 +45,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import reactor.core.Disposable
|
import reactor.core.Disposable
|
||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
|
import reactor.core.scheduler.Scheduler
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
@@ -60,13 +63,14 @@ class GrpcUpstreams(
|
|||||||
private val fileResolver: FileResolver,
|
private val fileResolver: FileResolver,
|
||||||
private val nodeRating: Int,
|
private val nodeRating: Int,
|
||||||
private val labels: UpstreamsConfig.Labels,
|
private val labels: UpstreamsConfig.Labels,
|
||||||
private val executor: Executor
|
private val chainStatusScheduler: Scheduler,
|
||||||
|
private val grpcExecutor: Executor
|
||||||
) {
|
) {
|
||||||
private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java)
|
private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java)
|
||||||
|
|
||||||
var timeout = Defaults.timeout
|
var timeout = Defaults.timeout
|
||||||
|
|
||||||
private var client: ReactorBlockchainGrpc.ReactorBlockchainStub? = null
|
lateinit var client: ReactorBlockchainGrpc.ReactorBlockchainStub
|
||||||
private val known = HashMap<Chain, DefaultUpstream>()
|
private val known = HashMap<Chain, DefaultUpstream>()
|
||||||
private val lock = ReentrantLock()
|
private val lock = ReentrantLock()
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ class GrpcUpstreams(
|
|||||||
// some messages are very large. many of them in megabytes, some even in gigabytes (ex. ETH Traces)
|
// some messages are very large. many of them in megabytes, some even in gigabytes (ex. ETH Traces)
|
||||||
.maxInboundMessageSize(Defaults.maxMessageSize)
|
.maxInboundMessageSize(Defaults.maxMessageSize)
|
||||||
.enableRetry()
|
.enableRetry()
|
||||||
.executor(executor)
|
.executor(grpcExecutor)
|
||||||
.maxRetryAttempts(3)
|
.maxRetryAttempts(3)
|
||||||
if (auth != null && StringUtils.isNotEmpty(auth.ca)) {
|
if (auth != null && StringUtils.isNotEmpty(auth.ca)) {
|
||||||
chanelBuilder
|
chanelBuilder
|
||||||
@@ -91,9 +95,9 @@ class GrpcUpstreams(
|
|||||||
|
|
||||||
val statusSubscription = AtomicReference<Disposable>()
|
val statusSubscription = AtomicReference<Disposable>()
|
||||||
|
|
||||||
val updates = Flux.interval(Duration.ZERO, Duration.ofSeconds(20))
|
return Flux.interval(Duration.ZERO, Duration.ofSeconds(20))
|
||||||
.flatMap {
|
.flatMap {
|
||||||
client.describe(BlockchainOuterClass.DescribeRequest.newBuilder().build())
|
client.describe(DescribeRequest.newBuilder().build())
|
||||||
}.onErrorContinue { t, _ ->
|
}.onErrorContinue { t, _ ->
|
||||||
if (ExceptionUtils.indexOfType(t, IOException::class.java) >= 0) {
|
if (ExceptionUtils.indexOfType(t, IOException::class.java) >= 0) {
|
||||||
log.warn("gRPC upstream $host:$port is unavailable. (${t.javaClass}: ${t.message})")
|
log.warn("gRPC upstream $host:$port is unavailable. (${t.javaClass}: ${t.message})")
|
||||||
@@ -106,7 +110,10 @@ class GrpcUpstreams(
|
|||||||
}.flatMap { value ->
|
}.flatMap { value ->
|
||||||
processDescription(value)
|
processDescription(value)
|
||||||
}.doOnNext {
|
}.doOnNext {
|
||||||
val subscription = client.subscribeStatus(BlockchainOuterClass.StatusRequest.newBuilder().build())
|
val subscription = client.subscribeStatus(
|
||||||
|
StatusRequest.newBuilder()
|
||||||
|
.addChains(Common.ChainRef.forNumber(it.chain.id)).build()
|
||||||
|
).subscribeOn(chainStatusScheduler)
|
||||||
.subscribe { value ->
|
.subscribe { value ->
|
||||||
val chain = Chain.byId(value.chain.number)
|
val chain = Chain.byId(value.chain.number)
|
||||||
if (chain != Chain.UNSPECIFIED) {
|
if (chain != Chain.UNSPECIFIED) {
|
||||||
@@ -120,11 +127,9 @@ class GrpcUpstreams(
|
|||||||
}.doOnError { t ->
|
}.doOnError { t ->
|
||||||
log.error("Failed to process update from gRPC upstream $id", t)
|
log.error("Failed to process update from gRPC upstream $id", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return updates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processDescription(value: BlockchainOuterClass.DescribeResponse): Flux<UpstreamChangeEvent> {
|
private fun processDescription(value: DescribeResponse): Flux<UpstreamChangeEvent> {
|
||||||
log.info("Start processing grpc upstream description for $id with chains ${value.chainsList.map { it.chain.name }}")
|
log.info("Start processing grpc upstream description for $id with chains ${value.chainsList.map { it.chain.name }}")
|
||||||
val current = value.chainsList.filter {
|
val current = value.chainsList.filter {
|
||||||
Chain.byId(it.chain.number) != Chain.UNSPECIFIED
|
Chain.byId(it.chain.number) != Chain.UNSPECIFIED
|
||||||
@@ -150,11 +155,13 @@ class GrpcUpstreams(
|
|||||||
}.map {
|
}.map {
|
||||||
UpstreamChangeEvent(it.key, known.remove(it.key)!!, UpstreamChangeEvent.ChangeType.REMOVED)
|
UpstreamChangeEvent(it.key, known.remove(it.key)!!, UpstreamChangeEvent.ChangeType.REMOVED)
|
||||||
}
|
}
|
||||||
log.info("Finished processing of grpc upstream description for $id with content delta added [${added.map { it.chain }}] and removed [${removed.map { it.chain }}]")
|
if (removed.isNotEmpty() || added.isNotEmpty()) {
|
||||||
|
log.info("Finished processing of grpc upstream description for $id with content delta added ${added.map { it.chain }} and removed ${removed.map { it.chain }}")
|
||||||
|
}
|
||||||
return Flux.fromIterable(removed + added)
|
return Flux.fromIterable(removed + added)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun withTls(auth: AuthConfig.ClientTlsAuth): SslContext {
|
private fun withTls(auth: AuthConfig.ClientTlsAuth): SslContext {
|
||||||
val sslContext = SslContextBuilder.forClient()
|
val sslContext = SslContextBuilder.forClient()
|
||||||
.clientAuth(ClientAuth.REQUIRE)
|
.clientAuth(ClientAuth.REQUIRE)
|
||||||
sslContext.trustManager(fileResolver.resolve(auth.ca!!).inputStream())
|
sslContext.trustManager(fileResolver.resolve(auth.ca!!).inputStream())
|
||||||
@@ -176,13 +183,31 @@ class GrpcUpstreams(
|
|||||||
return sslContext.build()
|
return sslContext.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreate(chain: Chain): UpstreamChangeEvent {
|
private val creators: Map<BlockchainType, (chain: Chain, client: JsonRpcGrpcClient) -> DefaultUpstream> = mapOf(
|
||||||
|
BlockchainType.EVM_POW to { chain, rpcClient ->
|
||||||
|
EthereumGrpcUpstream(id, hash, role, chain, client, rpcClient, labels)
|
||||||
|
},
|
||||||
|
BlockchainType.EVM_POS to { chain, rpcClient ->
|
||||||
|
EthereumPosGrpcUpstream(id, hash, role, chain, client, rpcClient, nodeRating, labels)
|
||||||
|
},
|
||||||
|
BlockchainType.BITCOIN to { chain, rpcClient ->
|
||||||
|
BitcoinGrpcUpstream(id, role, chain, client, rpcClient, labels)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getOrCreate(chain: Chain): UpstreamChangeEvent {
|
||||||
|
val metrics = makeMetrics(chain)
|
||||||
|
val creator = creators.getValue(BlockchainType.from(chain))
|
||||||
|
return getOrCreate(chain, metrics, creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeMetrics(chain: Chain): RpcMetrics {
|
||||||
val metricsTags = listOf(
|
val metricsTags = listOf(
|
||||||
Tag.of("upstream", id),
|
Tag.of("upstream", id),
|
||||||
Tag.of("chain", chain.chainCode)
|
Tag.of("chain", chain.chainCode)
|
||||||
)
|
)
|
||||||
|
|
||||||
val metrics = RpcMetrics(
|
return RpcMetrics(
|
||||||
Timer.builder("upstream.grpc.conn")
|
Timer.builder("upstream.grpc.conn")
|
||||||
.description("Request time through a Dshackle/gRPC connection")
|
.description("Request time through a Dshackle/gRPC connection")
|
||||||
.tags(metricsTags)
|
.tags(metricsTags)
|
||||||
@@ -193,68 +218,24 @@ class GrpcUpstreams(
|
|||||||
.tags(metricsTags)
|
.tags(metricsTags)
|
||||||
.register(Metrics.globalRegistry)
|
.register(Metrics.globalRegistry)
|
||||||
)
|
)
|
||||||
|
|
||||||
val blockchainType = BlockchainType.from(chain)
|
|
||||||
if (blockchainType == BlockchainType.EVM_POW) {
|
|
||||||
return getOrCreateEthereum(chain, metrics)
|
|
||||||
} else if (blockchainType == BlockchainType.BITCOIN) {
|
|
||||||
return getOrCreateBitcoin(chain, metrics)
|
|
||||||
} else if (blockchainType == BlockchainType.EVM_POS) {
|
|
||||||
return getOrCreateEthereumPos(chain, metrics)
|
|
||||||
} else {
|
|
||||||
throw IllegalArgumentException("Unsupported blockchain: $chain")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreateEthereum(chain: Chain, metrics: RpcMetrics): UpstreamChangeEvent {
|
private fun getOrCreate(
|
||||||
|
chain: Chain,
|
||||||
|
metrics: RpcMetrics,
|
||||||
|
creator: (chain: Chain, client: JsonRpcGrpcClient) -> DefaultUpstream
|
||||||
|
): UpstreamChangeEvent {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
val current = known[chain]
|
val current = known[chain]
|
||||||
return if (current == null) {
|
return if (current == null) {
|
||||||
val rpcClient = JsonRpcGrpcClient(client!!, chain, metrics)
|
val rpcClient = JsonRpcGrpcClient(client, chain, metrics)
|
||||||
val created = EthereumGrpcUpstream(id, hash, role, chain, client!!, rpcClient, labels)
|
val created = creator(chain, rpcClient)
|
||||||
created.timeout = this.timeout
|
|
||||||
known[chain] = created
|
known[chain] = created
|
||||||
created.start()
|
if (created is Lifecycle) created.start()
|
||||||
UpstreamChangeEvent(chain, created, UpstreamChangeEvent.ChangeType.ADDED)
|
UpstreamChangeEvent(chain, created, UpstreamChangeEvent.ChangeType.ADDED)
|
||||||
} else {
|
} else {
|
||||||
UpstreamChangeEvent(chain, current, UpstreamChangeEvent.ChangeType.REVALIDATED)
|
UpstreamChangeEvent(chain, current, UpstreamChangeEvent.ChangeType.REVALIDATED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreateEthereumPos(chain: Chain, metrics: RpcMetrics): UpstreamChangeEvent {
|
|
||||||
lock.withLock {
|
|
||||||
val current = known[chain]
|
|
||||||
return if (current == null) {
|
|
||||||
val rpcClient = JsonRpcGrpcClient(client!!, chain, metrics)
|
|
||||||
val created = EthereumPosGrpcUpstream(id, hash, role, chain, client!!, rpcClient, nodeRating, labels)
|
|
||||||
created.timeout = this.timeout
|
|
||||||
known[chain] = created
|
|
||||||
created.start()
|
|
||||||
UpstreamChangeEvent(chain, created, UpstreamChangeEvent.ChangeType.ADDED)
|
|
||||||
} else {
|
|
||||||
UpstreamChangeEvent(chain, current, UpstreamChangeEvent.ChangeType.REVALIDATED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getOrCreateBitcoin(chain: Chain, metrics: RpcMetrics): UpstreamChangeEvent {
|
|
||||||
lock.withLock {
|
|
||||||
val current = known[chain]
|
|
||||||
return if (current == null) {
|
|
||||||
val rpcClient = JsonRpcGrpcClient(client!!, chain, metrics)
|
|
||||||
val created = BitcoinGrpcUpstream(id, role, chain, client!!, rpcClient, labels)
|
|
||||||
created.timeout = this.timeout
|
|
||||||
known[chain] = created
|
|
||||||
created.start()
|
|
||||||
UpstreamChangeEvent(chain, created, UpstreamChangeEvent.ChangeType.ADDED)
|
|
||||||
} else {
|
|
||||||
UpstreamChangeEvent(chain, current, UpstreamChangeEvent.ChangeType.REVALIDATED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(chain: Chain): DefaultUpstream {
|
|
||||||
return known[chain]!!
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.time.Duration
|
|||||||
|
|
||||||
class SubscribeStatusSpec extends Specification {
|
class SubscribeStatusSpec extends Specification {
|
||||||
|
|
||||||
def "gives UNAVAIL if non-configured chain is requested"() {
|
def "returns requested statuses"() {
|
||||||
setup:
|
setup:
|
||||||
def ethereumUp = Mock(Upstream) {
|
def ethereumUp = Mock(Upstream) {
|
||||||
_ * getStatus() >> UpstreamAvailability.OK
|
_ * getStatus() >> UpstreamAvailability.OK
|
||||||
@@ -28,17 +28,15 @@ class SubscribeStatusSpec extends Specification {
|
|||||||
def ups = Mock(MultistreamHolder) {
|
def ups = Mock(MultistreamHolder) {
|
||||||
_ * it.getAvailable() >> [Chain.ETHEREUM]
|
_ * it.getAvailable() >> [Chain.ETHEREUM]
|
||||||
1 * it.getUpstream(Chain.ETHEREUM) >> ethereumUpAll
|
1 * it.getUpstream(Chain.ETHEREUM) >> ethereumUpAll
|
||||||
1 * it.getUpstream(Chain.BITCOIN) >> null
|
|
||||||
}
|
}
|
||||||
def ctrl = new SubscribeStatus(ups)
|
def ctrl = new SubscribeStatus(ups)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def req = BlockchainOuterClass.StatusRequest.newBuilder()
|
def req = BlockchainOuterClass.StatusRequest.newBuilder()
|
||||||
.addChains(Common.ChainRef.CHAIN_ETHEREUM)
|
.addChains(Common.ChainRef.CHAIN_ETHEREUM)
|
||||||
.addChains(Common.ChainRef.CHAIN_BITCOIN)
|
|
||||||
.build()
|
.build()
|
||||||
def act = ctrl.subscribeStatus(Mono.just(req))
|
def act = ctrl.subscribeStatus(Mono.just(req))
|
||||||
.take(2)
|
.take(1)
|
||||||
// sort just for testing
|
// sort just for testing
|
||||||
.sort(new Comparator<BlockchainOuterClass.ChainStatus>() {
|
.sort(new Comparator<BlockchainOuterClass.ChainStatus>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -48,9 +46,6 @@ class SubscribeStatusSpec extends Specification {
|
|||||||
})
|
})
|
||||||
then:
|
then:
|
||||||
StepVerifier.create(act)
|
StepVerifier.create(act)
|
||||||
.expectNextMatches {
|
|
||||||
it.chainValue == Chain.BITCOIN.id && it.availability == BlockchainOuterClass.AvailabilityEnum.AVAIL_UNAVAILABLE
|
|
||||||
}
|
|
||||||
.expectNextMatches {
|
.expectNextMatches {
|
||||||
it.chainValue == Chain.ETHEREUM.id && it.availability == BlockchainOuterClass.AvailabilityEnum.AVAIL_OK
|
it.chainValue == Chain.ETHEREUM.id && it.availability == BlockchainOuterClass.AvailabilityEnum.AVAIL_OK
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user