Skip to content

Commit

Permalink
Autodetect methods (#562)
Browse files Browse the repository at this point in the history
* Autodetect methods

* add polkadot magic method detection

* fix lint

* change config.methods writing

* fix log

* change Flux to Mono.zip

* change block to subscribe

* split chain method detectors

* eth method detector use rpc_modules

* rm UpstreamRpcModulesDetector.kt

* add tests

* getAllMethods for eth

* prefer enable/disable methods from config

* update tests

* whiter list & full is not available error check

* prefer config method group

* mv notAvailableRegexps to class variable
  • Loading branch information
tonatoz authored Sep 11, 2024
1 parent 5fd26ac commit 5fe044b
Show file tree
Hide file tree
Showing 13 changed files with 468 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ open class GenericUpstreamCreator(
connectorFactory,
cs::validator,
cs::upstreamSettingsDetector,
cs::upstreamRpcModulesDetector,
cs::upstreamRpcMethodsDetector,
buildMethodsFun,
cs::lowerBoundService,
cs::finalizationDetectorBuilder,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.emeraldpay.dshackle.upstream

import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.upstream.rpcclient.CallParams
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono

typealias UpstreamRpcMethodsDetectorBuilder = (Upstream, UpstreamsConfig.Upstream<*>?) -> UpstreamRpcMethodsDetector?

abstract class UpstreamRpcMethodsDetector(
private val upstream: Upstream,
private val config: UpstreamsConfig.Upstream<*>? = null,
) {
protected val log: Logger = LoggerFactory.getLogger(this::class.java)

private val notAvailableRegexps =
listOf(
"method ([A-Za-z0-9_]+) does not exist/is not available",
"([A-Za-z0-9_]+) found but the containing module is disabled",
"Method not found",
"The method ([A-Za-z0-9_]+) is not available",
).map { s -> s.toRegex() }

open fun detectRpcMethods(): Mono<Map<String, Boolean>> = detectByMagicMethod().switchIfEmpty(detectByMethod())

protected fun detectByMethod(): Mono<Map<String, Boolean>> =
Mono.zip(
rpcMethods().map {
Mono
.just(it)
.flatMap { (method, param) ->
upstream
.getIngressReader()
.read(ChainRequest(method, param))
.flatMap(ChainResponse::requireResult)
.map { method to true }
.onErrorResume { err ->
val notAvailableError =
notAvailableRegexps.any { s -> s.containsMatchIn(err.message ?: "") }
if (notAvailableError) {
Mono.just(method to false)
} else {
Mono.empty()
}
}
}
},
) {
it
.map { p -> p as Pair<String, Boolean> }
.associate { (method, enabled) -> method to enabled }
}

protected abstract fun detectByMagicMethod(): Mono<Map<String, Boolean>>

protected abstract fun rpcMethods(): Set<Pair<String, CallParams>>
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -809,4 +809,12 @@ class DefaultEthereumMethods(
override fun getSupportedMethods(): Set<String> {
return allowedMethods.plus(hardcodedMethods).toSortedSet()
}

fun getAllMethods(): Set<String> =
getSupportedMethods()
.plus(getGroupMethods("filter"))
.plus(getGroupMethods("trace"))
.plus(getGroupMethods("debug"))
.plus(getChainSpecificMethods(chain))
.toSet()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.emeraldpay.dshackle.upstream.ethereum

import com.fasterxml.jackson.core.type.TypeReference
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.upstream.ChainRequest
import io.emeraldpay.dshackle.upstream.ChainResponse
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.UpstreamRpcMethodsDetector
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.upstream.rpcclient.CallParams
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
import reactor.core.publisher.Mono

class BasicEthUpstreamRpcMethodsDetector(
private val upstream: Upstream,
private val config: UpstreamsConfig.Upstream<*>,
) : UpstreamRpcMethodsDetector(upstream) {
override fun detectByMagicMethod(): Mono<Map<String, Boolean>> =
upstream
.getIngressReader()
.read(ChainRequest("rpc_modules", ListParams()))
.flatMap(ChainResponse::requireResult)
.map(::parseRpcModules)
// force check all methods from rpcMethods
.zipWith(detectByMethod()) { a, b ->
a.plus(b)
}.onErrorResume {
log.warn("Can't detect rpc_modules of upstream ${upstream.getId()}, reason - {}", it.message)
Mono.empty()
}

override fun rpcMethods(): Set<Pair<String, CallParams>> =
setOf(
"eth_getBlockReceipts" to ListParams("latest"),
)

private fun parseRpcModules(data: ByteArray): Map<String, Boolean> {
val modules = Global.objectMapper.readValue(data, object : TypeReference<HashMap<String, String>>() {})
return DefaultEthereumMethods(upstream.getChain())
.getAllMethods()
.associateWith { method ->
if (config.methodGroups?.enabled?.any { group -> method.startsWith(group) } == true) {
return@associateWith true
}
if (config.methodGroups?.disabled?.any { group -> method.startsWith(group) } == true) {
return@associateWith false
}
modules.any { (module, _) -> method.startsWith(module) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.foundation.ChainOptions.Options
import io.emeraldpay.dshackle.reader.ChainReader
import io.emeraldpay.dshackle.upstream.BasicEthUpstreamRpcModulesDetector
import io.emeraldpay.dshackle.upstream.CachingReader
import io.emeraldpay.dshackle.upstream.ChainRequest
import io.emeraldpay.dshackle.upstream.EgressSubscription
Expand All @@ -19,7 +19,7 @@ import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.SingleValidator
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
import io.emeraldpay.dshackle.upstream.UpstreamRpcModulesDetector
import io.emeraldpay.dshackle.upstream.UpstreamRpcMethodsDetector
import io.emeraldpay.dshackle.upstream.UpstreamSettingsDetector
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
import io.emeraldpay.dshackle.upstream.calls.CallMethods
Expand Down Expand Up @@ -180,9 +180,10 @@ object EthereumChainSpecific : AbstractPollChainSpecific() {
return ChainIdValidator(upstream, chain, reader)
}

override fun upstreamRpcModulesDetector(upstream: Upstream): UpstreamRpcModulesDetector {
return BasicEthUpstreamRpcModulesDetector(upstream)
}
override fun upstreamRpcMethodsDetector(
upstream: Upstream,
config: UpstreamsConfig.Upstream<*>?,
): UpstreamRpcMethodsDetector? = config?.let { BasicEthUpstreamRpcMethodsDetector(upstream, it) }

override fun lowerBoundService(chain: Chain, upstream: Upstream): LowerBoundService {
return EthereumLowerBoundService(chain, upstream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.emeraldpay.dshackle.upstream.generic
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.config.hot.CompatibleVersionsRules
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.foundation.ChainOptions.Options
Expand All @@ -20,7 +21,7 @@ import io.emeraldpay.dshackle.upstream.NoopCachingReader
import io.emeraldpay.dshackle.upstream.SingleValidator
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
import io.emeraldpay.dshackle.upstream.UpstreamRpcModulesDetector
import io.emeraldpay.dshackle.upstream.UpstreamRpcMethodsDetector
import io.emeraldpay.dshackle.upstream.UpstreamSettingsDetector
import io.emeraldpay.dshackle.upstream.UpstreamValidator
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
Expand Down Expand Up @@ -68,9 +69,10 @@ abstract class AbstractChainSpecific : ChainSpecific {
return null
}

override fun upstreamRpcModulesDetector(upstream: Upstream): UpstreamRpcModulesDetector? {
return null
}
override fun upstreamRpcMethodsDetector(
upstream: Upstream,
config: UpstreamsConfig.Upstream<*>?,
): UpstreamRpcMethodsDetector? = null

override fun makeIngressSubscription(ws: WsSubscriptions): IngressSubscription {
return NoIngressSubscription()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.emeraldpay.dshackle.BlockchainType.UNKNOWN
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.config.hot.CompatibleVersionsRules
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.foundation.ChainOptions
Expand All @@ -25,7 +26,7 @@ import io.emeraldpay.dshackle.upstream.LogsOracle
import io.emeraldpay.dshackle.upstream.Multistream
import io.emeraldpay.dshackle.upstream.SingleValidator
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.UpstreamRpcModulesDetector
import io.emeraldpay.dshackle.upstream.UpstreamRpcMethodsDetector
import io.emeraldpay.dshackle.upstream.UpstreamSettingsDetector
import io.emeraldpay.dshackle.upstream.UpstreamValidator
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
Expand Down Expand Up @@ -86,7 +87,10 @@ interface ChainSpecific {

fun chainSettingsValidator(chain: Chain, upstream: Upstream, reader: ChainReader?): SingleValidator<ValidateUpstreamSettingsResult>?

fun upstreamRpcModulesDetector(upstream: Upstream): UpstreamRpcModulesDetector?
fun upstreamRpcMethodsDetector(
upstream: Upstream,
config: UpstreamsConfig.Upstream<*>?,
): UpstreamRpcMethodsDetector?

fun makeIngressSubscription(ws: WsSubscriptions): IngressSubscription

Expand Down
Loading

0 comments on commit 5fe044b

Please sign in to comment.