From df8586258f2ea93eaadbce778c729e0a240d29d3 Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Tue, 6 Jun 2023 17:08:57 +0400 Subject: [PATCH] support data field in error response --- emerald-grpc | 2 +- .../emeraldpay/dshackle/proxy/BaseHandler.kt | 2 +- .../io/emeraldpay/dshackle/rpc/NativeCall.kt | 35 ++++++++++++++----- .../upstream/rpcclient/JsonRpcError.kt | 24 +------------ .../dshackle/proxy/WriteRpcJsonSpec.groovy | 4 +-- 5 files changed, 32 insertions(+), 35 deletions(-) diff --git a/emerald-grpc b/emerald-grpc index 45b1c29d..ce401bcd 160000 --- a/emerald-grpc +++ b/emerald-grpc @@ -1 +1 @@ -Subproject commit 45b1c29d5194352d372a577c25acfcb7368811ac +Subproject commit ce401bcd9097b89e5b43916e3b6d9032bf71ea02 diff --git a/src/main/kotlin/io/emeraldpay/dshackle/proxy/BaseHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/proxy/BaseHandler.kt index 1bd48e74..85fba7b7 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/proxy/BaseHandler.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/proxy/BaseHandler.kt @@ -116,7 +116,7 @@ abstract class BaseHandler( // If Proxy is configured to preserve original order it means that a client expect responses at exact same position // as requests even if a request completely failed for a some reason. It's very unlikely situation, but still possible // At this case, if we found a gap in responses, we put a default response with an error - ?: NativeCall.CallResult(id, null, null, NativeCall.CallError(id, "No response", null), null, null, null) + ?: NativeCall.CallResult(id, null, null, NativeCall.CallError(id, "No response", null, null), null, null, null) } } .flatMapMany { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index cbc46e09..a32d0923 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -203,7 +203,12 @@ open class NativeCall( .setId(it.id) if (it.isError()) { it.error?.let { error -> - result.setErrorMessage(error.message).setErrorCode(error.id) + result.setErrorMessage(error.message) + .setErrorCode(error.id) + + error.data?.let { data -> + result.setErrorData(data) + } } } else { result.payload = ByteString.copyFrom(it.result) @@ -301,7 +306,8 @@ open class NativeCall( CallError( requestItem.id, errorMessage, - JsonRpcError(RpcResponseError.CODE_METHOD_NOT_EXIST, errorMessage) + JsonRpcError(RpcResponseError.CODE_METHOD_NOT_EXIST, errorMessage), + null ), requestId, requestCount @@ -409,7 +415,7 @@ open class NativeCall( counter.get().let { attempts -> CallResult.fail( ctx.id, ctx.nonce, - CallError(1, "No response or no available upstream for ${ctx.payload.method}", null), + CallError(1, "No response or no available upstream for ${ctx.payload.method}", null, null), ctx ).also { countFailure(attempts, ctx) @@ -592,20 +598,33 @@ open class NativeCall( open class CallFailure(val id: Int, val reason: Throwable) : Exception("Failed to call $id: ${reason.message}") - open class CallError(val id: Int, val message: String, val upstreamError: JsonRpcError?) { + open class CallError(val id: Int, val message: String, val upstreamError: JsonRpcError?, val data: String?) { + companion object { + + private val log = LoggerFactory.getLogger(CallError::class.java) + private fun getDataAsSting(details: Any?): String? { + return when (details) { + is String -> details + null -> null + else -> { + log.debug("Unsupported error details: {}", details) + null + } + } + } fun from(t: Throwable): CallError { return when (t) { - 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) + is JsonRpcException -> CallError(t.id.asNumber().toInt(), t.error.message, t.error, getDataAsSting(t.error.details)) + is RpcException -> CallError(t.code, t.rpcMessage, null, getDataAsSting(t.details)) + is CallFailure -> CallError(t.id, t.reason.message ?: "Upstream Error", null, 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) + CallError(1, t.message ?: "Upstream Error", null, null) } } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt index 0c5bb6bd..fe51daab 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/rpcclient/JsonRpcError.kt @@ -17,7 +17,7 @@ package io.emeraldpay.dshackle.upstream.rpcclient import io.emeraldpay.etherjar.rpc.RpcException -class JsonRpcError(val code: Int, val message: String, val details: Any?) { +data class JsonRpcError(val code: Int, val message: String, val details: Any?) { constructor(code: Int, message: String) : this(code, message, null) @@ -33,26 +33,4 @@ class JsonRpcError(val code: Int, val message: String, val details: Any?) { fun asException(id: JsonRpcResponse.Id?): JsonRpcException { return JsonRpcException(id ?: JsonRpcResponse.NumberId(-1), this, false) } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is JsonRpcError) return false - - if (code != other.code) return false - if (message != other.message) return false - if (details != other.details) return false - - return true - } - - override fun hashCode(): Int { - var result = code - result = 31 * result + message.hashCode() - result = 31 * result + (details?.hashCode() ?: 0) - return result - } - - override fun toString(): String { - return "JsonRpcError(code=$code, message='$message', details=$details)" - } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/proxy/WriteRpcJsonSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/proxy/WriteRpcJsonSpec.groovy index d56efb99..2214e68b 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/proxy/WriteRpcJsonSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/proxy/WriteRpcJsonSpec.groovy @@ -98,7 +98,7 @@ class WriteRpcJsonSpec extends Specification { def call = new ProxyCall(ProxyCall.RpcType.SINGLE) call.ids[1] = 1 def data = [ - new NativeCall.CallResult(1, null, null, new NativeCall.CallError(1, "Internal Error", null), null, null, null) + new NativeCall.CallResult(1, null, null, new NativeCall.CallError(1, "Internal Error", null, null), null, null, null) ] when: def act = writer.toJson(call, data[0]) @@ -127,7 +127,7 @@ class WriteRpcJsonSpec extends Specification { call.ids[3] = 15 def data = [ new NativeCall.CallResult(1, null, '"0x98dbb1"'.bytes, null, null, null, null), - new NativeCall.CallResult(2, null, null, new NativeCall.CallError(2, "oops", null), null, null, null), + new NativeCall.CallResult(2, null, null, new NativeCall.CallError(2, "oops", null, null), null, null, null), new NativeCall.CallResult(3, null, '{"hash": "0x2484f459dc"}'.bytes, null, null, null, null), ] when: