diff --git a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java index a85cbc1d..11ac7e24 100644 --- a/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/XmrWalletService.java @@ -278,6 +278,15 @@ public class XmrWalletService { } } + public void deleteMultisigWalletBackups(String tradeId) { + log.info("{}.deleteMultisigWalletBackups({})", getClass().getSimpleName(), tradeId); + initWalletLock(tradeId); + synchronized (walletLocks.get(tradeId)) { + String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId; + deleteWalletBackups(walletName); + } + } + public MoneroTxWallet createTx(List destinations) { try { synchronized (wallet) { @@ -726,7 +735,6 @@ public class XmrWalletService { if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path); if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path); if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path); - deleteBackupWallets(walletName); // TODO: retain backup for some time? } private void closeAllWallets() { @@ -761,7 +769,7 @@ public class XmrWalletService { FileUtil.rollingBackup(walletDir, walletName + ".address.txt", NUM_MAX_BACKUP_WALLETS); } - private void deleteBackupWallets(String walletName) { + private void deleteWalletBackups(String walletName) { FileUtil.deleteRollingBackup(walletDir, walletName); FileUtil.deleteRollingBackup(walletDir, walletName + ".keys"); FileUtil.deleteRollingBackup(walletDir, walletName + ".address.txt"); diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 03d908e6..f5bbef42 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -133,6 +133,7 @@ public abstract class Trade implements Tradable, Model { SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), + PUBLISH_DEPOSIT_TX_REQUEST_FAILED(Phase.DEPOSIT_REQUESTED), // deposit published ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED), @@ -935,8 +936,24 @@ public abstract class Trade implements Tradable, Model { } public void deleteWallet() { - if (xmrWalletService.multisigWalletExists(getId())) xmrWalletService.deleteMultisigWallet(getId()); - else log.warn("Multisig wallet to delete for trade {} does not exist", getId()); + if (xmrWalletService.multisigWalletExists(getId())) { + + // delete trade wallet unless funded + if (isDepositPublished() && !isPayoutUnlocked()) { + log.warn("Refusing to delete wallet for {} {} because it could be funded", getClass().getSimpleName(), getId()); + return; + } + xmrWalletService.deleteMultisigWallet(getId()); + + // delete trade wallet backups unless possibly funded + boolean possiblyFunded = isDepositRequested() && !isPayoutUnlocked(); + if (possiblyFunded) { + log.warn("Refusing to delete backup wallet for {} {} in the small chance it becomes funded", getClass().getSimpleName(), getId()); + return; + } + } else { + log.warn("Multisig wallet to delete for trade {} does not exist", getId()); + } } public void shutDown() { @@ -1266,6 +1283,10 @@ public abstract class Trade implements Tradable, Model { return getState().getPhase().ordinal() >= Phase.DEPOSIT_REQUESTED.ordinal(); } + public boolean isDepositFailed() { + return getState() == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED; + } + public boolean isDepositPublished() { return getState().getPhase().ordinal() >= Phase.DEPOSITS_PUBLISHED.ordinal(); } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 6400c1bb..892e186f 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -1078,9 +1078,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi return; } - // remove trade and wallet unless timeout after deposit requested - boolean isTimeoutError = TradeProtocol.isTimeoutError(trade.getErrorMessage()); - if (!trade.isDepositRequested() || !isTimeoutError) { + // remove trade and wallet unless deposit requested without nack + if (!trade.isDepositRequested() || trade.isDepositFailed()) { removeTrade(trade); if (xmrWalletService.multisigWalletExists(trade.getId())) trade.deleteWallet(); } else { @@ -1100,13 +1099,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi MoneroTx takerDepositTx = xmrWalletService.getDaemon().getTx(trade.getTaker().getDepositTxHash()); // delete multisig trade wallet if neither deposit tx published - if ((makerDepositTx != null && makerDepositTx.isRelayed()) || (takerDepositTx != null && takerDepositTx.isRelayed())) { - log.warn("Refusing to delete {} {} after protocol timeout because its wallet might be funded", trade.getClass().getSimpleName(), trade.getId()); - } else { + if (makerDepositTx == null && takerDepositTx == null) { log.warn("Deleting {} {} after protocol timeout", trade.getClass().getSimpleName(), trade.getId()); removeTrade(trade); failedTradesManager.removeTrade(trade); if (xmrWalletService.multisigWalletExists(trade.getId())) trade.deleteWallet(); + } else { + log.warn("Refusing to delete {} {} after protocol timeout because its wallet might be funded", trade.getClass().getSimpleName(), trade.getId()); } }, 60); } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index e9dafc3f..9c1fbaca 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -27,6 +27,7 @@ import bisq.core.trade.HavenoUtils; import bisq.core.trade.SellerTrade; import bisq.core.trade.handlers.TradeResultHandler; import bisq.core.trade.messages.PaymentSentMessage; +import bisq.core.trade.messages.DepositRequest; import bisq.core.trade.messages.DepositResponse; import bisq.core.trade.messages.DepositsConfirmedMessage; import bisq.core.trade.messages.InitMultisigRequest; @@ -549,6 +550,13 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D String err = "Received AckMessage with error state for " + ackMessage.getSourceMsgClassName() + " from "+ peer + " with tradeId " + trade.getId() + " and errorMessage=" + ackMessage.getErrorMessage(); log.warn(err); + + // set trade state on deposit request nack + if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) { + trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED); + processModel.getTradeManager().requestPersistence(); + } + handleError(err); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java index 12d0c4a9..c5fdc548 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java @@ -47,6 +47,7 @@ public class ProcessDepositResponse extends TradeTask { processModel.getTradeManager().requestPersistence(); complete(); } catch (Throwable t) { + trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED); failed(t); } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 13eb6b97..a1f292de 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1573,22 +1573,23 @@ message Trade { SENT_PUBLISH_DEPOSIT_TX_REQUEST = 8; SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 9; SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST = 10; - ARBITRATOR_PUBLISHED_DEPOSIT_TXS = 11; - DEPOSIT_TXS_SEEN_IN_NETWORK = 12; - DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN = 13; - DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN = 14; - BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 15; - BUYER_SENT_PAYMENT_SENT_MSG = 16; - BUYER_SEND_FAILED_PAYMENT_SENT_MSG = 17; - BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG = 18; - BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG = 19; - SELLER_RECEIVED_PAYMENT_SENT_MSG = 20; - SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 21; - SELLER_SENT_PAYMENT_RECEIVED_MSG = 22; - SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 23; - SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 24; - SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 25; - TRADE_COMPLETED = 26; + PUBLISH_DEPOSIT_TX_REQUEST_FAILED = 11; + ARBITRATOR_PUBLISHED_DEPOSIT_TXS = 12; + DEPOSIT_TXS_SEEN_IN_NETWORK = 13; + DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN = 14; + DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN = 15; + BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 16; + BUYER_SENT_PAYMENT_SENT_MSG = 17; + BUYER_SEND_FAILED_PAYMENT_SENT_MSG = 18; + BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG = 19; + BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG = 20; + SELLER_RECEIVED_PAYMENT_SENT_MSG = 21; + SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 22; + SELLER_SENT_PAYMENT_RECEIVED_MSG = 23; + SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 24; + SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 25; + SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 26; + TRADE_COMPLETED = 27; } enum Phase {