solution: connects to remote dshackle
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
32
src/test/resources/log4j2.xml
Normal file
32
src/test/resources/log4j2.xml
Normal 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>
|
||||
Reference in New Issue
Block a user