182 lines
6.1 KiB
Kotlin
182 lines
6.1 KiB
Kotlin
/**
|
|
* Copyright (c) 2020 EmeraldPay, Inc
|
|
* Copyright (c) 2019 ETCDEV GmbH
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package io.emeraldpay.dshackle.upstream
|
|
|
|
import io.emeraldpay.api.proto.BlockchainOuterClass
|
|
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
|
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
|
import org.slf4j.LoggerFactory
|
|
import reactor.core.publisher.Flux
|
|
import reactor.core.publisher.Sinks
|
|
import java.util.concurrent.atomic.AtomicReference
|
|
|
|
abstract class DefaultUpstream(
|
|
private val id: String,
|
|
private val hash: Byte,
|
|
defaultLag: Long?,
|
|
defaultAvail: UpstreamAvailability,
|
|
private val options: UpstreamsConfig.Options,
|
|
private val role: UpstreamsConfig.UpstreamRole,
|
|
private val targets: CallMethods?,
|
|
node: QuorumForLabels.QuorumItem?,
|
|
private val chainConfig: ChainsConfig.ChainConfig
|
|
) : Upstream {
|
|
|
|
constructor(
|
|
id: String,
|
|
hash: Byte,
|
|
options: UpstreamsConfig.Options,
|
|
role: UpstreamsConfig.UpstreamRole,
|
|
targets: CallMethods?,
|
|
node: QuorumForLabels.QuorumItem?,
|
|
chainConfig: ChainsConfig.ChainConfig
|
|
) :
|
|
this(id, hash, null, UpstreamAvailability.UNAVAILABLE, options, role, targets, node, chainConfig)
|
|
|
|
protected val log = LoggerFactory.getLogger(this::class.java)
|
|
|
|
private val status = AtomicReference(Status(defaultLag, defaultAvail, statusByLag(defaultLag, defaultAvail)))
|
|
private val statusStream = Sinks.many()
|
|
.multicast()
|
|
.directBestEffort<UpstreamChangeState>()
|
|
|
|
init {
|
|
if (id.length < 3 || !id.matches(Regex("[a-zA-Z][a-zA-Z0-9_-]+[a-zA-Z0-9]"))) {
|
|
throw IllegalArgumentException("Invalid upstream id: $id")
|
|
}
|
|
}
|
|
|
|
override fun isAvailable(): Boolean {
|
|
return getStatus() == UpstreamAvailability.OK || getStatus() == UpstreamAvailability.LAGGING
|
|
}
|
|
|
|
fun onStatus(value: BlockchainOuterClass.ChainStatus) {
|
|
this.onStatus(value, false)
|
|
}
|
|
|
|
fun onStatus(value: BlockchainOuterClass.ChainStatus, stateChanged: Boolean = false) {
|
|
val available = value.availability
|
|
setStatus(
|
|
if (available != null) UpstreamAvailability.fromGrpc(available.number) else UpstreamAvailability.UNAVAILABLE,
|
|
stateChanged
|
|
)
|
|
}
|
|
|
|
override fun getStatus(): UpstreamAvailability {
|
|
return status.get().status
|
|
}
|
|
|
|
open fun setStatus(avail: UpstreamAvailability) {
|
|
this.setStatus(avail, false)
|
|
}
|
|
|
|
open fun setStatus(avail: UpstreamAvailability, stateChanged: Boolean = false) {
|
|
status.updateAndGet { curr ->
|
|
Status(curr.lag, avail, statusByLag(curr.lag, avail))
|
|
}.also {
|
|
statusStream.emitNext(
|
|
UpstreamChangeState(it.status, stateChanged)
|
|
) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED }
|
|
log.trace("Status of upstream [$id] changed to [$it], requested change status to [$avail]")
|
|
}
|
|
}
|
|
|
|
private fun statusByLag(lag: Long?, proposed: UpstreamAvailability): UpstreamAvailability {
|
|
if (options.disableValidation) {
|
|
// if we specifically told that this upstream should be _always valid_ then skip
|
|
// the status calculation and trust the proposed value as is
|
|
return proposed
|
|
}
|
|
if (lag == null) {
|
|
return UpstreamAvailability.UNAVAILABLE
|
|
}
|
|
return if (proposed == UpstreamAvailability.OK) {
|
|
when {
|
|
lag > chainConfig.syncingLagSize -> UpstreamAvailability.SYNCING
|
|
lag > chainConfig.laggingLagSize -> UpstreamAvailability.LAGGING
|
|
else -> proposed
|
|
}
|
|
} else proposed
|
|
}
|
|
|
|
override fun observeStatus(): Flux<UpstreamAvailability> {
|
|
return statusStream.asFlux()
|
|
.distinctUntilChanged(
|
|
{ it },
|
|
{ prev, current ->
|
|
if (current.stateChanged) {
|
|
false
|
|
} else {
|
|
prev.status == current.status
|
|
}
|
|
}
|
|
)
|
|
.map { it.status }
|
|
}
|
|
|
|
override fun setLag(lag: Long) {
|
|
lag.coerceAtLeast(0).let { nLag ->
|
|
status.updateAndGet { curr ->
|
|
Status(nLag, curr.avail, statusByLag(nLag, curr.avail))
|
|
}.also {
|
|
statusStream.emitNext(
|
|
UpstreamChangeState(it.status, false)
|
|
) { _, res -> res == Sinks.EmitResult.FAIL_NON_SERIALIZED }
|
|
log.trace("Status of upstream [$id] changed to [$it], requested change lag to [$lag]")
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun getLag(): Long? {
|
|
return this.status.get().lag
|
|
}
|
|
|
|
override fun getOptions(): UpstreamsConfig.Options {
|
|
return options
|
|
}
|
|
|
|
override fun getRole(): UpstreamsConfig.UpstreamRole {
|
|
return role
|
|
}
|
|
|
|
override fun getMethods(): CallMethods {
|
|
return targets ?: throw IllegalStateException("Methods are not set")
|
|
}
|
|
|
|
override fun nodeId(): Byte = hash
|
|
|
|
private val quorumByLabel = node?.let { QuorumForLabels(it) }
|
|
?: QuorumForLabels(QuorumForLabels.QuorumItem.empty())
|
|
|
|
open fun getQuorumByLabel(): QuorumForLabels {
|
|
return quorumByLabel
|
|
}
|
|
|
|
override fun getId(): String {
|
|
return id
|
|
}
|
|
|
|
data class Status(val lag: Long?, val avail: UpstreamAvailability, val status: UpstreamAvailability)
|
|
|
|
private data class UpstreamChangeState(
|
|
val status: UpstreamAvailability,
|
|
val stateChanged: Boolean
|
|
)
|
|
}
|