more granular config for upstream validation
This commit is contained in:
@@ -27,27 +27,37 @@ open class UpstreamsConfig {
|
||||
|
||||
open class Options {
|
||||
var disableValidation: Boolean? = null
|
||||
var validationInterval: Int = 30
|
||||
set(value) {
|
||||
require(value > 0) {
|
||||
"validation-interval must be a positive number: $value"
|
||||
}
|
||||
field = value
|
||||
}
|
||||
var timeout = Defaults.timeout
|
||||
var providesBalance: Boolean? = null
|
||||
|
||||
var validatePeers: Boolean = true
|
||||
var minPeers: Int? = 1
|
||||
set(minPeers) {
|
||||
if (minPeers != null && minPeers < 0) {
|
||||
throw IllegalArgumentException("minPeers must be positive number")
|
||||
set(value) {
|
||||
require(value != null && value >= 0) {
|
||||
"min-peers must be a positive number: $value"
|
||||
}
|
||||
field = minPeers
|
||||
field = value
|
||||
}
|
||||
|
||||
fun merge(additional: Options?): Options {
|
||||
if (additional == null) {
|
||||
var validateSyncing: Boolean = true
|
||||
fun merge(overwrites: Options?): Options {
|
||||
if (overwrites == null) {
|
||||
return this
|
||||
}
|
||||
val copy = Options()
|
||||
copy.minPeers = if (this.minPeers != null) this.minPeers else additional.minPeers
|
||||
copy.validatePeers = this.validatePeers && overwrites.validatePeers
|
||||
copy.minPeers = if (this.minPeers != null) this.minPeers else overwrites.minPeers
|
||||
copy.disableValidation =
|
||||
if (this.disableValidation != null) this.disableValidation else additional.disableValidation
|
||||
if (this.disableValidation != null) this.disableValidation else overwrites.disableValidation
|
||||
copy.validationInterval = overwrites.validationInterval
|
||||
copy.providesBalance =
|
||||
if (this.providesBalance != null) this.providesBalance else additional.providesBalance
|
||||
if (this.providesBalance != null) this.providesBalance else overwrites.providesBalance
|
||||
copy.validateSyncing = this.validateSyncing && overwrites.validateSyncing
|
||||
return copy
|
||||
}
|
||||
|
||||
|
||||
@@ -347,6 +347,12 @@ class UpstreamsConfigReader(
|
||||
|
||||
internal fun readOptions(values: MappingNode): UpstreamsConfig.Options {
|
||||
val options = UpstreamsConfig.Options()
|
||||
getValueAsBool(values, "validate-peers")?.let {
|
||||
options.validatePeers = it
|
||||
}
|
||||
getValueAsBool(values, "validate-syncing")?.let {
|
||||
options.validateSyncing = it
|
||||
}
|
||||
getValueAsInt(values, "min-peers")?.let {
|
||||
options.minPeers = it
|
||||
}
|
||||
@@ -356,6 +362,9 @@ class UpstreamsConfigReader(
|
||||
getValueAsBool(values, "disable-validation")?.let {
|
||||
options.disableValidation = it
|
||||
}
|
||||
getValueAsInt(values, "validation-interval")?.let {
|
||||
options.validationInterval = it
|
||||
}
|
||||
getValueAsBool(values, "balance")?.let {
|
||||
options.providesBalance = it
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ open class ConfiguredUpstreams(
|
||||
log.error("Chain is unknown: ${up.chain}")
|
||||
return@forEach
|
||||
}
|
||||
val options = (up.options ?: UpstreamsConfig.Options())
|
||||
.merge(defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults())
|
||||
val options = (defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults())
|
||||
.merge(up.options ?: UpstreamsConfig.Options())
|
||||
val upstream = when (BlockchainType.from(chain)) {
|
||||
BlockchainType.EVM_POW -> {
|
||||
buildEthereumUpstream(
|
||||
@@ -155,9 +155,6 @@ open class ConfiguredUpstreams(
|
||||
}
|
||||
}
|
||||
}
|
||||
defaultOptions.keys.forEach { chain ->
|
||||
defaultOptions[chain] = defaultOptions[chain]!!.merge(UpstreamsConfig.Options.getDefaults())
|
||||
}
|
||||
return defaultOptions
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ enum class UpstreamAvailability(val grpcId: Int) {
|
||||
*/
|
||||
UNAVAILABLE(5);
|
||||
|
||||
fun isBetterTo(other: UpstreamAvailability): Boolean {
|
||||
return other.grpcId > grpcId
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromGrpc(id: Int?): UpstreamAvailability {
|
||||
if (id == null) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.scheduling.concurrent.CustomizableThreadFactory
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import reactor.util.function.Tuple2
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeoutException
|
||||
@@ -47,6 +48,23 @@ open class EthereumUpstreamValidator(
|
||||
private val objectMapper: ObjectMapper = Global.objectMapper
|
||||
|
||||
open fun validate(): Mono<UpstreamAvailability> {
|
||||
return Mono.zip(
|
||||
validateSyncing(),
|
||||
validatePeers()
|
||||
)
|
||||
.map(::resolve)
|
||||
.defaultIfEmpty(UpstreamAvailability.UNAVAILABLE)
|
||||
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
||||
}
|
||||
|
||||
fun resolve(results: Tuple2<UpstreamAvailability, UpstreamAvailability>): UpstreamAvailability {
|
||||
return if (results.t1.isBetterTo(results.t2)) results.t2 else results.t1
|
||||
}
|
||||
|
||||
fun validateSyncing(): Mono<UpstreamAvailability> {
|
||||
if (!options.validateSyncing) {
|
||||
return Mono.just(UpstreamAvailability.OK)
|
||||
}
|
||||
return upstream
|
||||
.getIngressReader()
|
||||
.read(JsonRpcRequest("eth_syncing", listOf()))
|
||||
@@ -57,38 +75,42 @@ open class EthereumUpstreamValidator(
|
||||
Mono.fromCallable { log.warn("No response for eth_syncing from ${upstream.getId()}") }
|
||||
.then(Mono.error(TimeoutException("Validation timeout for Syncing")))
|
||||
)
|
||||
.flatMap { value ->
|
||||
.map { value ->
|
||||
if (value.isSyncing) {
|
||||
Mono.just(UpstreamAvailability.SYNCING)
|
||||
UpstreamAvailability.SYNCING
|
||||
} else {
|
||||
upstream
|
||||
.getIngressReader()
|
||||
.read(JsonRpcRequest("net_peerCount", listOf()))
|
||||
.flatMap(JsonRpcResponse::requireStringResult)
|
||||
.map(Integer::decode)
|
||||
.timeout(
|
||||
Defaults.timeoutInternal,
|
||||
Mono.fromCallable { log.warn("No response for net_peerCount from ${upstream.getId()}") }
|
||||
.then(Mono.error(TimeoutException("Validation timeout for Peers")))
|
||||
)
|
||||
.map { count ->
|
||||
val minPeers = options.minPeers ?: 1
|
||||
if (count < minPeers) {
|
||||
UpstreamAvailability.IMMATURE
|
||||
} else {
|
||||
UpstreamAvailability.OK
|
||||
}
|
||||
}
|
||||
UpstreamAvailability.OK
|
||||
}
|
||||
}
|
||||
.doOnError {
|
||||
log.warn("Error validating ${upstream.getId()}", it)
|
||||
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
||||
}
|
||||
fun validatePeers(): Mono<UpstreamAvailability> {
|
||||
if (!options.validatePeers || options.minPeers == 0) {
|
||||
return Mono.just(UpstreamAvailability.OK)
|
||||
}
|
||||
return upstream
|
||||
.getIngressReader()
|
||||
.read(JsonRpcRequest("net_peerCount", listOf()))
|
||||
.flatMap(JsonRpcResponse::requireStringResult)
|
||||
.map(Integer::decode)
|
||||
.timeout(
|
||||
Defaults.timeoutInternal,
|
||||
Mono.fromCallable { log.warn("No response for net_peerCount from ${upstream.getId()}") }
|
||||
.then(Mono.error(TimeoutException("Validation timeout for Peers")))
|
||||
)
|
||||
.map { count ->
|
||||
val minPeers = options.minPeers ?: 1
|
||||
if (count < minPeers) {
|
||||
UpstreamAvailability.IMMATURE
|
||||
} else {
|
||||
UpstreamAvailability.OK
|
||||
}
|
||||
}
|
||||
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
||||
}
|
||||
|
||||
fun start(): Flux<UpstreamAvailability> {
|
||||
return Flux.interval(Duration.ofSeconds(15))
|
||||
return Flux.interval(Duration.ofSeconds(options.validationInterval.toLong()))
|
||||
.subscribeOn(scheduler)
|
||||
.flatMap {
|
||||
validate()
|
||||
|
||||
Reference in New Issue
Block a user