diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index 2dfa5b0f1f..f121470e3a 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -145,6 +145,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model { private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS; private static final int NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT = 5; protected final Object pollLock = new Object(); + private final Object removeTradeOnErrorLock = new Object(); protected static final Object importMultisigLock = new Object(); private boolean pollInProgress; private boolean restartInProgress; @@ -1608,11 +1609,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model { } // shut down trade threads - isInitialized = false; isShutDown = true; List shutDownThreads = new ArrayList<>(); shutDownThreads.add(() -> ThreadUtils.shutDown(getId())); ThreadUtils.awaitTasks(shutDownThreads); + stopProtocolTimeout(); + isInitialized = false; // save and close if (wallet != null) { @@ -1765,24 +1767,30 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model { } private void removeTradeOnError() { - log.warn("removeTradeOnError() trade={}, tradeId={}, state={}", getClass().getSimpleName(), getShortId(), getState()); + synchronized (removeTradeOnErrorLock) { - // force close and re-open wallet in case stuck - forceCloseWallet(); - if (isDepositRequested()) getWallet(); + // skip if already shut down or removed + if (isShutDown || !processModel.getTradeManager().hasTrade(getId())) return; + log.warn("removeTradeOnError() trade={}, tradeId={}, state={}", getClass().getSimpleName(), getShortId(), getState()); - // shut down trade thread - try { - ThreadUtils.shutDown(getId(), 1000l); - } catch (Exception e) { - log.warn("Error shutting down trade thread for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage()); + // force close and re-open wallet in case stuck + forceCloseWallet(); + if (isDepositRequested()) getWallet(); + + // clear and shut down trade + onShutDownStarted(); + clearAndShutDown(); + + // shut down trade thread + try { + ThreadUtils.shutDown(getId(), 5000l); + } catch (Exception e) { + log.warn("Error shutting down trade thread for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage()); + } + + // unregister trade + processModel.getTradeManager().unregisterTrade(this); } - - // clear and shut down trade - clearAndShutDown(); - - // unregister trade - processModel.getTradeManager().unregisterTrade(this); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -1824,6 +1832,13 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model { getProtocol().startTimeout(TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS); } + public void stopProtocolTimeout() { + if (!isInitialized) return; + TradeProtocol protocol = getProtocol(); + if (protocol == null) return; + protocol.stopTimeout(); + } + public void setState(State state) { if (isInitialized) { // We don't want to log at startup the setState calls from all persisted trades diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index 14ac565c26..9b092decac 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -563,9 +563,14 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi Optional openOfferOptional = openOfferManager.getOpenOffer(request.getOfferId()); if (!openOfferOptional.isPresent()) return; OpenOffer openOffer = openOfferOptional.get(); - if (openOffer.getState() != OpenOffer.State.AVAILABLE) return; Offer offer = openOffer.getOffer(); + // check availability + if (openOffer.getState() != OpenOffer.State.AVAILABLE) { + log.warn("Ignoring InitTradeRequest to maker because offer is not available, offerId={}, sender={}", request.getOfferId(), sender); + return; + } + // validate challenge if (openOffer.getChallenge() != null && !HavenoUtils.getChallengeHash(openOffer.getChallenge()).equals(HavenoUtils.getChallengeHash(request.getChallenge()))) { log.warn("Ignoring InitTradeRequest to maker because challenge is incorrect, tradeId={}, sender={}", request.getOfferId(), sender); @@ -980,9 +985,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi closedTradableManager.add(trade); trade.setCompleted(true); removeTrade(trade, true); - - // TODO The address entry should have been removed already. Check and if its the case remove that. - xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); + xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); // TODO The address entry should have been removed already. Check and if its the case remove that. requestPersistence(); } @@ -990,6 +993,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi log.warn("Unregistering {} {}", trade.getClass().getSimpleName(), trade.getId()); removeTrade(trade, true); removeFailedTrade(trade); + xmrWalletService.swapPayoutAddressEntryToAvailable(trade.getId()); // TODO The address entry should have been removed already. Check and if its the case remove that. requestPersistence(); } @@ -1274,11 +1278,15 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi return offer.getDirection() == OfferDirection.SELL; } - // TODO (woodser): make Optional versus Trade return types consistent + // TODO: make Optional versus Trade return types consistent public Trade getTrade(String tradeId) { return getOpenTrade(tradeId).orElseGet(() -> getClosedTrade(tradeId).orElseGet(() -> getFailedTrade(tradeId).orElseGet(() -> null))); } + public boolean hasTrade(String tradeId) { + return getTrade(tradeId) != null; + } + public Optional getOpenTrade(String tradeId) { synchronized (tradableList.getList()) { return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); diff --git a/core/src/main/java/haveno/core/trade/protocol/TradeProtocol.java b/core/src/main/java/haveno/core/trade/protocol/TradeProtocol.java index d87d5f3d5d..65fcee23c6 100644 --- a/core/src/main/java/haveno/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/haveno/core/trade/protocol/TradeProtocol.java @@ -842,7 +842,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } } - protected synchronized void stopTimeout() { + public synchronized void stopTimeout() { synchronized (timeoutTimerLock) { if (timeoutTimer != null) { timeoutTimer.stop(); diff --git a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java index e035a04f23..884df454e7 100644 --- a/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java +++ b/desktop/src/main/java/haveno/desktop/main/funds/deposit/DepositView.java @@ -346,8 +346,7 @@ public class DepositView extends ActivatableView { List addressEntries = xmrWalletService.getAddressEntries(); List items = new ArrayList<>(); for (XmrAddressEntry addressEntry : addressEntries) { - DepositListItem item = new DepositListItem(addressEntry, xmrWalletService, formatter); - if (addressEntry.isTradePayout() && BigInteger.ZERO.equals(item.getBalanceAsBI())) continue; // do not show empty trade payout addresses + if (addressEntry.isTradePayout()) continue; // do not show trade payout addresses items.add(new DepositListItem(addressEntry, xmrWalletService, formatter)); }