From a012a4a1e0dfa35facf3ec3fd9bc24aa0d7d321d Mon Sep 17 00:00:00 2001 From: webwarrior Date: Mon, 12 Aug 2024 11:12:53 +0200 Subject: [PATCH 1/5] Backend: handle null response from Eth server Introduce AbnormalNullValueInJsonResponseException type and raise it when response for balance request for Ethereum returns json with "result" field value of `null`. This way server will be marked as faulty instead of crashing the application. Fixes https://github.com/nblockchain/geewallet/issues/282 --- src/GWallet.Backend/Ether/EtherExceptions.fs | 7 +++++++ src/GWallet.Backend/Ether/EtherServer.fs | 4 +++- src/GWallet.Backend/ServerManager.fs | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/GWallet.Backend/Ether/EtherExceptions.fs b/src/GWallet.Backend/Ether/EtherExceptions.fs index a08d73033..11c6c3fc1 100644 --- a/src/GWallet.Backend/Ether/EtherExceptions.fs +++ b/src/GWallet.Backend/Ether/EtherExceptions.fs @@ -95,3 +95,10 @@ type UnhandledWebException = } new (info: SerializationInfo, context: StreamingContext) = { inherit Exception (info, context) } + +/// Exception indicating that response JSON contains null value where it should not. +/// E.g. {"jsonrpc":"2.0","id":1,"result":null} +type AbnormalNullValueInJsonResponseException(message: string) = + inherit CommunicationUnsuccessfulException(message) + + static member BalanceJobErrorMessage = "Abnormal null response from balance job" diff --git a/src/GWallet.Backend/Ether/EtherServer.fs b/src/GWallet.Backend/Ether/EtherServer.fs index 827a144f1..b6ea6e1f6 100644 --- a/src/GWallet.Backend/Ether/EtherServer.fs +++ b/src/GWallet.Backend/Ether/EtherServer.fs @@ -543,7 +543,9 @@ module Server = return! Async.AwaitTask task } if Object.ReferenceEquals(balance, null) then - failwith "Weird null response from balance job" + raise <| + AbnormalNullValueInJsonResponseException + AbnormalNullValueInJsonResponseException.BalanceJobErrorMessage return UnitConversion.Convert.FromWei(balance.Value, UnitConversion.EthUnit.Ether) } GetRandomizedFuncs currency web3Func diff --git a/src/GWallet.Backend/ServerManager.fs b/src/GWallet.Backend/ServerManager.fs index dbe42a612..90f481ca8 100644 --- a/src/GWallet.Backend/ServerManager.fs +++ b/src/GWallet.Backend/ServerManager.fs @@ -128,6 +128,10 @@ module ServerManager = let web3Func (web3: Ether.SomeWeb3): Async = async { let! balance = Async.AwaitTask (web3.Eth.GetBalance.SendRequestAsync ETH_GENESISBLOCK_ADDRESS) + if isNull balance then + raise <| + Ether.AbnormalNullValueInJsonResponseException + Ether.AbnormalNullValueInJsonResponseException.BalanceJobErrorMessage return balance.Value |> decimal } From f381924394797de9084de5aa9ac75b0814ee2f1e Mon Sep 17 00:00:00 2001 From: webwarrior Date: Mon, 19 Aug 2024 11:14:58 +0200 Subject: [PATCH 2/5] Backend: handle other Eth server null responses Raise WeirdNullResponseException on all possible null responses or responses that contain null value from Ethereum servers. --- src/GWallet.Backend/Ether/EtherServer.fs | 27 +++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/GWallet.Backend/Ether/EtherServer.fs b/src/GWallet.Backend/Ether/EtherServer.fs index b6ea6e1f6..4994abc89 100644 --- a/src/GWallet.Backend/Ether/EtherServer.fs +++ b/src/GWallet.Backend/Ether/EtherServer.fs @@ -464,7 +464,10 @@ module Server = let! cancelToken = Async.CancellationToken let task = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(address, null, cancelToken) - return! Async.AwaitTask task + let! txCount = Async.AwaitTask task + if isNull txCount then + raise <| AbnormalNullValueInJsonResponseException "Abnormal null response from tx count job" + return txCount } GetRandomizedFuncs currency web3Func return! faultTolerantEtherClient.Query @@ -480,7 +483,7 @@ module Server = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync (null, cancelToken) |> Async.AwaitTask if isNull latestBlock then - failwith "latestBlock somehow is null" + raise <| AbnormalNullValueInJsonResponseException "latestBlock somehow is null" let blockToCheck = BigInteger.Subtract(latestBlock.Value, NUMBER_OF_CONFIRMATIONS_TO_CONSIDER_BALANCE_CONFIRMED) @@ -575,7 +578,7 @@ module Server = let contractHandler = web3.Eth.GetContractHandler contractAddress if isNull contractHandler then - failwith "contractHandler somehow is null" + raise <| AbnormalNullValueInJsonResponseException "contractHandler somehow is null" let! cancelToken = Async.CancellationToken cancelToken.ThrowIfCancellationRequested() @@ -637,7 +640,10 @@ module Server = let! cancelToken = Async.CancellationToken let task = contractHandler.EstimateGasAsync(transferFunctionMsg, cancelToken) - return! Async.AwaitTask task + let! fee = Async.AwaitTask task + if isNull fee then + raise <| AbnormalNullValueInJsonResponseException "Abnormal null response from transfer fee job" + return fee } GetRandomizedFuncs account.Currency web3Func return! faultTolerantEtherClient.Query @@ -660,6 +666,8 @@ module Server = let! cancelToken = Async.CancellationToken let task = web3.Eth.GasPrice.SendRequestAsync(null, cancelToken) let! hexBigInteger = Async.AwaitTask task + if isNull hexBigInteger then + raise <| AbnormalNullValueInJsonResponseException "Abnormal null response from gas price job" if hexBigInteger.Value = BigInteger 0 then return failwith "Some server returned zero for gas price, which is invalid" return hexBigInteger @@ -690,7 +698,12 @@ module Server = let! cancelToken = Async.CancellationToken let task = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync(transaction, null, cancelToken) - return! Async.AwaitTask task + let! response = Async.AwaitTask task + if isNull response then + raise <| + AbnormalNullValueInJsonResponseException + "Abnormal null response from broadcast transaction job" + return response } GetRandomizedFuncs currency web3Func try @@ -721,6 +734,10 @@ module Server = let task = web3.TransactionManager.TransactionReceiptService.PollForReceiptAsync(txHash, cancelToken) let! transactionReceipt = Async.AwaitTask task + if isNull transactionReceipt || isNull transactionReceipt.GasUsed || isNull transactionReceipt.Status then + raise <| + AbnormalNullValueInJsonResponseException + (SPrintF1 "Abnormal null response when getting details from tx receipt (%A)" transactionReceipt) return { GasUsed = transactionReceipt.GasUsed.Value Status = transactionReceipt.Status.Value From d00527f1ba66a00c5c0f379a43e1a13c3f344057 Mon Sep 17 00:00:00 2001 From: webwarrior Date: Tue, 20 Aug 2024 10:48:49 +0200 Subject: [PATCH 3/5] Backend/Ether: handle/retry RPC error -39000 Add code -39000 (UnparsableResponseType) to RpcErrorCode enum and process it since this error was encountered during integration testing. --- src/GWallet.Backend/Ether/EtherExceptions.fs | 1 + src/GWallet.Backend/Ether/EtherServer.fs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/GWallet.Backend/Ether/EtherExceptions.fs b/src/GWallet.Backend/Ether/EtherExceptions.fs index 11c6c3fc1..517bcbe0d 100644 --- a/src/GWallet.Backend/Ether/EtherExceptions.fs +++ b/src/GWallet.Backend/Ether/EtherExceptions.fs @@ -33,6 +33,7 @@ type RpcErrorCode = | CannotFulfillRequest = -32046 | ResourceNotFound = -32001 | InternalError = -32603 + | UnparsableResponseType = -39000 type ServerCannotBeResolvedException = inherit CommunicationUnsuccessfulException diff --git a/src/GWallet.Backend/Ether/EtherServer.fs b/src/GWallet.Backend/Ether/EtherServer.fs index 4994abc89..c9b694781 100644 --- a/src/GWallet.Backend/Ether/EtherServer.fs +++ b/src/GWallet.Backend/Ether/EtherServer.fs @@ -226,6 +226,8 @@ module Server = raise <| ServerMisconfiguredException(exMsg, rpcResponseEx) | i when i = int RpcErrorCode.InternalError -> raise <| ServerFaultException(exMsg, rpcResponseEx) + | j when j = int RpcErrorCode.UnparsableResponseType -> + raise <| ServerFaultException(exMsg, rpcResponseEx) | _ -> raise <| Exception (SPrintF3 "RpcResponseException with RpcError Code <%i> and Message '%s' (%s)" From c46a3da2efbb8939c4cbcdc47cff280e392a024c Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 21 Aug 2024 12:14:46 +0200 Subject: [PATCH 4/5] Backend/Ether: handle/retry HTTP error 408 Handle/retry HTTP error 408 (request timeout) in error handling code (raise ServerTimedOutException). --- src/GWallet.Backend/Ether/EtherServer.fs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/GWallet.Backend/Ether/EtherServer.fs b/src/GWallet.Backend/Ether/EtherServer.fs index c9b694781..7a7487ade 100644 --- a/src/GWallet.Backend/Ether/EtherServer.fs +++ b/src/GWallet.Backend/Ether/EtherServer.fs @@ -136,6 +136,8 @@ module Server = raise <| ServerTimedOutException(exMsg, httpReqEx) if HttpRequestExceptionMatchesErrorCode httpReqEx (int CloudFlareError.OriginUnreachable) then raise <| ServerTimedOutException(exMsg, httpReqEx) + if HttpRequestExceptionMatchesErrorCode httpReqEx (int HttpStatusCode.RequestTimeout) then + raise <| ServerTimedOutException(exMsg, httpReqEx) if HttpRequestExceptionMatchesErrorCode httpReqEx (int CloudFlareError.OriginSslHandshakeError) then raise <| ServerChannelNegotiationException(exMsg, CloudFlareError.OriginSslHandshakeError, httpReqEx) From f05dd7ada5627f0e51bef3f25190a77b96e0736e Mon Sep 17 00:00:00 2001 From: webwarrior Date: Wed, 21 Aug 2024 13:57:17 +0200 Subject: [PATCH 5/5] Backend/Ether: refactor null checks Make all null checks use isNull function as it's the most reliable way to test if value is null. --- src/GWallet.Backend/Ether/EtherServer.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GWallet.Backend/Ether/EtherServer.fs b/src/GWallet.Backend/Ether/EtherServer.fs index 7a7487ade..2263b08f7 100644 --- a/src/GWallet.Backend/Ether/EtherServer.fs +++ b/src/GWallet.Backend/Ether/EtherServer.fs @@ -549,7 +549,7 @@ module Server = let task = web3.Eth.GetBalance.SendRequestAsync (address, null, cancelToken) return! Async.AwaitTask task } - if Object.ReferenceEquals(balance, null) then + if isNull balance then raise <| AbnormalNullValueInJsonResponseException AbnormalNullValueInJsonResponseException.BalanceJobErrorMessage