solution: setup custom upstream methods

This commit is contained in:
Igor Artamonov
2019-08-25 20:50:16 -04:00
parent e5c9124705
commit bc83b1b120
33 changed files with 678 additions and 92 deletions

View File

@@ -174,4 +174,24 @@ class UpstreamsConfigReaderSpec extends Specification {
}
}
}
def "Parse config with methods"() {
setup:
def config = this.class.getClassLoader().getResourceAsStream("upstreams-methods.yaml")
when:
def act = reader.read(config)
then:
act != null
with(act.upstreams.get(0)) {
methods != null
with(methods) {
enabled.size() == 1
enabled.first().name == "parity_trace"
disabled.size() == 2
disabled.toList()[0].name == "eth_getBlockByNumber"
disabled.toList()[1].name == "admin_shutdown"
}
}
}
}

View File

@@ -15,6 +15,10 @@
*/
package io.emeraldpay.dshackle.test
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.upstream.CallMethods
import io.emeraldpay.dshackle.upstream.NodeDetailsList
import io.emeraldpay.dshackle.upstream.QuorumBasedMethods
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
@@ -29,7 +33,13 @@ class EthereumUpstreamMock extends EthereumUpstream {
EthereumHeadMock ethereumHeadMock = new EthereumHeadMock()
EthereumUpstreamMock(@NotNull Chain chain, @NotNull DirectEthereumApi api) {
super(chain, api)
this(chain, api, new QuorumBasedMethods(TestingCommons.objectMapper(), chain))
}
EthereumUpstreamMock(@NotNull Chain chain, @NotNull DirectEthereumApi api, CallMethods methods) {
super(chain, api, null,
UpstreamsConfig.Options.getDefaults(), new NodeDetailsList.NodeDetails(1, new UpstreamsConfig.Labels()),
methods)
setLag(0)
setStatus(UpstreamAvailability.OK)
}

View File

@@ -20,9 +20,12 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import io.emeraldpay.dshackle.upstream.AggregatedUpstream
import io.emeraldpay.dshackle.upstream.CallMethods
import io.emeraldpay.dshackle.upstream.ChainUpstreams
import io.emeraldpay.dshackle.upstream.DirectCallMethods
import io.emeraldpay.dshackle.upstream.QuorumBasedMethods
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.JacksonRpcConverter
import io.infinitape.etherjar.rpc.RpcClient
@@ -57,6 +60,10 @@ class TestingCommons {
}
static AggregatedUpstream aggregatedUpstream(DirectEthereumApi api) {
return new ChainUpstreams(Chain.ETHEREUM, [upstream(api)], new DirectCallMethods(), objectMapper())
return aggregatedUpstream(upstream(api))
}
static AggregatedUpstream aggregatedUpstream(EthereumUpstream up) {
return new ChainUpstreams(Chain.ETHEREUM, [up], objectMapper())
}
}

View File

