From fcc3a69cb6abaccf890b77725b507b8b79aa8a03 Mon Sep 17 00:00:00 2001 From: Rodrigo <39995243+RodriFS@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:47:50 +0200 Subject: [PATCH] Cancel withdrawal request when no available utxos (#370) * Cancel withdrawal request when no available utxos * removed weird line break --- .../WalletWithdrawalRequestRepository.cs | 4 +- src/Helpers/CustomExceptions.cs | 7 +- src/Rpc/NodeGuardService.cs | 64 ++++++++++++++----- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/Data/Repositories/WalletWithdrawalRequestRepository.cs b/src/Data/Repositories/WalletWithdrawalRequestRepository.cs index a8b98b56..958b6ec7 100644 --- a/src/Data/Repositories/WalletWithdrawalRequestRepository.cs +++ b/src/Data/Repositories/WalletWithdrawalRequestRepository.cs @@ -131,6 +131,8 @@ public async Task> GetAllUnsignedPendingRequests() return (false, "The wallet could not be found."); } + type.Wallet = wallet; + var derivationStrategyBase = wallet.GetDerivationStrategy(); if (derivationStrategyBase == null) @@ -279,4 +281,4 @@ public async Task> GetOnChainPendingWithdrawals() return walletWithdrawalRequests; } } -} \ No newline at end of file +} diff --git a/src/Helpers/CustomExceptions.cs b/src/Helpers/CustomExceptions.cs index 1a996553..624fb494 100644 --- a/src/Helpers/CustomExceptions.cs +++ b/src/Helpers/CustomExceptions.cs @@ -22,4 +22,9 @@ public PeerNotOnlineException(string? message = null): base(message) {} public class RemoteCanceledFundingException : Exception { public RemoteCanceledFundingException(string? message = null): base(message) {} -} \ No newline at end of file +} + +public class NotEnoughBalanceInWalletException : Exception +{ + public NotEnoughBalanceInWalletException(string? message = null): base(message) {} +} diff --git a/src/Rpc/NodeGuardService.cs b/src/Rpc/NodeGuardService.cs index 42b4a76a..1b092d75 100644 --- a/src/Rpc/NodeGuardService.cs +++ b/src/Rpc/NodeGuardService.cs @@ -177,6 +177,17 @@ public override async Task RequestWithdrawal(RequestW throw new RpcException(new Status(StatusCode.NotFound, "Wallet not found")); } + if (request.Amount <= 0) + { + throw new RpcException(new Status(StatusCode.InvalidArgument, "Amount must be greater than 0")); + } + + if (request.Address == "") + { + throw new RpcException(new Status(StatusCode.InvalidArgument, + "A destination address must be provided")); + } + //Create withdrawal request var amount = new Money(request.Amount, MoneyUnit.Satoshi).ToUnit(MoneyUnit.BTC); @@ -192,10 +203,10 @@ public override async Task RequestWithdrawal(RequestW // Search the utxos and lock them var derivationStrategyBase = wallet.GetDerivationStrategy(); - - if(derivationStrategyBase == null) + + if (derivationStrategyBase == null) throw new RpcException(new Status(StatusCode.Internal, "Derivation strategy not found")); - + utxos = await _coinSelectionService.GetUTXOsByOutpointAsync(derivationStrategyBase, outpoints); amount = utxos.Sum(u => ((Money)u.Value).ToUnit(MoneyUnit.BTC)); } @@ -211,12 +222,17 @@ public override async Task RequestWithdrawal(RequestW : WalletWithdrawalRequestStatus.Pending, RequestMetadata = request.RequestMetadata, Changeless = request.Changeless, - MempoolRecommendedFeesType = (MempoolRecommendedFeesType) request.MempoolFeeRate, + MempoolRecommendedFeesType = (MempoolRecommendedFeesType)request.MempoolFeeRate, CustomFeeRate = request.CustomFeeRate }; //Save withdrawal request var withdrawalSaved = await _walletWithdrawalRequestRepository.AddAsync(withdrawalRequest); + if (!withdrawalSaved.Item1 && withdrawalSaved.Item2!.Contains("does not have enough funds")) + { + _logger.LogError(withdrawalSaved.Item2); + throw new NotEnoughBalanceInWalletException(withdrawalSaved.Item2); + } if (!withdrawalSaved.Item1) { @@ -239,7 +255,7 @@ await _coinSelectionService.LockUTXOs(utxos, withdrawalRequest, _logger.LogError("Error saving withdrawal request for wallet with id {walletId}", request.WalletId); throw new RpcException(new Status(StatusCode.Internal, "Error saving withdrawal request for wallet")); } - + //Template PSBT generation with SIGHASH_ALL var psbt = await _bitcoinService.GenerateTemplatePSBT(withdrawalRequest ?? throw new ArgumentException(nameof(withdrawalRequest))); @@ -264,25 +280,41 @@ await _coinSelectionService.LockUTXOs(utxos, withdrawalRequest, } catch (NoUTXOsAvailableException) { + CancelWithdrawalRequest(withdrawalRequest); _logger.LogError("No available UTXOs for wallet with id {walletId}", request.WalletId); - throw new RpcException(new Status(StatusCode.Internal, "No available UTXOs for wallet")); + throw new RpcException(new Status(StatusCode.ResourceExhausted, "No available UTXOs for wallet")); + } + catch (NotEnoughBalanceInWalletException e) + { + _logger.LogError(e.Message); + throw new RpcException(new Status(StatusCode.ResourceExhausted, e.Message)); + } + catch (RpcException e) + { + CancelWithdrawalRequest(withdrawalRequest); + _logger.LogError(e.Message); + throw new RpcException(new Status(e.Status.StatusCode, e.Status.Detail)); } catch (Exception e) { - if (withdrawalRequest != null) - { - withdrawalRequest.Status = WalletWithdrawalRequestStatus.Cancelled; - var (success, error) = _walletWithdrawalRequestRepository.Update(withdrawalRequest); - if (!success) - { - _logger.LogError(e, "Error updating status of withdrawal request {RequestId} for wallet {WalletId}", withdrawalRequest.Id, request.WalletId); - } - } + CancelWithdrawalRequest(withdrawalRequest); _logger.LogError(e, "Error requesting withdrawal for wallet with id {walletId}", request.WalletId); throw new RpcException(new Status(StatusCode.Internal, "Error requesting withdrawal for wallet")); } } + private void CancelWithdrawalRequest(WalletWithdrawalRequest? withdrawalRequest) + { + if (withdrawalRequest != null) + { + withdrawalRequest.Status = WalletWithdrawalRequestStatus.Cancelled; + var (success, error) = _walletWithdrawalRequestRepository.Update(withdrawalRequest); + if (!success) + { + _logger.LogError(error, "Error updating status of withdrawal request {RequestId} for wallet {WalletId}", withdrawalRequest.Id, withdrawalRequest.WalletId); + } + } + } public override async Task GetAvailableWallets(GetAvailableWalletsRequest request, ServerCallContext context) { @@ -965,4 +997,4 @@ private bool ValidateBitcoinAddress(string address) return true; } -} \ No newline at end of file +}