solution: use reactor based stubs for grpc server

This commit is contained in:
Igor Artamonov
2019-07-29 22:27:15 -04:00
parent 4c9343c146
commit 36e089f65f
18 changed files with 448 additions and 304 deletions

View File

@@ -1,16 +1,22 @@
package io.emeraldpay.dshackle.test
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.dshackle.upstream.EthereumApi
import io.emeraldpay.grpc.Chain
import io.grpc.stub.StreamObserver
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 org.slf4j.Logger
import org.slf4j.LoggerFactory
import reactor.core.publisher.Mono
class EthereumApiMock extends EthereumApi {
private static final Logger log = LoggerFactory.getLogger(this)
List<PredefinedResponse> predefined = []
private ObjectMapper objectMapper
@@ -31,11 +37,26 @@ class EthereumApiMock extends EthereumApi {
if (predefined != null) {
json.result = predefined.result
} else {
log.error("Method ${method} with ${params} is not mocked")
json.error = new RpcResponseError(-32601, "Method ${method} with ${params} is not mocked")
}
return Mono.just(objectMapper.writeValueAsBytes(json))
}
def nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
request.itemsList.forEach { req ->
def resp = execute(req.id, req.target, objectMapper.readerFor(List).readValue(req.payload.toByteArray()))
resp.subscribe {
def proto = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(req.id)
.setSucceed(true)
.setPayload(ByteString.copyFrom(resp.block()))
responseObserver.onNext(proto.build())
}
}
responseObserver.onCompleted()
}
class PredefinedResponse {
String method
List params

View File

@@ -10,7 +10,15 @@ class MockServer {
GrpcCleanupRule grpcCleanup = new GrpcCleanupRule()
ReactorBlockchainGrpc.ReactorBlockchainStub runServer(BlockchainGrpc.BlockchainImplBase impl){
ReactorBlockchainGrpc.ReactorBlockchainStub clientForServer(ReactorBlockchainGrpc.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)
}
ReactorBlockchainGrpc.ReactorBlockchainStub clientForServer(BlockchainGrpc.BlockchainImplBase impl){
String serverName = InProcessServerBuilder.generateName()
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(impl).build().start());

View File

@@ -1,17 +1,18 @@
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.api.proto.ReactorBlockchainGrpc
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 reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import spock.lang.Specification
class EthereumGrpcTransportSpec extends Specification {
@@ -27,11 +28,11 @@ class EthereumGrpcTransportSpec extends Specification {
def otherSideNativeCall = new NativeCall(otherSideUpstreams, objectMapper)
def otherSideApi = new EthereumApiMock(Mock(RpcClient), objectMapper, Chain.ETHEREUM)
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
def client = mockServer.clientForServer(new ReactorBlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
callData["request"] = request
otherSideNativeCall.nativeCall(request, responseObserver)
Flux<BlockchainOuterClass.NativeCallReplyItem> nativeCall(Mono<BlockchainOuterClass.NativeCallRequest> request) {
callData["request"] = request.block()
return otherSideNativeCall.nativeCall(request)
}
})
@@ -68,12 +69,13 @@ class EthereumGrpcTransportSpec extends Specification {
def otherSideNativeCall = new NativeCall(otherSideUpstreams, objectMapper)
def otherSideApi = new EthereumApiMock(Mock(RpcClient), objectMapper, Chain.ETHEREUM)
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
def client = mockServer.clientForServer(new ReactorBlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
callData["request"] = request
otherSideNativeCall.nativeCall(request, responseObserver)
Flux<BlockchainOuterClass.NativeCallReplyItem> nativeCall(Mono<BlockchainOuterClass.NativeCallRequest> request) {
callData["request"] = request.block()
return otherSideNativeCall.nativeCall(request)
}
})
EthereumGrpcTransport transport = new EthereumGrpcTransport(Chain.ETHEREUM, client, objectMapper)

View File

@@ -5,14 +5,18 @@ 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.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.domain.BlockHash
import io.infinitape.etherjar.rpc.RpcClient
import io.infinitape.etherjar.rpc.json.BlockJson
import org.apache.commons.codec.binary.Hex
import spock.lang.Specification
import java.time.Duration
import java.util.concurrent.CompletableFuture
class GrpcUpstreamSpec extends Specification {
@@ -23,24 +27,37 @@ class GrpcUpstreamSpec extends Specification {
def "Subscribe to head"() {
setup:
def callData = [:]
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
def chain = Chain.ETHEREUM
def api = new EthereumApiMock(Mock(RpcClient), objectMapper, chain)
def block1 = new BlockJson().with {
it.number = 650246
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
return it
}
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
api.nativeCall(request, responseObserver)
}
@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()
.setBlockId(block1.hash.toHex().substring(2))
.setHeight(block1.number)
.setWeight(ByteString.copyFrom(block1.totalDifficulty.toByteArray()))
.build()
)
}
})
def chain = Chain.ETHEREUM
def upstream = new GrpcUpstream(chain, client, objectMapper)
when:
upstream.connect()
def h = upstream.head.head.block()
def h = upstream.head.head.block(Duration.ofSeconds(1))
then:
callData.chain == Chain.ETHEREUM.id
upstream.status == UpstreamAvailability.OK
@@ -51,32 +68,52 @@ class GrpcUpstreamSpec extends Specification {
setup:
def callData = [:]
def finished = new CompletableFuture<Boolean>()
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
def chain = Chain.ETHEREUM
def api = new EthereumApiMock(Mock(RpcClient), objectMapper, chain)
def block1 = new BlockJson().with {
it.number = 650246
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
return it
}
def block2 = new BlockJson().with {
it.number = 650247
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
it.totalDifficulty = new BigInteger("35bbde5595de6455", 16)
return it
}
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
api.answer("eth_getBlockByHash", [block2.hash.toHex(), false], block2)
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
api.nativeCall(request, responseObserver)
}
@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")))
.setBlockId(block1.hash.toHex().substring(2))
.setHeight(block1.number)
.setWeight(ByteString.copyFrom(block1.totalDifficulty.toByteArray()))
.build()
)
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("3ec2ebf5d0ec474d0ac6bca770d8409ad750d26e119968e7919f85d5ec891521")
.setHeight(650247)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6455")))
.setBlockId(block2.hash.toHex().substring(2))
.setHeight(block2.number)
.setWeight(ByteString.copyFrom(block2.totalDifficulty.toByteArray()))
.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()
def h = upstream.head.head.block(Duration.ofSeconds(1))
then:
upstream.status == UpstreamAvailability.OK
h.hash == BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
@@ -87,32 +124,52 @@ class GrpcUpstreamSpec extends Specification {
setup:
def callData = [:]
def finished = new CompletableFuture<Boolean>()
def client = mockServer.runServer(new BlockchainGrpc.BlockchainImplBase() {
def chain = Chain.ETHEREUM
def api = new EthereumApiMock(Mock(RpcClient), objectMapper, chain)
def block1 = new BlockJson().with {
it.number = 650246
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
return it
}
def block2 = new BlockJson().with {
it.number = 650247
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
it.totalDifficulty = new BigInteger("35bbde5595de6457", 16)
return it
}
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
api.answer("eth_getBlockByHash", [block2.hash.toHex(), false], block2)
def client = mockServer.clientForServer(new BlockchainGrpc.BlockchainImplBase() {
@Override
void nativeCall(BlockchainOuterClass.NativeCallRequest request, StreamObserver<BlockchainOuterClass.NativeCallReplyItem> responseObserver) {
api.nativeCall(request, responseObserver)
}
@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")))
.setBlockId(block1.hash.toHex().substring(2))
.setHeight(block1.number)
.setWeight(ByteString.copyFrom(block1.totalDifficulty.toByteArray()))
.build()
)
responseObserver.onNext(
BlockchainOuterClass.ChainHead.newBuilder()
.setBlockId("3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
.setHeight(650247)
.setWeight(ByteString.copyFrom(Hex.decodeHex("35bbde5595de6457")))
.setBlockId(block2.hash.toHex().substring(2))
.setHeight(block2.number)
.setWeight(ByteString.copyFrom(block2.totalDifficulty.toByteArray()))
.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()
def h = upstream.head.head.block(Duration.ofSeconds(1))
then:
upstream.status == UpstreamAvailability.OK
h.hash == BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")