Add validation of chain setting (#258)

This commit is contained in:
KirillPamPam
2023-08-01 14:02:31 +04:00
committed by GitHub
parent b0af3ab3b3
commit 9757dfd201
11 changed files with 301 additions and 139 deletions

View File

@@ -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
)
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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(