From 753b7d850358729d10cdc24d4cf6dd7d7f03fca0 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 3 Jun 2024 12:51:50 +0300 Subject: [PATCH] Check gas price multiply conditions (#496) * multiply conditions * update doc --- docs/reference-configuration.adoc | 3 +- .../org/drpc/chainsconfig/ChainsConfig.kt | 39 ++++++++------ .../drpc/chainsconfig/ChainsConfigReader.kt | 51 ++++++++++++++++--- foundation/src/main/resources/chains.yaml | 7 ++- .../ethereum/EthereumUpstreamValidator.kt | 6 +-- .../EthereumUpstreamValidatorSpec.groovy | 14 +++-- 6 files changed, 87 insertions(+), 33 deletions(-) diff --git a/docs/reference-configuration.adoc b/docs/reference-configuration.adoc index d5ddabf9c..0bd3e3952 100644 --- a/docs/reference-configuration.adoc +++ b/docs/reference-configuration.adoc @@ -781,7 +781,8 @@ If it's enabled configuration parameter for chain `call-limit-contract` is requi | `validate-gas-price` | boolean | `true` -| Enable/Disable the gas price validation. If it's enabled, the Dshackle will check the gas price of the upstream and will not use it if it's too high. +| Enable/Disable the gas price validation. If it's enabled, the Dshackle will check the gas price of the upstream and compare it with the gas price conditions in chain.yaml +Check conditions can contain multiple values presented as a list of pair operator and value. The operator can be `eq`, `ne`, `gt`, `ge`, `lt`, `le`. Value is a Long number. | `call-limit-size` | number diff --git a/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfig.kt b/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfig.kt index e3950c783..7ffa3c7b0 100644 --- a/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfig.kt +++ b/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfig.kt @@ -18,21 +18,30 @@ data class ChainsConfig(private val chains: List) : Iterable) { + private val conditions: List> = rawConditions.map { + val parts = it.split(" ") + if (parts.size != 2 || listOf("ne", "eq", "gt", "lt", "ge", "le").none { op -> op == parts[0] }) { + throw IllegalArgumentException("Invalid condition: $it") + } + Pair(parts[0], parts[1].toLong()) + } + fun check(value: Long): Boolean { - val (op, valueStr) = condition.split(" ") - return when (op) { - "ne" -> value != valueStr.toLong() - "eq" -> value == valueStr.toLong() - "gt" -> value > valueStr.toLong() - "lt" -> value < valueStr.toLong() - "ge" -> value >= valueStr.toLong() - "le" -> value <= valueStr.toLong() - else -> throw IllegalArgumentException("Unsupported condition: $condition") + return conditions.all { (op, limit) -> + when (op) { + "ne" -> value != limit + "eq" -> value == limit + "gt" -> value > limit + "lt" -> value < limit + "ge" -> value >= limit + "le" -> value <= limit + else -> false + } } } - fun rules() = condition + fun rules() = conditions.joinToString { (op, limit) -> "$op $limit" } } data class ChainConfig( @@ -49,7 +58,7 @@ data class ChainsConfig(private val chains: List) : Iterable) : Iterable) = defaultWithContract(null).copy( + gasPriceCondition = GasPriceCondition(gasPriceConditions), ) } } diff --git a/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt b/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt index d7999d6ae..f7aaa6782 100644 --- a/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt +++ b/foundation/src/main/kotlin/org/drpc/chainsconfig/ChainsConfigReader.kt @@ -35,14 +35,47 @@ class ChainsConfigReader( MappingNode( chain.tag, listOf( - NodeTuple(ScalarNode(Tag.STR, "settings", null, null, DumperOptions.ScalarStyle.LITERAL), chainSettings), NodeTuple( - ScalarNode(Tag.STR, "blockchain", null, null, DumperOptions.ScalarStyle.LITERAL), - ScalarNode(Tag.STR, blockchain, null, null, DumperOptions.ScalarStyle.LITERAL), + ScalarNode( + Tag.STR, + "settings", + null, + null, + DumperOptions.ScalarStyle.LITERAL, + ), + chainSettings, ), NodeTuple( - ScalarNode(Tag.STR, "type", null, null, DumperOptions.ScalarStyle.LITERAL), - ScalarNode(Tag.STR, type, null, null, DumperOptions.ScalarStyle.LITERAL), + ScalarNode( + Tag.STR, + "blockchain", + null, + null, + DumperOptions.ScalarStyle.LITERAL, + ), + ScalarNode( + Tag.STR, + blockchain, + null, + null, + DumperOptions.ScalarStyle.LITERAL, + ), + ), + NodeTuple( + ScalarNode( + Tag.STR, + "type", + null, + null, + DumperOptions.ScalarStyle.LITERAL, + ), + ScalarNode( + Tag.STR, + type, + null, + null, + DumperOptions.ScalarStyle.LITERAL, + ), ), ), chain.flowStyle, @@ -62,7 +95,8 @@ class ChainsConfigReader( private fun parseChain(blockchain: String, node: MappingNode): ChainsConfig.ChainConfig { val id = getValueAsString(node, "id") ?: throw IllegalArgumentException("undefined id for $blockchain") - val settings = getMapping(node, "settings") ?: throw IllegalArgumentException("undefined settings for $blockchain") + val settings = + getMapping(node, "settings") ?: throw IllegalArgumentException("undefined settings for $blockchain") val lags = getMapping(settings, "lags")?.let { lagConfig -> Pair( getValueAsInt(lagConfig, "syncing") @@ -86,7 +120,7 @@ class ChainsConfigReader( ?: throw IllegalArgumentException("undefined shortnames for $blockchain") val type = getValueAsString(node, "type") ?: throw IllegalArgumentException("undefined type for $blockchain") - val gasPriceCondition = getValueAsString(node, "gas-price-condition") + val gasPriceConditions = getListOfString(node, "gas-price-condition") ?: emptyList() return ChainsConfig.ChainConfig( expectedBlockTime = expectedBlockTime, syncingLagSize = lags.first, @@ -101,7 +135,7 @@ class ChainsConfigReader( id = id, blockchain = blockchain, type = type, - gasPriceCondition = gasPriceCondition?.let { ChainsConfig.GasPriceCondition(gasPriceCondition) }, + gasPriceCondition = ChainsConfig.GasPriceCondition(gasPriceConditions), ) } @@ -118,6 +152,7 @@ class ChainsConfigReader( val merged = mergeMappingNode(defChain.second, curChain.second) parseChain(defChain.first, merged!!) } + else -> ChainsConfig.ChainConfig.default() } } diff --git a/foundation/src/main/resources/chains.yaml b/foundation/src/main/resources/chains.yaml index 07ab16537..6d70d74c7 100644 --- a/foundation/src/main/resources/chains.yaml +++ b/foundation/src/main/resources/chains.yaml @@ -191,7 +191,9 @@ chain-settings: grpcId: 1006 chain-id: 0x38 short-names: [bsc, binance, bnb-smart-chain] - gas-price-condition: ne 3000000000 + gas-price-condition: + - ne 3000000000 + - ne 5000000000 - id: Testnet priority: 1 code: BSC_TESTNET @@ -610,7 +612,8 @@ chain-settings: short-names: [kava] chain-id: 0x8ae grpcId: 1025 - gas-price-condition: eq 1000000000 + gas-price-condition: + - eq 1000000000 - id: Testnet priority: 10 code: KAVA_TESTNET diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt index 587f6ed50..c46fad378 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt @@ -197,7 +197,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( } private fun validateGasPrice(): Mono { - if (!options.validateGasPrice || config.gasPriceCondition == null) { + if (!options.validateGasPrice) { return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID) } return upstream.getIngressReader() @@ -205,10 +205,10 @@ open class EthereumUpstreamValidator @JvmOverloads constructor( .flatMap(ChainResponse::requireStringResult) .map { result -> val actualGasPrice = result.substring(2).toLong(16) - if (!config.gasPriceCondition!!.check(actualGasPrice)) { + if (!config.gasPriceCondition.check(actualGasPrice)) { log.warn( "Node ${upstream.getId()} has gasPrice $actualGasPrice, " + - "but it is not equal to the required ${config.gasPriceCondition!!.rules()}", + "but it is not equal to the required ${config.gasPriceCondition.rules()}", ) ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR } else { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy index 249409d39..284c3b5dc 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidatorSpec.groovy @@ -351,7 +351,7 @@ class EthereumUpstreamValidatorSpec extends Specification { it.validateChain = false it.validateCallLimit = false }.buildOptions() - def conf = ChainConfig.defaultWithGasPriceCondition("ne 3000000000") + def conf = ChainConfig.defaultWithGasPriceCondition(["ne 3000000000", "ne 5000000000"]) def up = Mock(Upstream) { 3 * getIngressReader() >> Mock(Reader) { @@ -374,7 +374,7 @@ class EthereumUpstreamValidatorSpec extends Specification { it.validateChain = false it.validateCallLimit = false }.buildOptions() - def conf = ChainConfig.defaultWithGasPriceCondition("eq 1000000000") + def conf = ChainConfig.defaultWithGasPriceCondition(["eq 1000000000"]) def up = Mock(Upstream) { 3 * getIngressReader() >> Mock(Reader) { @@ -395,6 +395,7 @@ class EthereumUpstreamValidatorSpec extends Specification { setup: def options = ChainOptions.PartialOptions.getDefaults().tap { it.validateCallLimit = false + it.validateGasPrice = false }.buildOptions() def up = Mock(Upstream) { 4 * getIngressReader() >> Mock(Reader) { @@ -417,6 +418,7 @@ class EthereumUpstreamValidatorSpec extends Specification { setup: def options = ChainOptions.PartialOptions.getDefaults().tap { it.validateCallLimit = false + it.validateGasPrice = false }.buildOptions() def up = Mock(Upstream) { 4 * getIngressReader() >> Mock(Reader) { @@ -437,7 +439,9 @@ class EthereumUpstreamValidatorSpec extends Specification { def "Upstream is valid if all setting are valid"() { setup: - def options = ChainOptions.PartialOptions.getDefaults().buildOptions() + def options = ChainOptions.PartialOptions.getDefaults().tap{ + it.validateGasPrice = false + }.buildOptions() def up = Mock(Upstream) { 5 * getIngressReader() >> Mock(Reader) { 1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse('"0x1"'.getBytes(), null)) @@ -461,7 +465,9 @@ class EthereumUpstreamValidatorSpec extends Specification { def "Upstream is not valid if there are errors"() { setup: - def options = ChainOptions.PartialOptions.getDefaults().buildOptions() + def options = ChainOptions.PartialOptions.getDefaults().tap { + it.validateGasPrice = false + }.buildOptions() def up = Mock(Upstream) { 5 * getIngressReader() >> Mock(Reader) { 1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long")))