Add validation of chain setting (#258)
This commit is contained in:
@@ -37,7 +37,8 @@ open class UpstreamsConfig {
|
||||
val validatePeers: Boolean,
|
||||
val minPeers: Int,
|
||||
val validateSyncing: Boolean,
|
||||
val validateCallLimit: Boolean
|
||||
val validateCallLimit: Boolean,
|
||||
val validateChain: Boolean
|
||||
)
|
||||
|
||||
open class PartialOptions {
|
||||
@@ -61,6 +62,7 @@ open class UpstreamsConfig {
|
||||
field = value
|
||||
}
|
||||
var validateSyncing: Boolean? = null
|
||||
var validateChain: Boolean? = null
|
||||
|
||||
fun merge(overwrites: PartialOptions?): PartialOptions {
|
||||
if (overwrites == null) {
|
||||
@@ -75,6 +77,7 @@ open class UpstreamsConfig {
|
||||
copy.validateSyncing = firstNonNull(overwrites.validateSyncing, this.validateSyncing)
|
||||
copy.validateCalllimit = firstNonNull(overwrites.validateCalllimit, this.validateCalllimit)
|
||||
copy.timeout = firstNonNull(overwrites.timeout, this.timeout)
|
||||
copy.validateChain = firstNonNull(overwrites.validateChain, this.validateChain)
|
||||
return copy
|
||||
}
|
||||
|
||||
@@ -87,7 +90,8 @@ open class UpstreamsConfig {
|
||||
firstNonNull(this.validatePeers, true)!!,
|
||||
firstNonNull(this.minPeers, 1)!!,
|
||||
firstNonNull(this.validateSyncing, true)!!,
|
||||
firstNonNull(this.validateCalllimit, true)!!
|
||||
firstNonNull(this.validateCalllimit, true)!!,
|
||||
firstNonNull(this.validateChain, true)!!
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -356,6 +356,9 @@ class UpstreamsConfigReader(
|
||||
getValueAsBool(values, "validate-call-limit")?.let {
|
||||
options.validateCalllimit = it
|
||||
}
|
||||
getValueAsBool(values, "validate-chain")?.let {
|
||||
options.validateChain = it
|
||||
}
|
||||
getValueAsInt(values, "min-peers")?.let {
|
||||
options.minPeers = it
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ open class ConfiguredUpstreams(
|
||||
override fun run(args: ApplicationArguments) {
|
||||
log.debug("Starting upstreams")
|
||||
val defaultOptions = buildDefaultOptions(config)
|
||||
config.upstreams.forEach { up ->
|
||||
config.upstreams.parallelStream().forEach { up ->
|
||||
if (!up.isEnabled) {
|
||||
log.debug("Upstream ${up.id} is disabled")
|
||||
return@forEach
|
||||
@@ -239,6 +239,7 @@ open class ConfiguredUpstreams(
|
||||
true
|
||||
)
|
||||
upstream.start()
|
||||
if (!upstream.isRunning) return null
|
||||
return upstream
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,44 @@ class DefaultEthereumMethods(
|
||||
"debug_traceCallMany",
|
||||
"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(
|
||||
@@ -243,48 +281,12 @@ class DefaultEthereumMethods(
|
||||
|
||||
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 {
|
||||
// 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.
|
||||
val json = when (method) {
|
||||
"net_version" -> hardcodedData.get(chain)?.netVersion ?: throw RpcException(-32602, "Invalid chain")
|
||||
"eth_chainId" -> hardcodedData.get(chain)?.chainId ?: throw RpcException(-32602, "Invalid chain")
|
||||
"net_version" -> CHAIN_DATA.get(chain)?.netVersion ?: throw RpcException(-32602, "Invalid chain")
|
||||
"eth_chainId" -> CHAIN_DATA.get(chain)?.chainId ?: throw RpcException(-32602, "Invalid chain")
|
||||
|
||||
"net_peerCount" -> {
|
||||
"\"0x2a\""
|
||||
|
||||
@@ -45,7 +45,7 @@ open class EthereumLikeRpcUpstream(
|
||||
chainConfig: ChainsConfig.ChainConfig,
|
||||
skipEnhance: Boolean
|
||||
) : 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 labelsDetector = EthereumLabelsDetector(this.getIngressReader())
|
||||
|
||||
@@ -60,6 +60,11 @@ open class EthereumLikeRpcUpstream(
|
||||
override fun start() {
|
||||
log.info("Configured for ${chain.chainName}")
|
||||
connector.start()
|
||||
if (!validator.validateUpstreamSettings()) {
|
||||
connector.stop()
|
||||
log.warn("Upstream ${getId()} couldn't start, invalid upstream settings")
|
||||
return
|
||||
}
|
||||
if (getOptions().disableValidation) {
|
||||
log.warn("Disable validation for upstream ${this.getId()}")
|
||||
this.setLag(0)
|
||||
|
||||
@@ -17,11 +17,15 @@
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.Defaults
|
||||
import io.emeraldpay.dshackle.Global
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
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.JsonRpcResponse
|
||||
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.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import reactor.util.function.Tuple3
|
||||
import reactor.util.function.Tuple2
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
open class EthereumUpstreamValidator @JvmOverloads constructor(
|
||||
private val chain: Chain,
|
||||
private val upstream: Upstream,
|
||||
private val options: UpstreamsConfig.Options,
|
||||
private val callLimitContract: String? = null
|
||||
) {
|
||||
private var callLimitSucceed: Boolean = false
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java)
|
||||
val scheduler =
|
||||
@@ -55,55 +59,16 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
||||
open fun validate(): Mono<UpstreamAvailability> {
|
||||
return Mono.zip(
|
||||
validateSyncing(),
|
||||
validatePeers(),
|
||||
validateCallLimit()
|
||||
validatePeers()
|
||||
)
|
||||
.map(::resolve)
|
||||
.defaultIfEmpty(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 }
|
||||
return listOf(results.t1, results.t2, results.t3).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)
|
||||
return listOf(results.t1, results.t2).sortedWith(cp).last()
|
||||
}
|
||||
|
||||
fun validateSyncing(): Mono<UpstreamAvailability> {
|
||||
@@ -165,4 +130,95 @@ open class EthereumUpstreamValidator @JvmOverloads constructor(
|
||||
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()
|
||||
then:
|
||||
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) {
|
||||
super(master, followers, DistanceExtractor.@Companion::extractPowDistance,
|
||||
Schedulers.parallel(), Duration.ofNanos(1))
|
||||
Schedulers.boundedElastic(), Duration.ofNanos(1))
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,30 +15,35 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
||||
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.reader.Reader
|
||||
import io.emeraldpay.dshackle.test.ApiReaderMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
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.etherjar.domain.Address
|
||||
import io.emeraldpay.etherjar.hex.HexData
|
||||
import io.emeraldpay.etherjar.rpc.RpcResponseError
|
||||
import io.emeraldpay.etherjar.rpc.json.TransactionCallJson
|
||||
import io.emeraldpay.etherjar.domain.Address
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.util.function.Tuples
|
||||
import spock.lang.Specification
|
||||
|
||||
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 java.util.Collections.emptyList
|
||||
|
||||
class EthereumUpstreamValidatorSpec extends Specification {
|
||||
|
||||
def "Resolve to final availability"() {
|
||||
setup:
|
||||
def validator = new EthereumUpstreamValidator(Stub(EthereumLikeUpstream), UpstreamsConfig.PartialOptions.getDefaults().buildOptions())
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, Stub(EthereumLikeUpstream), UpstreamsConfig.PartialOptions.getDefaults().buildOptions())
|
||||
expect:
|
||||
validator.resolve(Tuples.of(sync, peers, call)) == exp
|
||||
where:
|
||||
@@ -60,7 +65,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
it.validateSyncing = false
|
||||
}.buildOptions()
|
||||
def up = Mock(EthereumLikeUpstream)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||
@@ -79,7 +84,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
answer("eth_syncing", [], false)
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||
@@ -104,7 +109,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
1 * head.onSyncingNode(false)
|
||||
}
|
||||
}
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||
@@ -124,7 +129,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
answer("eth_syncing", [], [startingBlock: 100, currentBlock: 50])
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
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"))
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validateSyncing().block(Duration.ofSeconds(1))
|
||||
@@ -157,7 +162,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
it.minPeers = 10
|
||||
}.buildOptions()
|
||||
def up = Mock(EthereumLikeUpstream)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||
@@ -173,7 +178,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
it.minPeers = 0
|
||||
}.buildOptions()
|
||||
def up = Mock(EthereumLikeUpstream)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||
@@ -193,7 +198,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
answer("net_peerCount", [], "0x5")
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||
@@ -212,7 +217,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
answer("net_peerCount", [], "0xa")
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||
@@ -231,7 +236,7 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
answer("net_peerCount", [], "0xff")
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
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"))
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def validator = new EthereumUpstreamValidator(ETHEREUM__MAINNET, up, options)
|
||||
|
||||
when:
|
||||
def act = validator.validatePeers().block(Duration.ofSeconds(1))
|
||||
@@ -258,65 +263,142 @@ class EthereumUpstreamValidatorSpec extends Specification {
|
||||
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:
|
||||
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||
it.validateCalllimit = false
|
||||
}.buildOptions()
|
||||
def up = Mock(EthereumLikeUpstream)
|
||||
def validator = new EthereumUpstreamValidator(up, options)
|
||||
def up = Mock(EthereumLikeRpcUpstream) {
|
||||
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:
|
||||
def act = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
||||
def act = validator.validateUpstreamSettings()
|
||||
then:
|
||||
act == OK
|
||||
0 * up.getIngressReader()
|
||||
act
|
||||
}
|
||||
|
||||
def "Upstream available if not error from call limit check"() {
|
||||
def "Upstream is not valid - specified optimism but got ethereum"() {
|
||||
setup:
|
||||
def options = UpstreamsConfig.PartialOptions.getDefaults().buildOptions()
|
||||
def up = TestingCommons.upstream(
|
||||
new ApiReaderMock().tap {
|
||||
answerOnce("eth_call", [new TransactionCallJson(
|
||||
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||
), "latest"], "0x00000000000000000000")
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||
def options = UpstreamsConfig.PartialOptions.getDefaults().tap {
|
||||
it.validateCalllimit = false
|
||||
}.buildOptions()
|
||||
def up = Mock(EthereumLikeRpcUpstream) {
|
||||
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(OPTIMISM__MAINNET, up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||
|
||||
when:
|
||||
def act = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
||||
def act = validator.validateUpstreamSettings()
|
||||
then:
|
||||
act == OK
|
||||
when:
|
||||
def act2 = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
||||
then:
|
||||
act2 == OK
|
||||
!act
|
||||
}
|
||||
|
||||
def "Upstream not available if error returned on call limit check"() {
|
||||
def "Upstream is valid if all setting are valid"() {
|
||||
setup:
|
||||
def options = UpstreamsConfig.PartialOptions.getDefaults().buildOptions()
|
||||
def up = TestingCommons.upstream(
|
||||
new ApiReaderMock().tap {
|
||||
answer("eth_call", [new TransactionCallJson(
|
||||
Address.from("0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96"),
|
||||
HexData.from("0xd8a26e3a0000000000000000000000000000000000000000000000000000000000030ce0")
|
||||
), "latest"], new RpcResponseError(RpcResponseError.CODE_INVALID_REQUEST, "Too long"))
|
||||
}
|
||||
)
|
||||
def validator = new EthereumUpstreamValidator(up, options, "0x32268860cAAc2948Ab5DdC7b20db5a420467Cf96")
|
||||
def up = Mock(EthereumLikeRpcUpstream) {
|
||||
3 * 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))
|
||||
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.validateCallLimit().block(Duration.ofSeconds(1))
|
||||
def act = validator.validateUpstreamSettings()
|
||||
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:
|
||||
def act2 = validator.validateCallLimit().block(Duration.ofSeconds(1))
|
||||
def act = validator.validateUpstreamSettings()
|
||||
then:
|
||||
act2 == UNAVAILABLE
|
||||
!act
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -95,6 +95,10 @@ class IntegrationTest {
|
||||
id = "ganache"
|
||||
nodeId = 1
|
||||
chain = "ethereum"
|
||||
options = UpstreamsConfig.PartialOptions()
|
||||
.apply {
|
||||
validateChain = false
|
||||
}
|
||||
connection = UpstreamsConfig.EthereumPosConnection().apply {
|
||||
execution = UpstreamsConfig.EthereumConnection().apply {
|
||||
rpc = UpstreamsConfig.HttpEndpoint(
|
||||
|
||||
Reference in New Issue
Block a user