diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 9e281b38..29e690f5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -284,11 +284,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)) - } else { - Mono.just(CallResult.fail(ctx.id, ctx.nonce, it)) - } + Mono.just(CallResult.fail(ctx.id, ctx.nonce, it)) } } @@ -310,21 +306,14 @@ open class NativeCall( CallResult(ctx.id, ctx.nonce, bytes, null, it.signature, ctx.upstream.getId()) } .onErrorResume { t -> - val failure = when (t) { - is CallFailure -> CallResult.fail(t.id, ctx.nonce, t.reason) - is JsonRpcException -> CallResult.fail(ctx.id, ctx.nonce, t.error.code, t.error.message) - else -> CallResult.fail(ctx.id, ctx.nonce, t) - } - Mono.just(failure) + Mono.just(CallResult.fail(ctx.id, ctx.nonce, t)) } .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) ).also { countFailure(attempts, ctx) } @@ -484,7 +473,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) + } + } } } } @@ -503,8 +500,8 @@ open class NativeCall( return CallResult(id, nonce, result, null, signature, upstreamId) } - fun fail(id: Int, nonce: Long?, errorCore: Int, errorMessage: String): CallResult { - return CallResult(id, nonce, null, CallError(errorCore, errorMessage, null), null, null) + fun fail(id: Int, nonce: Long?, error: CallError): CallResult { + return CallResult(id, nonce, null, error, null, null) } fun fail(id: Int, nonce: Long?, error: Throwable): CallResult { diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 98e4e585..e66daa89 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 @@ -156,6 +158,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()