From 5e6b586cf8c21803697d91a13329f0f7be748c15 Mon Sep 17 00:00:00 2001 From: Igor Artamonov Date: Sat, 31 Aug 2019 23:31:58 -0400 Subject: [PATCH] problem: doesn't retry on upstream error --- .../io/emeraldpay/dshackle/rpc/NativeCall.kt | 1 + .../dshackle/rpc/NativeCallSpec.groovy | 29 +++++++++++++++++++ .../dshackle/test/EthereumApiMock.groovy | 11 +++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 73ad3924..bfb9707f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -134,6 +134,7 @@ class NativeCall( .flatMap { api -> api.execute(ctx.id, ctx.payload.method, ctx.payload.params).map { Tuples.of(it, api.upstream!!) } } + .retry(3) .reduce(ctx.callQuorum, {res, a -> if (res.record(a.t1, a.t2)) { repeatControl.onComplete() diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 3c97dd0e..eed31de6 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -32,6 +32,7 @@ import reactor.test.StepVerifier import spock.lang.Specification import java.time.Duration +import java.util.concurrent.TimeoutException class NativeCallSpec extends Specification { @@ -300,4 +301,32 @@ class NativeCallSpec extends Specification { 1 * cacheMock.execute(10, "eth_test", []) >> Mono.just('{"result": "foo"}'.bytes) new String(act.block().payload) == '{"result": "foo"}' } + + def "Retries on error"() { + setup: + def quorum = Spy(new AlwaysQuorum()) + + def upstreams = Stub(Upstreams) + RpcClient rpcClient = Stub(RpcClient) + def apiMock = TestingCommons.api(rpcClient) + apiMock.upstream = Stub(Upstream) + + apiMock.answer("eth_test", [], null, 1, new TimeoutException("test 1")) + apiMock.answer("eth_test", [], null, 1, new TimeoutException("test 2")) + apiMock.answerOnce("eth_test", [], "bar") + + def nativeCall = new NativeCall(upstreams, TestingCommons.objectMapper()) + def call = new NativeCall.CallContext(1, TestingCommons.aggregatedUpstream(apiMock), + Selector.empty, quorum, + new NativeCall.ParsedCallDetails("eth_test", [])) + + + when: + def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(2)) + def act = objectMapper.readValue(resp.payload, Map) + then: + act == [jsonrpc:"2.0", id:1, result: "bar"] + 1 * quorum.record(_, _) + 1 * quorum.getResult() + } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiMock.groovy index 5ca6ca00..ed4688b0 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumApiMock.groovy @@ -45,8 +45,9 @@ class EthereumApiMock extends DirectEthereumApi { return answer(method, params, result, 1) } - EthereumApiMock answer(@NotNull String method, List params, Object result, Integer limit = null) { - predefined << new PredefinedResponse(method: method, params: params, result: result, limit: limit) + EthereumApiMock answer(@NotNull String method, List params, Object result, + Integer limit = null, Throwable exception = null) { + predefined << new PredefinedResponse(method: method, params: params, result: result, limit: limit, exception: exception) return this } @@ -55,6 +56,11 @@ class EthereumApiMock extends DirectEthereumApi { def predefined = predefined.find { it.isSame(id, method, params) } ResponseJson json = new ResponseJson(id: id) if (predefined != null) { + if (predefined.exception != null) { + predefined.onCalled() + predefined.print() + throw predefined.exception + } json.result = predefined.result } else { log.error("Method ${method} with ${params} is not mocked") @@ -84,6 +90,7 @@ class EthereumApiMock extends DirectEthereumApi { List params Object result Integer limit + Throwable exception boolean isSame(int id, String method, List params) { if (limit != null) {