solution: connects to remote dshackle

This commit is contained in:
Igor Artamonov
2019-07-25 22:53:02 -04:00
parent 499a92e4d6
commit fd8c35bec2
31 changed files with 1320 additions and 312 deletions

View File

@@ -54,4 +54,31 @@ class UpstreamsConfigReaderSpec extends Specification {
}
}
def "Parse ds config"() {
setup:
def config = this.class.getClassLoader().getResourceAsStream("upstreams-ds.yaml")
when:
def act = reader.read(config)
then:
act != null
act.version == "v1"
act.upstreams.size() == 1
with(act.upstreams.get(0)) {
id == "remote"
chain == "auto"
provider == "dshackle"
endpoints.size() == 1
with(endpoints.get(0)) {
type == UpstreamsConfig.EndpointType.DSHACKLE
host == "10.2.0.15"
auth instanceof UpstreamsConfig.TlsAuth
with((UpstreamsConfig.TlsAuth)auth) {
ca == "/etc/ca.myservice.com.crt"
certificate == "/etc/client1.myservice.com.crt"
key == "/etc/client1.myservice.com.key"
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
package io.emeraldpay.dshackle.test
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.upstream.EthereumApi
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.RpcClient
import io.infinitape.etherjar.rpc.RpcResponseError
import io.infinitape.etherjar.rpc.json.ResponseJson
import org.jetbrains.annotations.NotNull
import reactor.core.publisher.Mono
class EthereumApiMock extends EthereumApi {
List<PredefinedResponse> predefined = []
private ObjectMapper objectMapper
EthereumApiMock(@NotNull RpcClient rpcClient, @NotNull ObjectMapper objectMapper, @NotNull Chain chain) {
super(rpcClient, objectMapper, chain)
this.objectMapper = objectMapper
}
EthereumApiMock answer(@NotNull String method, List<Object> params, Object result) {
predefined << new PredefinedResponse(method: method, params: params, result: result)
return this
}
@Override
Mono<byte[]> execute(int id, @NotNull String method, @NotNull List<?> params) {
def predefined = predefined.find { it.isSame(id, method, params) }
ResponseJson json = new ResponseJson<Object, Integer>(id: id)
if (predefined != null) {
json.result = predefined.result
} else {
json.error = new RpcResponseError(-32601, "Method ${method} with ${params} is not mocked")
}
return Mono.just(objectMapper.writeValueAsBytes(json))
}
class PredefinedResponse {
String method
List params
Object result
boolean isSame(int id, String method, List<?> params) {
if (method != this.method) {
return false
}
if (this.params == null) {
return true
}
return this.params == params
}
}
}

View File

@@ -0,0 +1,20 @@
package io.emeraldpay.dshackle.test
import io.emeraldpay.api.proto.BlockchainGrpc
import io.emeraldpay.api.proto.ReactorBlockchainGrpc
import io.grpc.inprocess.InProcessChannelBuilder
import io.grpc.inprocess.InProcessServerBuilder
import io.grpc.testing.GrpcCleanupRule
class MockServer {
GrpcCleanupRule grpcCleanup = new GrpcCleanupRule()
ReactorBlockchainGrpc.ReactorBlockchainStub runServer(BlockchainGrpc.BlockchainImplBase impl){
String serverName = InProcessServerBuilder.generateName()
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(impl).build().start());
def channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build())
return ReactorBlockchainGrpc.newReactorStub(channel)
}
}

View File

@@ -0,0 +1,24 @@
package io.emeraldpay.dshackle.test
import com.fasterxml.jackson.core.Version
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import java.text.SimpleDateFormat
class TestingCommons {
static ObjectMapper objectMapper() {
def module = new SimpleModule("EmeraldDShackle", new Version(1, 0, 0, null, null, null))
def objectMapper = new ObjectMapper()
objectMapper.registerModule(module)
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
objectMapper
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd\'T\'HH:mm:ss.SSS"))
.setTimeZone(TimeZone.getTimeZone("UTC"))
return objectMapper
}
}

View File

@@ -0,0 +1,112 @@
package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.api.proto.BlockchainGrpc
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.dshackle.rpc.NativeCall
import io.emeraldpay.dshackle.test.EthereumApiMock
import io.emeraldpay.dshackle.test.MockServer
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.grpc.Chain
import io.grpc.stub.StreamObserver
import io.infinitape.etherjar.rpc.Batch
import io.infinitape.etherjar.rpc.RpcCall
import io.infinitape.etherjar.rpc.RpcClient
import spock.lang.Specification
class EthereumGrpcTransportSpec extends Specification {
MockServer mockServer = new MockServer()
ObjectMapper objectMapper = TestingCommons.objectMapper()
def "Make simple call"() {
setup:
def callData = [:]
def otherSideUpstreams = Mock(Upstreams)
def otherSideAggr = Mock(AggregatedUpstreams)
def otherSideNativeCall = new NativeCall(otherSideUpstreams, objectMapper)
def otherSideApi = new EthereumApiMock(Mock(RpcClient), objectMapper, Chain.ETHEREUM)
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
callData["request"] = request
otherSideNativeCall.nativeCall(request, responseObserver)
}
})
EthereumGrpcTransport transport = new EthereumGrpcTransport(Chain.ETHEREUM, client, objectMapper)
when:
otherSideApi.answer("eth_test", [1], "bar")
def batch = new Batch()
def f = batch.add(RpcCall.create("eth_test", [1]))
def status = transport.execute(batch.items).get()
then:
1 * otherSideUpstreams.ethereumUpstream(Chain.ETHEREUM) >> otherSideAggr
1 * otherSideAggr.api >> otherSideApi
status.failed == 0
status.succeed == 1
status.total == 1
callData.request != null
with((BlockchainOuterClass.NativeCallRequest)callData.request) {
chain.number == Chain.ETHEREUM.id
itemsCount == 1
with(getItems(0)) {
target == "eth_test"
payload.toStringUtf8() == "[1]"
}
}
f.get() == "bar"
}
def "Make few calls"() {
setup:
def callData = [:]
def otherSideUpstreams = Mock(Upstreams)
def otherSideAggr = Mock(AggregatedUpstreams)
def otherSideNativeCall = new NativeCall(otherSideUpstreams, objectMapper)
def otherSideApi = new EthereumApiMock(Mock(RpcClient), objectMapper, Chain.ETHEREUM)
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
callData["request"] = request
otherSideNativeCall.nativeCall(request, responseObserver)
}
})
EthereumGrpcTransport transport = new EthereumGrpcTransport(Chain.ETHEREUM, client, objectMapper)
when:
otherSideApi.answer("eth_test", [1], "bar")
otherSideApi.answer("eth_test2", [2, "3"], "baz")
def batch = new Batch()
def f1 = batch.add(RpcCall.create("eth_test", [1]))
def f2 = batch.add(RpcCall.create("eth_test2", [2, "3"]))
def status = transport.execute(batch.items).get()
then:
1 * otherSideUpstreams.ethereumUpstream(Chain.ETHEREUM) >> otherSideAggr
1 * otherSideAggr.api >> otherSideApi
status.failed == 0
status.succeed == 2
status.total == 2
callData.request != null
with((BlockchainOuterClass.NativeCallRequest)callData.request) {
chain.number == Chain.ETHEREUM.id
itemsCount == 2
with(getItems(0)) {
target == "eth_test"
payload.toStringUtf8() == "[1]"
}
with(getItems(1)) {
target == "eth_test2"
payload.toStringUtf8() == "[2,\"3\"]"
}
}
f1.get() == "bar"
f2.get() == "baz"
}
}

View File

@@ -0,0 +1,121 @@
package io.emeraldpay.dshackle.upstream
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainGrpc
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.test.MockServer
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.grpc.Chain
import io.grpc.stub.StreamObserver
import io.infinitape.etherjar.domain.BlockHash
import org.apache.commons.codec.binary.Hex
import spock.lang.Specification
import java.util.concurrent.CompletableFuture
class GrpcUpstreamSpec extends Specification {
MockServer mockServer = new MockServer()
ObjectMapper objectMapper = TestingCommons.objectMapper()
def "Subscribe to head"() {
setup:
def callData = [:]
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
callData.chain = request.getTypeValue()
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
.setHeight(650246)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6456")))
.build()
)
}
})
def chain = Chain.ETHEREUM
def upstream = new GrpcUpstream(chain, client, objectMapper)
when:
upstream.connect()
def h = upstream.head.head.block()
then:
callData.chain == Chain.ETHEREUM.id
upstream.status == UpstreamAvailability.OK
h.hash == BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
}
def "Follows difficulty, ignores less difficult"() {
setup:
def callData = [:]
def finished = new CompletableFuture<Boolean>()
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
.setHeight(650246)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6456")))
.build()
)
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("3ec2ebf5d0ec474d0ac6bca770d8409ad750d26e119968e7919f85d5ec891521")
.setHeight(650247)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6455")))
.build()
)
finished.complete(true)
}
})
def chain = Chain.ETHEREUM
def upstream = new GrpcUpstream(chain, client, objectMapper)
when:
upstream.connect()
finished.get()
def h = upstream.head.head.block()
then:
upstream.status == UpstreamAvailability.OK
h.hash == BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
h.number == 650246
}
def "Follows difficulty"() {
setup:
def callData = [:]
def finished = new CompletableFuture<Boolean>()
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void subscribeHead(Common.Chain request, StreamObserver<BlockchainOuterClass.ChainHead> responseObserver) {
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
.setHeight(650246)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6456")))
.build()
)
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
.setHeight(650247)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6457")))
.build()
)
finished.complete(true)
}
})
def chain = Chain.ETHEREUM
def upstream = new GrpcUpstream(chain, client, objectMapper)
when:
upstream.connect()
finished.get()
def h = upstream.head.head.block()
then:
upstream.status == UpstreamAvailability.OK
h.hash == BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
h.number == 650247
}
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="dfltPattern">%d{HH:mm:ss.SSS} [%-20t] %-5level %24.24c{1} | %msg%n</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="${dfltPattern}"/>
<Filters>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
</Filters>
</Console>
<Console name="STDERR" target="SYSTEM_ERR">
<PatternLayout pattern="${dfltPattern}" />
</Console>
</Appenders>
<Loggers>
<Logger name="io.emeraldpay" level="debug" additivity="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="STDERR" level="warn"/>
</Logger>
<Root level="warn" additivity="false">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="STDERR" level="warn"/>
</Root>
</Loggers>
</Configuration>