Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error deserialization bug #227

Merged
merged 2 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/GWallet.Backend.Tests/Deserialization.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ open System
open NUnit.Framework

open GWallet.Backend
open GWallet.Backend.UtxoCoin

[<TestFixture>]
type Deserialization() =
Expand Down
11 changes: 10 additions & 1 deletion src/GWallet.Backend.Tests/StratumParsing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,13 @@ type StratumParsing() =
StratumClient.Deserialize<BlockchainScriptHashGetBalanceResult> errorResponse
|> ignore<BlockchainScriptHashGetBalanceResult>
)
Assert.That(ex.ErrorCode, Is.EqualTo(-32603))
Assert.That(ex.ErrorCode, Is.EqualTo(Some -32603))

[<Test>]
member __.``can deserialize error result with string error``() =
let ex = Assert.Throws<ElectrumServerReturningErrorInJsonResponseException> (fun () ->
StratumClient.Deserialize<BlockchainTransactionGetResult> """{"error":"bad tx_hash","id":0,"jsonrpc":"2.0"}"""
|> ignore<BlockchainTransactionGetResult>
)

Assert.That(ex.ErrorCode, Is.EqualTo None)
2 changes: 1 addition & 1 deletion src/GWallet.Backend/UtxoCoin/ElectrumClient.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module ElectrumClient =
stratumClient.ServerVersion CLIENT_NAME_SENT_TO_STRATUM_SERVER_WHEN_HELLO PROTOCOL_VERSION_SUPPORTED
with
| :? ElectrumServerReturningErrorException as ex ->
if (ex.ErrorCode = 1 && ex.Message.StartsWith "unsupported protocol version" &&
if (ex.ErrorCode = Some 1 && ex.Message.StartsWith "unsupported protocol version" &&
ex.Message.EndsWith (PROTOCOL_VERSION_SUPPORTED.ToString())) then

// FIXME: even if this ex is already handled to ignore the server, we should report to sentry as WARN
Expand Down
54 changes: 39 additions & 15 deletions src/GWallet.Backend/UtxoCoin/StratumClient.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace GWallet.Backend.UtxoCoin

open System
open System.ComponentModel

open Newtonsoft.Json

Expand Down Expand Up @@ -81,6 +82,12 @@ type ErrorResult =
Id: int;
Error: ErrorInnerResult;
}

type ErrorResultWithStringError =
{
Id: int
Error: string
}

type RpcErrorCode =
// see https://gitlab.com/nblockchain/geewallet/issues/110
Expand All @@ -98,13 +105,13 @@ type RpcErrorCode =
type public ElectrumServerReturningImproperJsonResponseException(message: string, innerEx: Exception) =
inherit ServerMisconfiguredException (message, innerEx)

type public ElectrumServerReturningErrorInJsonResponseException(message: string, code: int) =
type public ElectrumServerReturningErrorInJsonResponseException(message: string, code: Option<int>) =
inherit CommunicationUnsuccessfulException(message)

member val ErrorCode: int =
member val ErrorCode: Option<int> =
code with get

type public ElectrumServerReturningErrorException(message: string, code: int,
type public ElectrumServerReturningErrorException(message: string, code: Option<int>,
originalRequest: string, originalResponse: string) =
inherit ElectrumServerReturningErrorInJsonResponseException(message, code)

Expand All @@ -114,7 +121,7 @@ type public ElectrumServerReturningErrorException(message: string, code: int,
member val OriginalResponse: string =
originalResponse with get

type public ElectrumServerReturningInternalErrorException(message: string, code: int,
type public ElectrumServerReturningInternalErrorException(message: string, code: Option<int>,
originalRequest: string, originalResponse: string) =
inherit ElectrumServerReturningErrorException(message, code, originalRequest, originalResponse)

Expand All @@ -137,31 +144,48 @@ type StratumClient (jsonRpcClient: JsonRpcTcpClient) =
return (StratumClient.Deserialize<'R> rawResponse, rawResponse)
with
| :? ElectrumServerReturningErrorInJsonResponseException as ex ->
if ex.ErrorCode = int RpcErrorCode.InternalError then
if ex.ErrorCode = (RpcErrorCode.InternalError |> int |> Some) then
return raise(ElectrumServerReturningInternalErrorException(ex.Message, ex.ErrorCode, jsonRequest, rawResponse))
if ex.ErrorCode = int RpcErrorCode.UnknownMethod then
if ex.ErrorCode = (RpcErrorCode.UnknownMethod |> int |> Some) then
return raise <| ServerMisconfiguredException(ex.Message, ex)
if ex.ErrorCode = int RpcErrorCode.ServerBusy then
if ex.ErrorCode = (RpcErrorCode.ServerBusy |> int |> Some) then
return raise <| ServerUnavailabilityException(ex.Message, ex)
if ex.ErrorCode = int RpcErrorCode.ExcessiveResourceUsage then
if ex.ErrorCode = (RpcErrorCode.ExcessiveResourceUsage |> int |> Some) then
return raise <| ServerUnavailabilityException(ex.Message, ex)

return raise(ElectrumServerReturningErrorException(ex.Message, ex.ErrorCode, jsonRequest, rawResponse))
}

static member private DeserializeInternal<'T> (result: string): 'T =
let resultTrimmed = result.Trim()
let maybeError =

let maybeError: Choice<ErrorResult, ErrorResultWithStringError> =
let raiseDeserializationError (ex: Exception) =
raise <| Exception(SPrintF2 "Failed deserializing JSON response (to check for error) '%s' to type '%s'"
resultTrimmed typedefof<'T>.FullName, ex)
try
JsonConvert.DeserializeObject<ErrorResult>(resultTrimmed,
Marshalling.PascalCase2LowercasePlusUnderscoreConversionSettings)
|> Choice1Of2
with
| ex -> raise <| Exception(SPrintF2 "Failed deserializing JSON response (to check for error) '%s' to type '%s'"
resultTrimmed typedefof<'T>.FullName, ex)

if (not (Object.ReferenceEquals(maybeError, null))) && (not (Object.ReferenceEquals(maybeError.Error, null))) then
raise(ElectrumServerReturningErrorInJsonResponseException(maybeError.Error.Message, maybeError.Error.Code))

| :? JsonSerializationException ->
try
JsonConvert.DeserializeObject<ErrorResultWithStringError>(resultTrimmed,
knocte marked this conversation as resolved.
Show resolved Hide resolved
Marshalling.PascalCase2LowercasePlusUnderscoreConversionSettings)
|> Choice2Of2
with
| ex ->
raiseDeserializationError ex
| ex ->
raiseDeserializationError ex

match maybeError with
| Choice1Of2 errorResult when (not (Object.ReferenceEquals(errorResult, null))) && (not (Object.ReferenceEquals(errorResult.Error, null))) ->
raise <| ElectrumServerReturningErrorInJsonResponseException(errorResult.Error.Message, Some errorResult.Error.Code)
| Choice2Of2 errorResultWithStringError when (not (Object.ReferenceEquals(errorResultWithStringError, null))) && (not (String.IsNullOrWhiteSpace errorResultWithStringError.Error)) ->
raise <| ElectrumServerReturningErrorInJsonResponseException(errorResultWithStringError.Error, None)
| _ -> ()

let failedDeserMsg = SPrintF2 "Failed deserializing JSON response '%s' to type '%s'"
resultTrimmed typedefof<'T>.FullName
let deserializedValue =
Expand Down
Loading