Check gas price multiply conditions (#496)
* multiply conditions * update doc
This commit is contained in:
@@ -781,7 +781,8 @@ If it's enabled configuration parameter for chain `call-limit-contract` is requi
|
|||||||
| `validate-gas-price`
|
| `validate-gas-price`
|
||||||
| boolean
|
| boolean
|
||||||
| `true`
|
| `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`
|
| `call-limit-size`
|
||||||
| number
|
| number
|
||||||
|
|||||||
@@ -18,21 +18,30 @@ data class ChainsConfig(private val chains: List<ChainConfig>) : Iterable<Chains
|
|||||||
fun default(): ChainsConfig = ChainsConfig(emptyList())
|
fun default(): ChainsConfig = ChainsConfig(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
class GasPriceCondition(private val condition: String) {
|
class GasPriceCondition(rawConditions: List<String>) {
|
||||||
|
private val conditions: List<Pair<String, Long>> = 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 {
|
fun check(value: Long): Boolean {
|
||||||
val (op, valueStr) = condition.split(" ")
|
return conditions.all { (op, limit) ->
|
||||||
return when (op) {
|
when (op) {
|
||||||
"ne" -> value != valueStr.toLong()
|
"ne" -> value != limit
|
||||||
"eq" -> value == valueStr.toLong()
|
"eq" -> value == limit
|
||||||
"gt" -> value > valueStr.toLong()
|
"gt" -> value > limit
|
||||||
"lt" -> value < valueStr.toLong()
|
"lt" -> value < limit
|
||||||
"ge" -> value >= valueStr.toLong()
|
"ge" -> value >= limit
|
||||||
"le" -> value <= valueStr.toLong()
|
"le" -> value <= limit
|
||||||
else -> throw IllegalArgumentException("Unsupported condition: $condition")
|
else -> false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rules() = condition
|
fun rules() = conditions.joinToString { (op, limit) -> "$op $limit" }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ChainConfig(
|
data class ChainConfig(
|
||||||
@@ -49,7 +58,7 @@ data class ChainsConfig(private val chains: List<ChainConfig>) : Iterable<Chains
|
|||||||
val id: String,
|
val id: String,
|
||||||
val blockchain: String,
|
val blockchain: String,
|
||||||
val type: String,
|
val type: String,
|
||||||
val gasPriceCondition: GasPriceCondition? = null,
|
val gasPriceCondition: GasPriceCondition,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -70,12 +79,12 @@ data class ChainsConfig(private val chains: List<ChainConfig>) : Iterable<Chains
|
|||||||
"undefined",
|
"undefined",
|
||||||
"undefined",
|
"undefined",
|
||||||
"unknown",
|
"unknown",
|
||||||
null,
|
GasPriceCondition(emptyList()),
|
||||||
)
|
)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun defaultWithGasPriceCondition(gasPriceCondition: String) = defaultWithContract(null).copy(
|
fun defaultWithGasPriceCondition(gasPriceConditions: List<String>) = defaultWithContract(null).copy(
|
||||||
gasPriceCondition = GasPriceCondition(gasPriceCondition),
|
gasPriceCondition = GasPriceCondition(gasPriceConditions),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,14 +35,47 @@ class ChainsConfigReader(
|
|||||||
MappingNode(
|
MappingNode(
|
||||||
chain.tag,
|
chain.tag,
|
||||||
listOf(
|
listOf(
|
||||||
NodeTuple(ScalarNode(Tag.STR, "settings", null, null, DumperOptions.ScalarStyle.LITERAL), chainSettings),
|
|
||||||
NodeTuple(
|
NodeTuple(
|
||||||
ScalarNode(Tag.STR, "blockchain", null, null, DumperOptions.ScalarStyle.LITERAL),
|
ScalarNode(
|
||||||
ScalarNode(Tag.STR, blockchain, null, null, DumperOptions.ScalarStyle.LITERAL),
|
Tag.STR,
|
||||||
|
"settings",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
DumperOptions.ScalarStyle.LITERAL,
|
||||||
|
),
|
||||||
|
chainSettings,
|
||||||
),
|
),
|
||||||
NodeTuple(
|
NodeTuple(
|
||||||
ScalarNode(Tag.STR, "type", null, null, DumperOptions.ScalarStyle.LITERAL),
|
ScalarNode(
|
||||||
ScalarNode(Tag.STR, type, null, null, DumperOptions.ScalarStyle.LITERAL),
|
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,
|
chain.flowStyle,
|
||||||
@@ -62,7 +95,8 @@ class ChainsConfigReader(
|
|||||||
private fun parseChain(blockchain: String, node: MappingNode): ChainsConfig.ChainConfig {
|
private fun parseChain(blockchain: String, node: MappingNode): ChainsConfig.ChainConfig {
|
||||||
val id = getValueAsString(node, "id")
|
val id = getValueAsString(node, "id")
|
||||||
?: throw IllegalArgumentException("undefined id for $blockchain")
|
?: 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 ->
|
val lags = getMapping(settings, "lags")?.let { lagConfig ->
|
||||||
Pair(
|
Pair(
|
||||||
getValueAsInt(lagConfig, "syncing")
|
getValueAsInt(lagConfig, "syncing")
|
||||||
@@ -86,7 +120,7 @@ class ChainsConfigReader(
|
|||||||
?: throw IllegalArgumentException("undefined shortnames for $blockchain")
|
?: throw IllegalArgumentException("undefined shortnames for $blockchain")
|
||||||
val type = getValueAsString(node, "type")
|
val type = getValueAsString(node, "type")
|
||||||
?: throw IllegalArgumentException("undefined type for $blockchain")
|
?: throw IllegalArgumentException("undefined type for $blockchain")
|
||||||
val gasPriceCondition = getValueAsString(node, "gas-price-condition")
|
val gasPriceConditions = getListOfString(node, "gas-price-condition") ?: emptyList()
|
||||||
return ChainsConfig.ChainConfig(
|
return ChainsConfig.ChainConfig(
|
||||||
expectedBlockTime = expectedBlockTime,
|
expectedBlockTime = expectedBlockTime,
|
||||||
syncingLagSize = lags.first,
|
syncingLagSize = lags.first,
|
||||||
@@ -101,7 +135,7 @@ class ChainsConfigReader(
|
|||||||
id = id,
|
id = id,
|
||||||
blockchain = blockchain,
|
blockchain = blockchain,
|
||||||
type = type,
|
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)
|
val merged = mergeMappingNode(defChain.second, curChain.second)
|
||||||
parseChain(defChain.first, merged!!)
|
parseChain(defChain.first, merged!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> ChainsConfig.ChainConfig.default()
|
else -> ChainsConfig.ChainConfig.default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,9 @@ chain-settings:
|
|||||||
grpcId: 1006
|
grpcId: 1006
|
||||||
chain-id: 0x38
|
chain-id: 0x38
|
||||||
short-names: [bsc, binance, bnb-smart-chain]
|
short-names: [bsc, binance, bnb-smart-chain]
|
||||||
gas-price-condition: ne 3000000000
|
gas-price-condition:
|
||||||
|
- ne 3000000000
|
||||||
|
- ne 5000000000
|
||||||
- id: Testnet
|
- id: Testnet
|
||||||
priority: 1
|
priority: 1
|
||||||
code: BSC_TESTNET
|
code: BSC_TESTNET
|
||||||
@@ -610,7 +612,8 @@ chain-settings:
|
|||||||
short-names: [kava]
|
short-names: [kava]
|
||||||
chain-id: 0x8ae
|
chain-id: 0x8ae
|
||||||
grpcId: 1025
|
grpcId: 1025
|
||||||
gas-price-condition: eq 1000000000
|
gas-price-condition:
|
||||||
|
- eq 1000000000
|
||||||
- id: Testnet
|
- id: Testnet
|
||||||
priority: 10
|
priority: 10
|
||||||
code: KAVA_TESTNET
|
code: KAVA_TESTNET
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun validateGasPrice(): Mono<ValidateUpstreamSettingsResult> {
|
private fun validateGasPrice(): Mono<ValidateUpstreamSettingsResult> {
|
||||||
if (!options.validateGasPrice || config.gasPriceCondition == null) {
|
if (!options.validateGasPrice) {
|
||||||
return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
||||||
}
|
}
|
||||||
return upstream.getIngressReader()
|
return upstream.getIngressReader()
|
||||||
@@ -205,10 +205,10 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
.flatMap(ChainResponse::requireStringResult)
|
.flatMap(ChainResponse::requireStringResult)
|
||||||
.map { result ->
|
.map { result ->
|
||||||
val actualGasPrice = result.substring(2).toLong(16)
|
val actualGasPrice = result.substring(2).toLong(16)
|
||||||
if (!config.gasPriceCondition!!.check(actualGasPrice)) {
|
if (!config.gasPriceCondition.check(actualGasPrice)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"Node ${upstream.getId()} has gasPrice $actualGasPrice, " +
|
"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
|
ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.validateChain = false
|
it.validateChain = false
|
||||||
it.validateCallLimit = false
|
it.validateCallLimit = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def conf = ChainConfig.defaultWithGasPriceCondition("ne 3000000000")
|
def conf = ChainConfig.defaultWithGasPriceCondition(["ne 3000000000", "ne 5000000000"])
|
||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
3 * getIngressReader() >>
|
3 * getIngressReader() >>
|
||||||
Mock(Reader) {
|
Mock(Reader) {
|
||||||
@@ -374,7 +374,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.validateChain = false
|
it.validateChain = false
|
||||||
it.validateCallLimit = false
|
it.validateCallLimit = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def conf = ChainConfig.defaultWithGasPriceCondition("eq 1000000000")
|
def conf = ChainConfig.defaultWithGasPriceCondition(["eq 1000000000"])
|
||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
3 * getIngressReader() >>
|
3 * getIngressReader() >>
|
||||||
Mock(Reader) {
|
Mock(Reader) {
|
||||||
@@ -395,6 +395,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
setup:
|
setup:
|
||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateCallLimit = false
|
it.validateCallLimit = false
|
||||||
|
it.validateGasPrice = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
4 * getIngressReader() >> Mock(Reader) {
|
4 * getIngressReader() >> Mock(Reader) {
|
||||||
@@ -417,6 +418,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
setup:
|
setup:
|
||||||
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
it.validateCallLimit = false
|
it.validateCallLimit = false
|
||||||
|
it.validateGasPrice = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
4 * getIngressReader() >> Mock(Reader) {
|
4 * getIngressReader() >> Mock(Reader) {
|
||||||
@@ -437,7 +439,9 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
|
|
||||||
def "Upstream is valid if all setting are valid"() {
|
def "Upstream is valid if all setting are valid"() {
|
||||||
setup:
|
setup:
|
||||||
def options = ChainOptions.PartialOptions.getDefaults().buildOptions()
|
def options = ChainOptions.PartialOptions.getDefaults().tap{
|
||||||
|
it.validateGasPrice = false
|
||||||
|
}.buildOptions()
|
||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
5 * getIngressReader() >> Mock(Reader) {
|
5 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse('"0x1"'.getBytes(), null))
|
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"() {
|
def "Upstream is not valid if there are errors"() {
|
||||||
setup:
|
setup:
|
||||||
def options = ChainOptions.PartialOptions.getDefaults().buildOptions()
|
def options = ChainOptions.PartialOptions.getDefaults().tap {
|
||||||
|
it.validateGasPrice = false
|
||||||
|
}.buildOptions()
|
||||||
def up = Mock(Upstream) {
|
def up = Mock(Upstream) {
|
||||||
5 * getIngressReader() >> Mock(Reader) {
|
5 * getIngressReader() >> Mock(Reader) {
|
||||||
1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long")))
|
1 * read(new ChainRequest("eth_chainId", new ListParams())) >> Mono.just(new ChainResponse(null, new ChainCallError(1, "Too long")))
|
||||||
|
|||||||
Reference in New Issue
Block a user