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