solution: use reactor based stubs for grpc server
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user