Add validation of chain setting (#258)
This commit is contained in:
@@ -744,6 +744,11 @@ See link:09-quorum-and-selectors.adoc[Quorum and Selectors]
|
|||||||
| `false`
|
| `false`
|
||||||
| Disables all the validations of the upstream. I.e., it turns off `validate-peers` and `validate-syncing` checks if set to `true`.
|
| Disables all the validations of the upstream. I.e., it turns off `validate-peers` and `validate-syncing` checks if set to `true`.
|
||||||
|
|
||||||
|
| `validate-chain`
|
||||||
|
| boolean
|
||||||
|
| `true`
|
||||||
|
| Disables validation of the chain settings of the upstream. Prevent of creating an upstream with incorrect chain if it relates to a node with another chain.
|
||||||
|
|
||||||
| `validation-interval`
|
| `validation-interval`
|
||||||
| number
|
| number
|
||||||
| `30`
|
| `30`
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ open class UpstreamsConfig {
|
|||||||
val validatePeers: Boolean,
|
val validatePeers: Boolean,
|
||||||
val minPeers: Int,
|
val minPeers: Int,
|
||||||
val validateSyncing: Boolean,
|
val validateSyncing: Boolean,
|
||||||
val validateCallLimit: Boolean
|
val validateCallLimit: Boolean,
|
||||||
|
val validateChain: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
open class PartialOptions {
|
open class PartialOptions {
|
||||||
@@ -61,6 +62,7 @@ open class UpstreamsConfig {
|
|||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
var validateSyncing: Boolean? = null
|
var validateSyncing: Boolean? = null
|
||||||
|
var validateChain: Boolean? = null
|
||||||
|
|
||||||
fun merge(overwrites: PartialOptions?): PartialOptions {
|
fun merge(overwrites: PartialOptions?): PartialOptions {
|
||||||
if (overwrites == null) {
|
if (overwrites == null) {
|
||||||
@@ -75,6 +77,7 @@ open class UpstreamsConfig {
|
|||||||
copy.validateSyncing = firstNonNull(overwrites.validateSyncing, this.validateSyncing)
|
copy.validateSyncing = firstNonNull(overwrites.validateSyncing, this.validateSyncing)
|
||||||
copy.validateCalllimit = firstNonNull(overwrites.validateCalllimit, this.validateCalllimit)
|
copy.validateCalllimit = firstNonNull(overwrites.validateCalllimit, this.validateCalllimit)
|
||||||
copy.timeout = firstNonNull(overwrites.timeout, this.timeout)
|
copy.timeout = firstNonNull(overwrites.timeout, this.timeout)
|
||||||
|
copy.validateChain = firstNonNull(overwrites.validateChain, this.validateChain)
|
||||||
return copy
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +90,8 @@ open class UpstreamsConfig {
|
|||||||
firstNonNull(this.validatePeers, true)!!,
|
firstNonNull(this.validatePeers, true)!!,
|
||||||
firstNonNull(this.minPeers, 1)!!,
|
firstNonNull(this.minPeers, 1)!!,
|
||||||
firstNonNull(this.validateSyncing, true)!!,
|
firstNonNull(this.validateSyncing, true)!!,
|
||||||
firstNonNull(this.validateCalllimit, true)!!
|
firstNonNull(this.validateCalllimit, true)!!,
|
||||||
|
firstNonNull(this.validateChain, true)!!
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -356,6 +356,9 @@ class UpstreamsConfigReader(
|
|||||||
getValueAsBool(values, "validate-call-limit")?.let {
|
getValueAsBool(values, "validate-call-limit")?.let {
|
||||||
options.validateCalllimit = it
|
options.validateCalllimit = it
|
||||||
}
|
}
|
||||||
|
getValueAsBool(values, "validate-chain")?.let {
|
||||||
|
options.validateChain = it
|
||||||
|
}
|
||||||
getValueAsInt(values, "min-peers")?.let {
|
getValueAsInt(values, "min-peers")?.let {
|
||||||
options.minPeers = it
|
options.minPeers = it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ open class ConfiguredUpstreams(
|
|||||||
override fun run(args: ApplicationArguments) {
|
override fun run(args: ApplicationArguments) {
|
||||||
log.debug("Starting upstreams")
|
log.debug("Starting upstreams")
|
||||||
val defaultOptions = buildDefaultOptions(config)
|
val defaultOptions = buildDefaultOptions(config)
|
||||||
config.upstreams.forEach { up ->
|
config.upstreams.parallelStream().forEach { up ->
|
||||||
if (!up.isEnabled) {
|
if (!up.isEnabled) {
|
||||||
log.debug("Upstream ${up.id} is disabled")
|
log.debug("Upstream ${up.id} is disabled")
|
||||||
return@forEach
|
return@forEach
|
||||||
@@ -239,6 +239,7 @@ open class ConfiguredUpstreams(
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
upstream.start()
|
upstream.start()
|
||||||
|
if (!upstream.isRunning) return null
|
||||||
return upstream
|
return upstream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,44 @@ class DefaultEthereumMethods(
|
|||||||
"debug_traceCallMany",
|
"debug_traceCallMany",
|
||||||
"debug_traceTransaction"
|
"debug_traceTransaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val CHAIN_DATA = mapOf(
|
||||||
|
Chain.ETHEREUM__MAINNET to HardcodedData("\"1\"", "\"0x1\""),
|
||||||
|
Chain.ETHEREUM__RINKEBY to HardcodedData("\"4\"", "\"0x4\""),
|
||||||
|
Chain.ETHEREUM__ROPSTEN to HardcodedData("\"3\"", "\"0x3\""),
|
||||||
|
Chain.ETHEREUM__KOVAN to HardcodedData("\"42\"", "\"0x2a\""),
|
||||||
|
Chain.ETHEREUM__GOERLI to HardcodedData("\"5\"", "\"0x5\""),
|
||||||
|
Chain.ETHEREUM__SEPOLIA to HardcodedData("\"11155111\"", "\"0xaa36a7\""),
|
||||||
|
|
||||||
|
Chain.ETHEREUM_CLASSIC__MAINNET to HardcodedData("\"1\"", "\"0x3d\""),
|
||||||
|
|
||||||
|
Chain.POLYGON_POS__MAINNET to HardcodedData("\"137\"", "\"0x89\""),
|
||||||
|
Chain.POLYGON_POS__MUMBAI to HardcodedData("\"80001\"", "\"0x13881\""),
|
||||||
|
|
||||||
|
Chain.ARBITRUM__MAINNET to HardcodedData("\"42161\"", "\"0xa4b1\""),
|
||||||
|
Chain.ARBITRUM__GOERLI to HardcodedData("\"421613\"", "\"0x66eed\""),
|
||||||
|
|
||||||
|
Chain.OPTIMISM__MAINNET to HardcodedData("\"10\"", "\"0xa\""),
|
||||||
|
Chain.OPTIMISM__GOERLI to HardcodedData("\"420\"", "\"0x1A4\""),
|
||||||
|
|
||||||
|
Chain.ARBITRUM_NOVA__MAINNET to HardcodedData("\"42170\"", "\"0xa4ba\""),
|
||||||
|
|
||||||
|
Chain.POLYGON_ZKEVM__MAINNET to HardcodedData("\"1101\"", "\"0x44d\""),
|
||||||
|
Chain.POLYGON_ZKEVM__TESTNET to HardcodedData("\"1442\"", "\"0x5a2\""),
|
||||||
|
|
||||||
|
Chain.ZKSYNC__MAINNET to HardcodedData("\"324\"", "\"0x144\""),
|
||||||
|
Chain.ZKSYNC__TESTNET to HardcodedData("\"280\"", "\"0x118\""),
|
||||||
|
|
||||||
|
Chain.BSC__MAINNET to HardcodedData("\"56\"", "\"0x38\""),
|
||||||
|
|
||||||
|
Chain.BASE__MAINNET to HardcodedData("\"8453\"", "\"0x2105\""),
|
||||||
|
Chain.BASE__GOERLI to HardcodedData("\"84531\"", "\"0x14a33\""),
|
||||||
|
|
||||||
|
Chain.LINEA__MAINNET to HardcodedData("\"59144\"", "\"0xe708\""),
|
||||||
|
Chain.LINEA__GOERLI to HardcodedData("\"59140\"", "\"0xe704\""),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getChainByData(data: HardcodedData) = CHAIN_DATA.entries.find { it.value == data }?.key
|
||||||
}
|
}
|
||||||
|
|
||||||
private val anyResponseMethods = listOf(
|
private val anyResponseMethods = listOf(
|
||||||
@@ -243,48 +281,12 @@ class DefaultEthereumMethods(
|
|||||||
|
|
||||||
data class HardcodedData(val netVersion: String, val chainId: String)
|
data class HardcodedData(val netVersion: String, val chainId: String)
|
||||||
|
|
||||||
private val hardcodedData = mapOf(
|
|
||||||
Chain.ETHEREUM__MAINNET to HardcodedData("\"1\"", "\"0x1\""),
|
|
||||||
Chain.ETHEREUM__RINKEBY to HardcodedData("\"4\"", "\"0x4\""),
|
|
||||||
Chain.ETHEREUM__ROPSTEN to HardcodedData("\"3\"", "\"0x3\""),
|
|
||||||
Chain.ETHEREUM__KOVAN to HardcodedData("\"42\"", "\"0x2a\""),
|
|
||||||
Chain.ETHEREUM__GOERLI to HardcodedData("\"5\"", "\"0x5\""),
|
|
||||||
Chain.ETHEREUM__SEPOLIA to HardcodedData("\"11155111\"", "\"0xaa36a7\""),
|
|
||||||
|
|
||||||
Chain.ETHEREUM_CLASSIC__MAINNET to HardcodedData("\"1\"", "\"0x3d\""),
|
|
||||||
|
|
||||||
Chain.POLYGON_POS__MAINNET to HardcodedData("\"137\"", "\"0x89\""),
|
|
||||||
Chain.POLYGON_POS__MUMBAI to HardcodedData("\"80001\"", "\"0x13881\""),
|
|
||||||
|
|
||||||
Chain.ARBITRUM__MAINNET to HardcodedData("\"42161\"", "\"0xa4b1\""),
|
|
||||||
Chain.ARBITRUM__GOERLI to HardcodedData("\"421613\"", "\"0x66eed\""),
|
|
||||||
|
|
||||||
Chain.OPTIMISM__MAINNET to HardcodedData("\"10\"", "\"0xa\""),
|
|
||||||
Chain.OPTIMISM__GOERLI to HardcodedData("\"420\"", "\"0x1A4\""),
|
|
||||||
|
|
||||||
Chain.ARBITRUM_NOVA__MAINNET to HardcodedData("\"42170\"", "\"0xa4ba\""),
|
|
||||||
|
|
||||||
Chain.POLYGON_ZKEVM__MAINNET to HardcodedData("\"1101\"", "\"0x44d\""),
|
|
||||||
Chain.POLYGON_ZKEVM__TESTNET to HardcodedData("\"1442\"", "\"0x5a2\""),
|
|
||||||
|
|
||||||
Chain.ZKSYNC__MAINNET to HardcodedData("\"324\"", "\"0x144\""),
|
|
||||||
Chain.ZKSYNC__TESTNET to HardcodedData("\"280\"", "\"0x118\""),
|
|
||||||
|
|
||||||
Chain.BSC__MAINNET to HardcodedData("\"56\"", "\"0x38\""),
|
|
||||||
|
|
||||||
Chain.BASE__MAINNET to HardcodedData("\"8453\"", "\"0x2105\""),
|
|
||||||
Chain.BASE__GOERLI to HardcodedData("\"84531\"", "\"0x14a33\""),
|
|
||||||
|
|
||||||
Chain.LINEA__MAINNET to HardcodedData("\"59144\"", "\"0xe708\""),
|
|
||||||
Chain.LINEA__GOERLI to HardcodedData("\"59140\"", "\"0xe704\""),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun executeHardcoded(method: String): ByteArray {
|
override fun executeHardcoded(method: String): ByteArray {
|
||||||
// note that the value is in json representation, i.e. if it's a string it should be with quotes,
|
// note that the value is in json representation, i.e. if it's a string it should be with quotes,
|
||||||
// that's why "\"0x0\"", "\"1\"", etc. But just "true" for a boolean, or "[]" for array.
|
// that's why "\"0x0\"", "\"1\"", etc. But just "true" for a boolean, or "[]" for array.
|
||||||
val json = when (method) {
|
val json = when (method) {
|
||||||
"net_version" -> hardcodedData.get(chain)?.netVersion ?: throw RpcException(-32602, "Invalid chain")
|
"net_version" -> CHAIN_DATA.get(chain)?.netVersion ?: throw RpcException(-32602, "Invalid chain")
|
||||||
"eth_chainId" -> hardcodedData.get(chain)?.chainId ?: throw RpcException(-32602, "Invalid chain")
|
"eth_chainId" -> CHAIN_DATA.get(chain)?.chainId ?: throw RpcException(-32602, "Invalid chain")
|
||||||
|
|
||||||
"net_peerCount" -> {
|
"net_peerCount" -> {
|
||||||
"\"0x2a\""
|
"\"0x2a\""
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ open class EthereumLikeRpcUpstream(
|
|||||||
chainConfig: ChainsConfig.ChainConfig,
|
chainConfig: ChainsConfig.ChainConfig,
|
||||||
skipEnhance: Boolean
|
skipEnhance: Boolean
|
||||||
) : EthereumLikeUpstream(id, hash, options, role, targets, node, chainConfig), Lifecycle, Upstream, CachesEnabled {
|
) : EthereumLikeUpstream(id, hash, options, role, targets, node, chainConfig), Lifecycle, Upstream, CachesEnabled {
|
||||||
private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(this, getOptions(), chainConfig.callLimitContract)
|
private val validator: EthereumUpstreamValidator = EthereumUpstreamValidator(chain, this, getOptions(), chainConfig.callLimitContract)
|
||||||
private val connector: EthereumConnector = connectorFactory.create(this, validator, chain, skipEnhance)
|
private val connector: EthereumConnector = connectorFactory.create(this, validator, chain, skipEnhance)
|
||||||
private val labelsDetector = EthereumLabelsDetector(this.getIngressReader())
|
private val labelsDetector = EthereumLabelsDetector(this.getIngressReader())
|
||||||
|
|
||||||
@@ -60,6 +60,11 @@ open class EthereumLikeRpcUpstream(
|
|||||||
override fun start() {
|
override fun start() {
|
||||||
log.info("Configured for ${chain.chainName}")
|
log.info("Configured for ${chain.chainName}")
|
||||||
connector.start()
|
connector.start()
|
||||||
|
if (!validator.validateUpstreamSettings()) {
|
||||||
|
connector.stop()
|
||||||
|
log.warn("Upstream ${getId()} couldn't start, invalid upstream settings")
|
||||||
|
return
|
||||||
|
}
|
||||||
if (getOptions().disableValidation) {
|
if (getOptions().disableValidation) {
|
||||||
log.warn("Disable validation for upstream ${this.getId()}")
|
log.warn("Disable validation for upstream ${this.getId()}")
|
||||||
this.setLag(0)
|
this.setLag(0)
|
||||||
|
|||||||
@@ -17,11 +17,15 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
import io.emeraldpay.dshackle.Defaults
|
import io.emeraldpay.dshackle.Defaults
|
||||||
import io.emeraldpay.dshackle.Global
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
import io.emeraldpay.dshackle.upstream.Upstream
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods.Companion.CHAIN_DATA
|
||||||
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods.Companion.getChainByData
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
@@ -33,17 +37,17 @@ import org.springframework.scheduling.concurrent.CustomizableThreadFactory
|
|||||||
import reactor.core.publisher.Flux
|
import reactor.core.publisher.Flux
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import reactor.core.scheduler.Schedulers
|
import reactor.core.scheduler.Schedulers
|
||||||
import reactor.util.function.Tuple3
|
import reactor.util.function.Tuple2
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
open class EthereumUpstreamValidator @JvmOverloads constructor(
|
open class EthereumUpstreamValidator @JvmOverloads constructor(
|
||||||
|
private val chain: Chain,
|
||||||
private val upstream: Upstream,
|
private val upstream: Upstream,
|
||||||
private val options: UpstreamsConfig.Options,
|
private val options: UpstreamsConfig.Options,
|
||||||
private val callLimitContract: String? = null
|
private val callLimitContract: String? = null
|
||||||
) {
|
) {
|
||||||
private var callLimitSucceed: Boolean = false
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java)
|
private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java)
|
||||||
val scheduler =
|
val scheduler =
|
||||||
@@ -55,55 +59,16 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
open fun validate(): Mono<UpstreamAvailability> {
|
open fun validate(): Mono<UpstreamAvailability> {
|
||||||
return Mono.zip(
|
return Mono.zip(
|
||||||
validateSyncing(),
|
validateSyncing(),
|
||||||
validatePeers(),
|
validatePeers()
|
||||||
validateCallLimit()
|
|
||||||
)
|
)
|
||||||
.map(::resolve)
|
.map(::resolve)
|
||||||
.defaultIfEmpty(UpstreamAvailability.UNAVAILABLE)
|
.defaultIfEmpty(UpstreamAvailability.UNAVAILABLE)
|
||||||
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resolve(results: Tuple3<UpstreamAvailability, UpstreamAvailability, UpstreamAvailability>): UpstreamAvailability {
|
fun resolve(results: Tuple2<UpstreamAvailability, UpstreamAvailability>): UpstreamAvailability {
|
||||||
val cp = Comparator { avail1: UpstreamAvailability, avail2: UpstreamAvailability -> if (avail1.isBetterTo(avail2)) -1 else 1 }
|
val cp = Comparator { avail1: UpstreamAvailability, avail2: UpstreamAvailability -> if (avail1.isBetterTo(avail2)) -1 else 1 }
|
||||||
return listOf(results.t1, results.t2, results.t3).sortedWith(cp).last()
|
return listOf(results.t1, results.t2).sortedWith(cp).last()
|
||||||
}
|
|
||||||
|
|
||||||
fun validateCallLimit(): Mono<UpstreamAvailability> {
|
|
||||||
// do not rerun this check after first success because it's more expensive than others
|
|
||||||
if (!options.validateCallLimit || callLimitContract == null || callLimitSucceed) {
|
|
||||||
return Mono.just(UpstreamAvailability.OK)
|
|
||||||
}
|
|
||||||
return upstream.getIngressReader()
|
|
||||||
.read(
|
|
||||||
JsonRpcRequest(
|
|
||||||
"eth_call",
|
|
||||||
listOf(
|
|
||||||
TransactionCallJson(
|
|
||||||
Address.from(callLimitContract),
|
|
||||||
// calling contract with param 200_000, meaning it will generate 200k symbols or response
|
|
||||||
// 30ce0 + metadata — 200_000k
|
|
||||||
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
|
||||||
),
|
|
||||||
"latest"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.flatMap(JsonRpcResponse::requireResult)
|
|
||||||
.doOnError {
|
|
||||||
log.error(
|
|
||||||
"Error: ${it.message}. Node ${upstream.getId()} is prbably incorrectly configured. " +
|
|
||||||
"You need to set up your return limit to at least 200000. " +
|
|
||||||
"Erigon config example: https://github.com/ledgerwatch/erigon/blob/devel/cmd/utils/flags.go#L364. "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.map { UpstreamAvailability.OK }
|
|
||||||
.timeout(
|
|
||||||
Defaults.timeoutInternal,
|
|
||||||
Mono.fromCallable { log.error("No response for eth_call limit check from ${upstream.getId()}") }
|
|
||||||
.then(Mono.error(TimeoutException("Validation timeout for call limit")))
|
|
||||||
)
|
|
||||||
.doOnSuccess { callLimitSucceed = true }
|
|
||||||
.onErrorReturn(UpstreamAvailability.UNAVAILABLE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateSyncing(): Mono<UpstreamAvailability> {
|
fun validateSyncing(): Mono<UpstreamAvailability> {
|
||||||
@@ -165,4 +130,95 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
|||||||
validate()
|
validate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun validateUpstreamSettings(): Boolean {
|
||||||
|
return Mono.zip(
|
||||||
|
validateChain(),
|
||||||
|
validateCallLimit()
|
||||||
|
).map {
|
||||||
|
it.t1 && it.t2
|
||||||
|
}.block() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateChain(): Mono<Boolean> {
|
||||||
|
if (!options.validateChain) {
|
||||||
|
return Mono.just(true)
|
||||||
|
}
|
||||||
|
return Mono.zip(
|
||||||
|
chainId(),
|
||||||
|
netVersion()
|
||||||
|
)
|
||||||
|
.map {
|
||||||
|
val chainData = CHAIN_DATA[chain] ?: return@map false
|
||||||
|
val isChainValid = chainData.chainId == it.t1 && chainData.netVersion == it.t2
|
||||||
|
|
||||||
|
if (!isChainValid) {
|
||||||
|
val actualChain = getChainByData(
|
||||||
|
DefaultEthereumMethods.HardcodedData(it.t2, it.t1)
|
||||||
|
)?.chainName
|
||||||
|
log.warn(
|
||||||
|
"${chain.chainName} is specified for upstream ${upstream.getId()} " +
|
||||||
|
"but actually it is $actualChain with chainId ${it.t1} and net_version ${it.t2}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
isChainValid
|
||||||
|
}
|
||||||
|
.onErrorResume {
|
||||||
|
log.error("Error during chain validation", it)
|
||||||
|
Mono.just(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateCallLimit(): Mono<Boolean> {
|
||||||
|
if (!options.validateCallLimit || callLimitContract == null) {
|
||||||
|
return Mono.just(true)
|
||||||
|
}
|
||||||
|
return upstream.getIngressReader()
|
||||||
|
.read(
|
||||||
|
JsonRpcRequest(
|
||||||
|
"eth_call",
|
||||||
|
listOf(
|
||||||
|
TransactionCallJson(
|
||||||
|
Address.from(callLimitContract),
|
||||||
|
// calling contract with param 200_000, meaning it will generate 200k symbols or response
|
||||||
|
// 30ce0 + metadata — 200_000k
|
||||||
|
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||||
|
),
|
||||||
|
"latest"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
|
.doOnError {
|
||||||
|
log.warn(
|
||||||
|
"Error: ${it.message}. Node ${upstream.getId()} is probably incorrectly configured. " +
|
||||||
|
"You need to set up your return limit to at least 200000. " +
|
||||||
|
"Erigon config example: https://github.com/ledgerwatch/erigon/blob/devel/cmd/utils/flags.go#L364. "
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map { true }
|
||||||
|
.timeout(
|
||||||
|
Defaults.timeoutInternal,
|
||||||
|
Mono.fromCallable { log.error("No response for eth_call limit check from ${upstream.getId()}") }
|
||||||
|
.then(Mono.error(TimeoutException("Validation timeout for call limit")))
|
||||||
|
)
|
||||||
|
.onErrorReturn(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chainId(): Mono<String> {
|
||||||
|
return upstream.getIngressReader()
|
||||||
|
.read(JsonRpcRequest("eth_chainId", emptyList()))
|
||||||
|
.doOnError { log.error("Error during execution 'eth_chainId' - ${it.message}") }
|
||||||
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
|
.map { String(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun netVersion(): Mono<String> {
|
||||||
|
return upstream.getIngressReader()
|
||||||
|
.read(JsonRpcRequest("net_version", emptyList()))
|
||||||
|
.doOnError { log.error("Error during execution 'net_version' - ${it.message}") }
|
||||||
|
.flatMap(JsonRpcResponse::requireResult)
|
||||||
|
.map { String(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ class UpstreamsConfigReaderSpec extends Specification {
|
|||||||
def options = partialOptions.buildOptions()
|
def options = partialOptions.buildOptions()
|
||||||
then:
|
then:
|
||||||
options == new UpstreamsConfig.Options(
|
options == new UpstreamsConfig.Options(
|
||||||
false, 30, Duration.ofSeconds(60), null, true, 1, true, true
|
false, 30, Duration.ofSeconds(60), null, true, 1, true, true, true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class HeadLagObserverSpec extends Specification {
|
|||||||
|
|
||||||
TestHeadLagObserver(@NotNull Head master, @NotNull Collection<? extends Upstream> followers) {
|
TestHeadLagObserver(@NotNull Head master, @NotNull Collection<? extends Upstream> followers) {
|
||||||
super(master, followers, DistanceExtractor.@Companion::extractPowDistance,
|
super(master, followers, DistanceExtractor.@Companion::extractPowDistance,
|
||||||
Schedulers.parallel(), Duration.ofNanos(1))
|
Schedulers.boundedElastic(), Duration.ofNanos(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,30 +15,35 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
|
||||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.emeraldpay.dshackle.test.ApiReaderMock
|
import io.emeraldpay.dshackle.test.ApiReaderMock
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
import io.emeraldpay.dshackle.upstream.Head
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
|
import io.emeraldpay.etherjar.domain.Address
|
||||||
import io.emeraldpay.etherjar.hex.HexData
|
import io.emeraldpay.etherjar.hex.HexData
|
||||||
import io.emeraldpay.etherjar.rpc.RpcResponseError
|
import io.emeraldpay.etherjar.rpc.RpcResponseError
|
||||||
import io.emeraldpay.etherjar.rpc.json.TransactionCallJson
|
import io.emeraldpay.etherjar.rpc.json.TransactionCallJson
|
||||||
import io.emeraldpay.etherjar.domain.Address
|
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import reactor.util.function.Tuples
|
import reactor.util.function.Tuples
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
|
import static io.emeraldpay.dshackle.Chain.ETHEREUM__MAINNET
|
||||||
|
import static io.emeraldpay.dshackle.Chain.OPTIMISM__MAINNET
|
||||||
import static io.emeraldpay.dshackle.upstream.UpstreamAvailability.*
|
import static io.emeraldpay.dshackle.upstream.UpstreamAvailability.*
|
||||||
|
import static java.util.Collections.emptyList
|
||||||
|
|
||||||
class EthereumUpstreamValidatorSpec extends Specification {
|
class EthereumUpstreamValidatorSpec extends Specification {
|
||||||
|
|
||||||
def "Resolve to final availability"() {
|
def "Resolve to final availability"() {
|
||||||
setup:
|
setup:
|
||||||
def validator = new EthereumUpstreamValidator(Stub(EthereumLikeUpstream), UpstreamsConfig.PartialOptions.getDefaults().buildOptions())
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, Stub(EthereumLikeUpstream), UpstreamsConfig.PartialOptions.getDefaults().buildOptions())
|
||||||
expect:
|
expect:
|
||||||
validator.resolve(Tuples.of(sync, peers, call)) == exp
|
validator.resolve(Tuples.of(sync, peers, call)) == exp
|
||||||
where:
|
where:
|
||||||
@@ -60,7 +65,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.validateSyncing = false
|
it.validateSyncing = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(EthereumLikeUpstream)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -79,7 +84,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("eth_syncing", [], false)
|
answer("eth_syncing", [], false)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -104,7 +109,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
1 * head.onSyncingNode(false)
|
1 * head.onSyncingNode(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -124,7 +129,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("eth_syncing", [], [startingBlock: 100, currentBlock: 50])
|
answer("eth_syncing", [], [startingBlock: 100, currentBlock: 50])
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -142,7 +147,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("eth_syncing", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
answer("eth_syncing", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||||
@@ -157,7 +162,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.minPeers = 10
|
it.minPeers = 10
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(EthereumLikeUpstream)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -173,7 +178,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
it.minPeers = 0
|
it.minPeers = 0
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(EthereumLikeUpstream)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -193,7 +198,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], "0x5")
|
answer("net_peerCount", [], "0x5")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -212,7 +217,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], "0xa")
|
answer("net_peerCount", [], "0xa")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -231,7 +236,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], "0xff")
|
answer("net_peerCount", [], "0xff")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -250,7 +255,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
answer("net_peerCount", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
answer("net_peerCount", [], new RpcResponseError(RpcResponseError.CODE_METHOD_NOT_EXIST, "Unavailable"))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||||
@@ -258,65 +263,142 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
|||||||
act == UNAVAILABLE
|
act == UNAVAILABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Doesnt check call limit when disabled"() {
|
def "Doesnt validate settings when disabled"() {
|
||||||
|
setup:
|
||||||
|
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||||
|
it.validateCalllimit = false
|
||||||
|
it.validateChain = false
|
||||||
|
}.buildOptions()
|
||||||
|
def up = Mock(EthereumLikeUpstream)
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = validator.validateUpstreamSettings()
|
||||||
|
then:
|
||||||
|
act
|
||||||
|
0 * up.getIngressReader()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Upstream is valid if not error from call limit check"() {
|
||||||
|
setup:
|
||||||
|
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||||
|
it.validateChain = false
|
||||||
|
}.buildOptions()
|
||||||
|
def up = Mock(EthereumLikeRpcUpstream) {
|
||||||
|
1 * getIngressReader() >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
||||||
|
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||||
|
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||||
|
), "latest"])) >> Mono.just(new JsonRpcResponse("0x00000000000000000000".getBytes(), null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = validator.validateUpstreamSettings()
|
||||||
|
then:
|
||||||
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Upstream is not valid if error returned on call limit check"() {
|
||||||
|
setup:
|
||||||
|
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||||
|
it.validateChain = false
|
||||||
|
}.buildOptions()
|
||||||
|
def up = Mock(EthereumLikeRpcUpstream) {
|
||||||
|
1 * getIngressReader() >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
||||||
|
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||||
|
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||||
|
), "latest"])) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = validator.validateUpstreamSettings()
|
||||||
|
then:
|
||||||
|
!act
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Upstream is valid if chain settings are valid"() {
|
||||||
setup:
|
setup:
|
||||||
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||||
it.validateCalllimit = false
|
it.validateCalllimit = false
|
||||||
}.buildOptions()
|
}.buildOptions()
|
||||||
def up = Mock(EthereumLikeUpstream)
|
def up = Mock(EthereumLikeRpcUpstream) {
|
||||||
def validator = new EthereumUpstreamValidator(up, options)
|
2 * getIngressReader() >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
||||||
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
def act = validator.validateUpstreamSettings()
|
||||||
then:
|
then:
|
||||||
act == OK
|
act
|
||||||
0 * up.getIngressReader()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Upstream available if not error from call limit check"() {
|
def "Upstream is not valid - specified optimism but got ethereum"() {
|
||||||
setup:
|
setup:
|
||||||
def options = UpstreamsConfig.PartialOptions.getDefaults().buildOptions()
|
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||||
def up = TestingCommons.upstream(
|
it.validateCalllimit = false
|
||||||
new ApiReaderMock().tap {
|
}.buildOptions()
|
||||||
answerOnce("eth_call", [new TransactionCallJson(
|
def up = Mock(EthereumLikeRpcUpstream) {
|
||||||
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
2 * getIngressReader() >> Mock(Reader) {
|
||||||
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
||||||
), "latest"], "0x00000000000000000000")
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
def validator = new EthereumUpstreamValidator(up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
def validator = new EthereumUpstreamValidator(OPTIMISM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
def act = validator.validateUpstreamSettings()
|
||||||
then:
|
then:
|
||||||
act == OK
|
!act
|
||||||
when:
|
|
||||||
def act2 = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
|
||||||
then:
|
|
||||||
act2 == OK
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def "Upstream not available if error returned on call limit check"() {
|
def "Upstream is valid if all setting are valid"() {
|
||||||
setup:
|
setup:
|
||||||
def options = UpstreamsConfig.PartialOptions.getDefaults().buildOptions()
|
def options = UpstreamsConfig.PartialOptions.getDefaults().buildOptions()
|
||||||
def up = TestingCommons.upstream(
|
def up = Mock(EthereumLikeRpcUpstream) {
|
||||||
new ApiReaderMock().tap {
|
3 * getIngressReader() >> Mock(Reader) {
|
||||||
answer("eth_call", [new TransactionCallJson(
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse('"0x1"'.getBytes(), null))
|
||||||
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse('"1"'.getBytes(), null))
|
||||||
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
||||||
), "latest"], new RpcResponseError(RpcResponseError.CODE_INVALID_REQUEST, "Too long"))
|
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||||
}
|
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||||
)
|
), "latest"])) >> Mono.just(new JsonRpcResponse("0x00000000000000000000".getBytes(), null))
|
||||||
def validator = new EthereumUpstreamValidator(up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
}
|
||||||
|
}
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
def act = validator.validateUpstreamSettings()
|
||||||
then:
|
then:
|
||||||
act == UNAVAILABLE
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
def "Upstream is not valid if there are errors"() {
|
||||||
|
setup:
|
||||||
|
def options = UpstreamsConfig.PartialOptions.getDefaults().buildOptions()
|
||||||
|
def up = Mock(EthereumLikeRpcUpstream) {
|
||||||
|
3 * getIngressReader() >> Mock(Reader) {
|
||||||
|
1 * read(new JsonRpcRequest("eth_chainId", emptyList())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
||||||
|
1 * read(new JsonRpcRequest("net_version", emptyList())) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
||||||
|
1 * read(new JsonRpcRequest("eth_call", [new TransactionCallJson(
|
||||||
|
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||||
|
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||||
|
), "latest"])) >> Mono.just(new JsonRpcResponse(null, new JsonRpcError(1, "Too long")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||||
|
|
||||||
when:
|
when:
|
||||||
def act2 = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
def act = validator.validateUpstreamSettings()
|
||||||
then:
|
then:
|
||||||
act2 == UNAVAILABLE
|
!act
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ class IntegrationTest {
|
|||||||
id = "ganache"
|
id = "ganache"
|
||||||
nodeId = 1
|
nodeId = 1
|
||||||
chain = "ethereum"
|
chain = "ethereum"
|
||||||
|
options = UpstreamsConfig.PartialOptions()
|
||||||
|
.apply {
|
||||||
|
validateChain = false
|
||||||
|
}
|
||||||
connection = UpstreamsConfig.EthereumPosConnection().apply {
|
connection = UpstreamsConfig.EthereumPosConnection().apply {
|
||||||
execution = UpstreamsConfig.EthereumConnection().apply {
|
execution = UpstreamsConfig.EthereumConnection().apply {
|
||||||
rpc = UpstreamsConfig.HttpEndpoint(
|
rpc = UpstreamsConfig.HttpEndpoint(
|
||||||
|
|||||||
Reference in New Issue
Block a user