more granular config for upstream validation

This commit is contained in:
a10zn8
2023-02-15 19:13:10 +04:00
parent 671903f65c
commit 1683be8dac
9 changed files with 419 additions and 39 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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()