From 6da5c65b03f92a1eba637f628a51ffaded5f6a54 Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Wed, 7 Dec 2022 18:26:01 +0400 Subject: [PATCH] problem: looses upstream error details --- .../io/emeraldpay/dshackle/rpc/NativeCall.kt | 33 +++++++++---------- .../dshackle/rpc/NativeCallSpec.groovy | 31 +++++++++++++++++ 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 99aeda3c..31345995 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -290,11 +290,7 @@ open class NativeCall( Mono.just(ctx).flatMap(this::executeOnRemote) ) .onErrorResume { - if (it is CallFailure) { - Mono.just(CallResult.fail(it.id, ctx.nonce, it.reason, ctx)) - } else { - Mono.just(CallResult.fail(ctx.id, ctx.nonce, it, ctx)) - } + Mono.just(CallResult.fail(ctx.id, ctx.nonce, it, ctx)) } } @@ -318,21 +314,14 @@ open class NativeCall( CallResult.ok(ctx.id, ctx.nonce, bytes, it.signature, ctx.upstream.getId(), ctx) } .onErrorResume { t -> - val failure = when (t) { - is CallFailure -> CallResult.fail(t.id, ctx.nonce, t.reason, ctx) - is JsonRpcException -> CallResult.fail(ctx.id, ctx.nonce, t.error.code, t.error.message, ctx) - else -> CallResult.fail(ctx.id, ctx.nonce, t, ctx) - } - Mono.just(failure) + Mono.just(CallResult.fail(ctx.id, ctx.nonce, t, ctx)) } .switchIfEmpty( Mono.fromSupplier { counter.get().let { attempts -> CallResult.fail( - ctx.id, - ctx.nonce, - 1, - errorMessage(attempts, ctx.payload.method), + ctx.id, ctx.nonce, + CallError(1, "No response or no available upstream for ${ctx.payload.method}", null), ctx ).also { countFailure(attempts, ctx) @@ -498,7 +487,15 @@ open class NativeCall( is JsonRpcException -> CallError(t.id.asNumber().toInt(), t.error.message, t.error) is RpcException -> CallError(t.code, t.rpcMessage, null) is CallFailure -> CallError(t.id, t.reason.message ?: "Upstream Error", null) - else -> CallError(1, t.message ?: "Upstream Error", null) + else -> { + // May only happen if it's an unhandled exception. + // In this case try to find a meaningless details in the stack. Most important reason for doing that is to find an ID of the request + if (t.cause != null) { + from(t.cause!!) + } else { + CallError(1, t.message ?: "Upstream Error", null) + } + } } } } @@ -518,8 +515,8 @@ open class NativeCall( return CallResult(id, nonce, result, null, signature, upstreamId, ctx) } - fun fail(id: Int, nonce: Long?, errorCore: Int, errorMessage: String, ctx: ValidCallContext?): CallResult { - return CallResult(id, nonce, null, CallError(errorCore, errorMessage, null), null, null, ctx) + fun fail(id: Int, nonce: Long?, error: CallError, ctx: ValidCallContext?): CallResult { + return CallResult(id, nonce, null, error, null, null, ctx) } fun fail(id: Int, nonce: Long?, error: Throwable, ctx: ValidCallContext?): CallResult { diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 38dd350c..164ee633 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -34,6 +34,8 @@ import io.emeraldpay.dshackle.upstream.Selector import io.emeraldpay.dshackle.upstream.MultistreamHolder import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcError +import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcException import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse import io.emeraldpay.dshackle.upstream.signature.ResponseSigner @@ -158,6 +160,35 @@ class NativeCallSpec extends Specification { .verify(Duration.ofSeconds(1)) } + def "Returns error details from remote"() { + setup: + def quorum = new AlwaysQuorum() + + def nativeCall = nativeCall() + nativeCall.quorumReaderFactory = Mock(QuorumReaderFactory) { + 1 * create(_, _, _) >> Mock(Reader) { + 1 * read(new JsonRpcRequest("eth_test", [], 10)) >> Mono.error( + new JsonRpcException(JsonRpcResponse.Id.from(12), new JsonRpcError(-32123, "Foo Bar", "Foo Bar Baz")) + ) + } + } + def call = new NativeCall.ValidCallContext(12, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum, + new NativeCall.ParsedCallDetails("eth_test", [])) + + when: + def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1)) + then: + resp.isError() + with(resp.getError()) { + message == "Foo Bar" + upstreamError != null + with (upstreamError) { + code == -32123 + details == "Foo Bar Baz" + } + } + } + def "Packs call exception into response with id"() { setup: def nativeCall = nativeCall()