diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index 68ae22906b..9b5b219a39 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -895,6 +895,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // if not found, create tx to split exact output if (splitOutputTx == null) { + if (openOffer.getSplitOutputTxHash() != null) { + log.warn("Split output tx not found for offer {}", openOffer.getId()); + } splitOrSchedule(openOffers, openOffer, amountNeeded); } else if (!splitOutputTx.isLocked()) { @@ -1026,7 +1029,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // handle sufficient available balance to split output boolean sufficientAvailableBalance = xmrWalletService.getAvailableBalance().compareTo(offerReserveAmount) >= 0; - if (sufficientAvailableBalance) { + if (sufficientAvailableBalance && openOffer.getSplitOutputTxHash() == null) { log.info("Splitting and scheduling outputs for offer {}", openOffer.getShortId()); splitAndSchedule(openOffer); } else if (openOffer.getScheduledTxHashes() == null) { @@ -1053,7 +1056,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe break; } catch (Exception e) { log.warn("Error creating split output tx to fund offer {} at subaddress {}, attempt={}/{}, error={}", openOffer.getShortId(), entry.getSubaddressIndex(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage()); - if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e; + if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e; HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying } } diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index 4c5cd81bb5..d139bb700b 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -482,8 +482,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi maybeRemoveTradeOnError(trade); } - // thaw unreserved outputs - xmrWalletService.thawUnreservedOutputs(); + // freeze or thaw outputs + xmrWalletService.fixReservedOutputs(); // reset any available funded address entries if (isShutDownStarted) return; diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 29c6fe8872..a00f6f85fc 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -459,35 +459,72 @@ public class XmrWalletService { public MoneroTxWallet createTx(MoneroTxConfig txConfig) { synchronized (WALLET_LOCK) { synchronized (HavenoUtils.getWalletFunctionLock()) { - return wallet.createTx(txConfig); + MoneroTxWallet tx = wallet.createTx(txConfig); + if (Boolean.TRUE.equals(txConfig.getRelay())) { + cachedTxs.addFirst(tx); + cacheWalletInfo(); + requestSaveMainWallet(); + } + return tx; } } } public MoneroTxWallet createTx(List destinations) { - MoneroTxWallet tx = createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));; + MoneroTxWallet tx = createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false)); //printTxs("XmrWalletService.createTx", tx); - requestSaveMainWallet(); return tx; } /** - * Thaw all outputs not reserved for a trade. + * Freeze reserved outputs and thaw unreserved outputs. */ - public void thawUnreservedOutputs() { + public void fixReservedOutputs() { synchronized (WALLET_LOCK) { // collect reserved outputs Set reservedKeyImages = new HashSet(); - for (Trade trade : tradeManager.getObservableList()) { + for (Trade trade : tradeManager.getOpenTrades()) { if (trade.getSelf().getReserveTxKeyImages() == null) continue; reservedKeyImages.addAll(trade.getSelf().getReserveTxKeyImages()); } - for (OpenOffer openOffer : tradeManager.getOpenOfferManager().getObservableList()) { + for (OpenOffer openOffer : tradeManager.getOpenOfferManager().getOpenOffers()) { if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue; reservedKeyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages()); } + freezeReservedOutputs(reservedKeyImages); + thawUnreservedOutputs(reservedKeyImages); + } + } + + private void freezeReservedOutputs(Set reservedKeyImages) { + synchronized (WALLET_LOCK) { + + // ensure wallet is open + if (wallet == null) { + log.warn("Cannot freeze reserved outputs because wallet not open"); + return; + } + + // freeze reserved outputs + Set reservedUnfrozenKeyImages = getOutputs(new MoneroOutputQuery() + .setIsFrozen(false) + .setIsSpent(false)) + .stream() + .map(output -> output.getKeyImage().getHex()) + .collect(Collectors.toSet()); + reservedUnfrozenKeyImages.retainAll(reservedKeyImages); + if (!reservedUnfrozenKeyImages.isEmpty()) { + log.warn("Freezing unfrozen outputs which are reserved for offer or trade: " + reservedUnfrozenKeyImages); + freezeOutputs(reservedUnfrozenKeyImages); + } + } + } + + private void thawUnreservedOutputs(Set reservedKeyImages) { + synchronized (WALLET_LOCK) { + // ensure wallet is open if (wallet == null) { log.warn("Cannot thaw unreserved outputs because wallet not open"); @@ -503,7 +540,7 @@ public class XmrWalletService { .collect(Collectors.toSet()); unreservedFrozenKeyImages.removeAll(reservedKeyImages); if (!unreservedFrozenKeyImages.isEmpty()) { - log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages); + log.warn("Thawing frozen outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages); thawOutputs(unreservedFrozenKeyImages); } }