@@ -41,7 +41,7 @@ class UpstreamsMock implements Upstreams {
@Override
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
if (!upstreams.containsKey(chain)) {
upstreams[chain] = new ChainUpstreams(chain, [up], targetFor(chain), TestingCommons.objectMapper())
upstreams[chain] = new ChainUpstreams(chain, [up], TestingCommons.objectMapper())
} else {
upstreams[chain].addUpstream(up)
}
@@ -64,7 +64,7 @@ class UpstreamsMock implements Upstreams {
}
@Override
QuorumBasedMethods targetFor(@NotNull Chain chain) {
QuorumBasedMethods getDefaultMethods(@NotNull Chain chain) {
if (target[chain] == null) {
QuorumBasedMethods targets = new QuorumBasedMethods(TestingCommons.objectMapper(), chain)
target[chain] = targets

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2019 ETCDEV GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
import spock.lang.Specification
class AggregatedCallMethodsSpec extends Specification {
def "Returns quorum from delegate that owns it"() {
setup:
def quorum = new AlwaysQuorum()
def delegate1 = Mock(CallMethods) {
_ * getSupportedMethods() >> ["eth_no_test", "foo_bar"]
1 * isAllowed("eth_test") >> false
}
def delegate2 = Mock(CallMethods) {
_ * getSupportedMethods() >> ["eth_test", "foo_bar"]
1 * isAllowed("eth_test") >> true
1 * getQuorumFor("eth_test") >> quorum
}
def aggregate = new AggregatedCallMethods([delegate1, delegate2])
when:
def act = aggregate.getQuorumFor("eth_test")
then:
act == quorum
}
def "Allowed if any allowed"() {
setup:
def delegate1 = new DirectCallMethods(["eth_no_test", "foo_bar"] as Set)
def delegate2 = new DirectCallMethods(["eth_test", "foo_bar"] as Set)
def aggregate = new AggregatedCallMethods([delegate1, delegate2])
when:
def act = aggregate.isAllowed("eth_test")
then:
act
when:
act = aggregate.isAllowed("eth_no_test")
then:
act
when:
act = aggregate.isAllowed("foo_bar")
then:
act
when:
act = aggregate.isAllowed("nothing")
then:
!act
}
def "Supported has all methods"() {
setup:
def delegate1 = new DirectCallMethods(["eth_no_test", "foo_bar"] as Set)
def delegate2 = new DirectCallMethods(["eth_test", "foo_bar"] as Set)
def aggregate = new AggregatedCallMethods([delegate1, delegate2])
when:
def act = aggregate.getSupportedMethods()
then:
act.sort() == ["eth_test", "eth_no_test", "foo_bar"].sort()
}
def "Hardcoded if any hardcoded"() {
setup:
def delegate1 = Mock(CallMethods) {
_ * getSupportedMethods() >> ["eth_no_test", "foo_bar"]
1 * isAllowed("eth_test") >> false
1 * isAllowed("eth_no_test") >> true
1 * isHardcoded("eth_no_test") >> false
}
def delegate2 = Mock(CallMethods) {
_ * getSupportedMethods() >> ["eth_test", "foo_bar"]
1 * isAllowed("eth_test") >> true
1 * isAllowed("eth_no_test") >> false
1 * isHardcoded("eth_test") >> true
}
def aggregate = new AggregatedCallMethods([delegate1, delegate2])
when:
def act = aggregate.isHardcoded("eth_test")
then:
act
when:
act = aggregate.isHardcoded("eth_no_test")
then:
!act
}
def "Execute hardcoded on delegate that owns it"() {
setup:
def delegate1 = Mock(CallMethods) {
_ * getSupportedMethods() >> ["eth_no_test", "foo_bar"]
1 * isAllowed("eth_test") >> false
}
def delegate2 = Mock(CallMethods) {
_ * getSupportedMethods() >> ["eth_test", "foo_bar"]
1 * isAllowed("eth_test") >> true
1 * isHardcoded("eth_test") >> true
1 * executeHardcoded("eth_test") >> "hello"
}
def aggregate = new AggregatedCallMethods([delegate1, delegate2])
when:
def act = aggregate.executeHardcoded("eth_test")
then:
act == "hello"
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2019 ETCDEV GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.dshackle.quorum.AlwaysQuorum
import spock.lang.Specification
class ManagedCallMethodsSpec extends Specification {
def "Gets quorum for enabled method"() {
setup:
def managed = new ManagedCallMethods(
new DirectCallMethods(),
["eth_test"] as Set,
[] as Set
)
when:
def act = managed.getQuorumFor("eth_test")
then:
act instanceof AlwaysQuorum
}
def "Allowed contacts all enabled + delegate"() {
setup:
def managed = new ManagedCallMethods(
new DirectCallMethods(["eth_test2"] as Set),
["eth_test"] as Set,
[] as Set
)
when:
def act = managed.getSupportedMethods()
then:
act.sort() == ["eth_test", "eth_test2"].sort()
}
def "Disabled removed from delegate"() {
setup:
def managed = new ManagedCallMethods(
new DirectCallMethods(["eth_test2", "foo_bar"] as Set),
["eth_test"] as Set,
["foo_bar"] as Set
)
when:
def act = managed.getSupportedMethods()
then:
act.sort() == ["eth_test", "eth_test2"].sort()
}
}

View File

@@ -35,7 +35,7 @@ class SelectorSpec extends Specification {
when:
def act = Selector.convertToMatcher(null)
then:
act.class == Selector.EmptyMatcher
act.class == Selector.AnyLabelMatcher
}
def "Convert LABEL match"() {

View File

@@ -20,8 +20,10 @@ import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
import io.emeraldpay.dshackle.rpc.NativeCall
import io.emeraldpay.dshackle.test.EthereumApiMock
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
import io.emeraldpay.dshackle.test.MockServer
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.DirectCallMethods
import io.emeraldpay.dshackle.upstream.QuorumBasedMethods
import io.emeraldpay.dshackle.upstream.Upstreams
import io.emeraldpay.dshackle.upstream.grpc.EthereumGrpcTransport
@@ -45,7 +47,9 @@ class EthereumGrpcTransportSpec extends Specification {
def callData = [:]
def otherSideUpstreams = Mock(Upstreams)
def otherSideAggr = TestingCommons.aggregatedUpstream(otherSideApi)
def otherSideAggr = TestingCommons.aggregatedUpstream(
new EthereumUpstreamMock(Chain.ETHEREUM, otherSideApi, new DirectCallMethods(["eth_test"]))
)
def otherSideNativeCall = new NativeCall(otherSideUpstreams, objectMapper)
otherSideApi.upstream = otherSideAggr
@@ -88,7 +92,9 @@ class EthereumGrpcTransportSpec extends Specification {
def callData = [:]
def otherSideUpstreams = Mock(Upstreams)
def otherSideAggr = TestingCommons.aggregatedUpstream(otherSideApi)
def otherSideAggr = TestingCommons.aggregatedUpstream(
new EthereumUpstreamMock(Chain.ETHEREUM, otherSideApi, new DirectCallMethods(["eth_test", "eth_test2"]))
)
def otherSideNativeCall = new NativeCall(otherSideUpstreams, objectMapper)
otherSideApi.upstream = otherSideAggr

View File

@@ -39,7 +39,6 @@ class GrpcUpstreamSpec extends Specification {
MockServer mockServer = new MockServer()
ObjectMapper objectMapper = TestingCommons.objectMapper()
def ethereumTargets = new QuorumBasedMethods(objectMapper, Chain.ETHEREUM)
def "Subscribe to head"() {
setup:
@@ -71,8 +70,11 @@ class GrpcUpstreamSpec extends Specification {
)
}
})
def upstream = new GrpcUpstream(chain, client, objectMapper, ethereumTargets)
def upstream = new GrpcUpstream(chain, client, objectMapper)
upstream.setLag(0)
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
.addAllSupportedMethods(["eth_getBlockByHash"])
.build())
when:
upstream.start()
def h = upstream.head.getFlux().next().block(Duration.ofSeconds(1))
@@ -127,8 +129,11 @@ class GrpcUpstreamSpec extends Specification {
finished.complete(true)
}
})
def upstream = new GrpcUpstream(chain, client, objectMapper, ethereumTargets)
def upstream = new GrpcUpstream(chain, client, objectMapper)
upstream.setLag(0)
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
.addAllSupportedMethods(["eth_getBlockByHash"])
.build())
when:
upstream.start()
finished.get()
@@ -184,8 +189,11 @@ class GrpcUpstreamSpec extends Specification {
finished.complete(true)
}
})
def upstream = new GrpcUpstream(chain, client, objectMapper, ethereumTargets)
def upstream = new GrpcUpstream(chain, client, objectMapper)
upstream.setLag(0)
upstream.init(BlockchainOuterClass.DescribeChain.newBuilder()
.addAllSupportedMethods(["eth_getBlockByHash"])
.build())
when:
upstream.start()
finished.get()

View File

@@ -0,0 +1,26 @@
version: v1
defaultOptions:
- chains:
- ethereum
options:
min-peers: 3
upstreams:
- id: local
chain: ethereum
options:
min-peers: 7
methods:
enabled:
- name: "parity_trace"
disabled:
- name: "eth_getBlockByNumber"
- name: "admin_shutdown"
connection:
ethereum:
rpc:
url: "http://localhost:8545"
ws:
url: "ws://localhost:8546"
origin: "http://localhost"