Check gas price (#494)

* add gas validation option

* gas price config for chains

* check gas price

* add tests

* fix txt

* make gas price dls

* fix config
This commit is contained in:
Anton
2024-05-31 15:46:55 +03:00
committed by GitHub
parent 4aff35994f
commit 772142e156
9 changed files with 133 additions and 19 deletions

View File

@@ -94,8 +94,9 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
validateChain(),
validateOldBlocks(),
validateCallLimit(),
validateGasPrice(),
).map {
listOf(it.t1, it.t2, it.t3).maxOf { it }
listOf(it.t1, it.t2, it.t3, it.t4).maxOf { it }
}.block() ?: ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR
}
@@ -195,6 +196,31 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
}
}
private fun validateGasPrice(): Mono<ValidateUpstreamSettingsResult> {
if (!options.validateGasPrice || config.gasPriceCondition == null) {
return Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
}
return upstream.getIngressReader()
.read(ChainRequest("eth_gasPrice", ListParams()))
.flatMap(ChainResponse::requireStringResult)
.map { result ->
val actualGasPrice = result.substring(2).toLong(16)
if (!config.gasPriceCondition!!.check(actualGasPrice)) {
log.warn(
"Node ${upstream.getId()} has gasPrice $actualGasPrice, " +
"but it is not equal to the required ${config.gasPriceCondition!!.rules()}",
)
ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR
} else {
ValidateUpstreamSettingsResult.UPSTREAM_VALID
}
}
.onErrorResume { err ->
log.warn("Error during gasPrice validation", err)
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR)
}
}
private fun chainId(): Mono<String> {
return upstream.getIngressReader()
.read(ChainRequest("eth_chainId", ListParams()))

View File

@@ -636,7 +636,7 @@ class UpstreamsConfigReaderSpec extends Specification {
def options = partialOptions.buildOptions()
then:
options == new ChainOptions.Options(
false, false, 30, Duration.ofSeconds(60), null, true, 1, true, true, true, 1_000_000
false, false, 30, Duration.ofSeconds(60), null, true, 1, true, true, true, true, 1_000_000
)
}
}

View File

@@ -36,6 +36,7 @@ import spock.lang.Specification
import java.time.Duration
import static io.emeraldpay.dshackle.Chain.BSC__MAINNET
import static io.emeraldpay.dshackle.Chain.ETHEREUM__MAINNET
import static io.emeraldpay.dshackle.Chain.OPTIMISM__MAINNET
import static io.emeraldpay.dshackle.upstream.UpstreamAvailability.*
@@ -55,16 +56,16 @@ class EthereumUpstreamValidatorSpec extends Specification {
expect:
validator.resolve(Tuples.of(sync, peers)) == exp
where:
exp | sync | peers
OK | OK | OK
IMMATURE | OK | IMMATURE
UNAVAILABLE | OK | UNAVAILABLE
SYNCING | SYNCING | OK
SYNCING | SYNCING | IMMATURE
UNAVAILABLE | SYNCING | UNAVAILABLE
UNAVAILABLE | UNAVAILABLE | OK
UNAVAILABLE | UNAVAILABLE | IMMATURE
UNAVAILABLE | UNAVAILABLE | UNAVAILABLE
exp | sync | peers
OK | OK | OK
IMMATURE | OK | IMMATURE
UNAVAILABLE | OK | UNAVAILABLE
SYNCING | SYNCING | OK
SYNCING | SYNCING | IMMATURE
UNAVAILABLE | SYNCING | UNAVAILABLE
UNAVAILABLE | UNAVAILABLE | OK
UNAVAILABLE | UNAVAILABLE | IMMATURE
UNAVAILABLE | UNAVAILABLE | UNAVAILABLE
}
def "Doesnt check eth_syncing when disabled"() {
@@ -112,7 +113,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
Mono.just(new ChainResponse('false'.getBytes(), null))
]
}
2 * getHead() >> Mock(Head) {head ->
2 * getHead() >> Mock(Head) { head ->
1 * head.onSyncingNode(true)
1 * head.onSyncingNode(false)
}
@@ -276,6 +277,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
def options = ChainOptions.PartialOptions.getDefaults().tap {
it.validateCallLimit = false
it.validateChain = false
it.validateGasPrice = false
}.buildOptions()
def up = Mock(Upstream) {
2 * getIngressReader() >>
@@ -297,6 +299,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
setup:
def options = ChainOptions.PartialOptions.getDefaults().tap {
it.validateChain = false
it.validateGasPrice = false
}.buildOptions()
def up = Mock(Upstream) {
3 * getIngressReader() >> Mock(Reader) {
@@ -321,6 +324,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
setup:
def options = ChainOptions.PartialOptions.getDefaults().tap {
it.validateChain = false
it.validateGasPrice = false
}.buildOptions()
def up = Mock(Upstream) {
3 * getIngressReader() >> Mock(Reader) {
@@ -341,6 +345,52 @@ class EthereumUpstreamValidatorSpec extends Specification {
act == UPSTREAM_SETTINGS_ERROR
}
def "Upstream is valid if gas price is equal to expected"() {
setup:
def options = ChainOptions.PartialOptions.getDefaults().tap {
it.validateChain = false
it.validateCallLimit = false
}.buildOptions()
def conf = ChainConfig.defaultWithGasPriceCondition("ne 3000000000")
def up = Mock(Upstream) {
3 * getIngressReader() >>
Mock(Reader) {
1 * read(new ChainRequest("eth_gasPrice", new ListParams())) >> Mono.just(new ChainResponse('"0x3b9aca00"'.getBytes(), null))
1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null))
1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >>
Mono.just(new ChainResponse('"result"'.getBytes(), null))
}
}
def validator = new EthereumUpstreamValidator(BSC__MAINNET, up, options, conf)
when:
def act = validator.validateUpstreamSettingsOnStartup()
then:
act == UPSTREAM_VALID
}
def "Upstream is NOT valid if gas price is different from expected"() {
setup:
def options = ChainOptions.PartialOptions.getDefaults().tap {
it.validateChain = false
it.validateCallLimit = false
}.buildOptions()
def conf = ChainConfig.defaultWithGasPriceCondition("eq 1000000000")
def up = Mock(Upstream) {
3 * getIngressReader() >>
Mock(Reader) {
1 * read(new ChainRequest("eth_gasPrice", new ListParams())) >> Mono.just(new ChainResponse('"0xb2d05e00"'.getBytes(), null))
1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >> Mono.just(new ChainResponse('"0x10ff9be"'.getBytes(), null))
1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x10fd2ae", false]))) >>
Mono.just(new ChainResponse('"result"'.getBytes(), null))
}
}
def validator = new EthereumUpstreamValidator(BSC__MAINNET, up, options, conf)
when:
def act = validator.validateUpstreamSettingsOnStartup()
then:
act == UPSTREAM_FATAL_SETTINGS_ERROR
}
def "Upstream is valid if chain settings are valid"() {
setup:
def options = ChainOptions.PartialOptions.getDefaults().tap {