diff --git a/common/src/main/java/haveno/common/UserThread.java b/common/src/main/java/haveno/common/UserThread.java index a08d6f9545..cacc16bd92 100644 --- a/common/src/main/java/haveno/common/UserThread.java +++ b/common/src/main/java/haveno/common/UserThread.java @@ -45,7 +45,7 @@ public class UserThread { @Getter @Setter private static Executor executor; - private static final String USER_THREAD_NAME = "UserThread"; + private static Thread USER_THREAD; public static void setTimerClass(Class timerClass) { UserThread.timerClass = timerClass; @@ -59,8 +59,10 @@ public class UserThread { public static void execute(Runnable command) { executor.execute(() -> { - Thread.currentThread().setName(USER_THREAD_NAME); - command.run(); + synchronized (executor) { + USER_THREAD = Thread.currentThread(); + command.run(); + } }); } @@ -79,9 +81,9 @@ public class UserThread { } } - // TODO: better way to determine if on UserThread, since this is not reliable - private static boolean isUserThread(Thread thread) { - return USER_THREAD_NAME.equals(thread.getName()); + public static boolean isUserThread(Thread thread) { + return thread == USER_THREAD; + } // Prefer FxTimer if a delay is needed in a JavaFx class (gui module) @@ -99,7 +101,7 @@ public class UserThread { } public static Timer runAfter(Runnable runnable, long delay, TimeUnit timeUnit) { - return getTimer().runLater(Duration.ofMillis(timeUnit.toMillis(delay)), runnable); + return getTimer().runLater(Duration.ofMillis(timeUnit.toMillis(delay)), () -> execute(runnable)); } public static Timer runPeriodically(Runnable runnable, long intervalInSec) { @@ -107,7 +109,7 @@ public class UserThread { } public static Timer runPeriodically(Runnable runnable, long interval, TimeUnit timeUnit) { - return getTimer().runPeriodically(Duration.ofMillis(timeUnit.toMillis(interval)), runnable); + return getTimer().runPeriodically(Duration.ofMillis(timeUnit.toMillis(interval)), () -> execute(runnable)); } private static Timer getTimer() { diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index 4ea756fbe0..0a7e413454 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -87,7 +87,6 @@ import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import lombok.Getter; import monero.daemon.model.MoneroTx; -import monero.wallet.model.MoneroOutputQuery; import org.bitcoinj.core.Coin; import org.bouncycastle.crypto.params.KeyParameter; import org.fxmisc.easybind.EasyBind; @@ -353,35 +352,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } } - private void thawUnreservedOutputs() { - if (xmrWalletService.getWallet() == null) return; - - // collect reserved outputs - Set reservedKeyImages = new HashSet(); - for (Trade trade : getObservableList()) { - if (trade.getSelf().getReserveTxKeyImages() == null) continue; - reservedKeyImages.addAll(trade.getSelf().getReserveTxKeyImages()); - } - for (OpenOffer openOffer : openOfferManager.getObservableList()) { - if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue; - reservedKeyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages()); - } - - // thaw unreserved outputs - Set unreservedFrozenKeyImages = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery() - .setIsFrozen(true) - .setIsSpent(false)) - .stream() - .map(output -> output.getKeyImage().getHex()) - .collect(Collectors.toSet()); - unreservedFrozenKeyImages.removeAll(reservedKeyImages); - if (!unreservedFrozenKeyImages.isEmpty()) { - log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages); - xmrWalletService.thawOutputs(unreservedFrozenKeyImages); - xmrWalletService.saveMainWallet(); - } - } - public TradeProtocol getTradeProtocol(Trade trade) { synchronized (tradeProtocolByTradeId) { return tradeProtocolByTradeId.get(trade.getUid()); @@ -464,9 +434,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } // thaw unreserved outputs - thawUnreservedOutputs(); + xmrWalletService.thawUnreservedOutputs(); // reset any available funded address entries + if (isShutDownStarted) return; xmrWalletService.getAddressEntriesForAvailableBalanceStream() .filter(addressEntry -> addressEntry.getOfferId() != null) .forEach(addressEntry -> { @@ -476,6 +447,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } // notify that persisted trades initialized + if (isShutDownStarted) return; persistedTradesInitialized.set(true); getObservableList().addListener((ListChangeListener) change -> onTradesChanged()); onTradesChanged(); 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 202f1c7c5d..60cf520a19 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -12,6 +12,7 @@ import haveno.common.util.Utilities; import haveno.core.api.AccountServiceListener; import haveno.core.api.CoreAccountService; import haveno.core.api.XmrConnectionService; +import haveno.core.offer.OpenOffer; import haveno.core.trade.BuyerTrade; import haveno.core.trade.HavenoUtils; import haveno.core.trade.MakerTrade; @@ -80,6 +81,7 @@ import java.util.stream.Stream; import javafx.beans.property.LongProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.SimpleLongProperty; +import javafx.beans.value.ChangeListener; import static com.google.common.base.Preconditions.checkState; @@ -120,6 +122,7 @@ public class XmrWalletService { protected final CopyOnWriteArraySet balanceListeners = new CopyOnWriteArraySet<>(); protected final CopyOnWriteArraySet walletListeners = new CopyOnWriteArraySet<>(); + private ChangeListener walletInitListener; private TradeManager tradeManager; private MoneroWalletRpc wallet; private Object walletLock = new Object(); @@ -364,6 +367,45 @@ public class XmrWalletService { } } + /** + * Thaw all outputs not reserved for a trade. + */ + public void thawUnreservedOutputs() { + synchronized (walletLock) { + + // collect reserved outputs + Set reservedKeyImages = new HashSet(); + for (Trade trade : tradeManager.getObservableList()) { + if (trade.getSelf().getReserveTxKeyImages() == null) continue; + reservedKeyImages.addAll(trade.getSelf().getReserveTxKeyImages()); + } + for (OpenOffer openOffer : tradeManager.getOpenOfferManager().getObservableList()) { + if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue; + reservedKeyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages()); + } + + // ensure wallet is open + if (wallet == null) { + log.warn("Cannot thaw unreserved outputs because wallet not open"); + return; + } + + // thaw unreserved outputs + Set unreservedFrozenKeyImages = wallet.getOutputs(new MoneroOutputQuery() + .setIsFrozen(true) + .setIsSpent(false)) + .stream() + .map(output -> output.getKeyImage().getHex()) + .collect(Collectors.toSet()); + unreservedFrozenKeyImages.removeAll(reservedKeyImages); + if (!unreservedFrozenKeyImages.isEmpty()) { + log.warn("Thawing outputs which are not reserved for offer or trade: " + unreservedFrozenKeyImages); + thawOutputs(unreservedFrozenKeyImages); + saveMainWallet(); + } + } + } + /** * Thaw the given outputs with a lock on the wallet. * @@ -695,17 +737,21 @@ public class XmrWalletService { HavenoUtils.submitToThread(() -> onConnectionChanged(connection), THREAD_ID); }); - // wait for monerod to sync - if (xmrConnectionService.downloadPercentageProperty().get() != 1) { - CountDownLatch latch = new CountDownLatch(1); - xmrConnectionService.downloadPercentageProperty().addListener((obs, oldVal, newVal) -> { - if (xmrConnectionService.downloadPercentageProperty().get() == 1) latch.countDown(); - }); - HavenoUtils.awaitLatch(latch); - } + // initialize main wallet when daemon synced + walletInitListener = (obs, oldVal, newVal) -> initMainWalletIfConnected(); + xmrConnectionService.downloadPercentageProperty().addListener(walletInitListener); + initMainWalletIfConnected(); + } - // initialize main wallet - maybeInitMainWallet(true); + private void initMainWalletIfConnected() { + HavenoUtils.submitToThread(() -> { + synchronized (walletLock) { + if (xmrConnectionService.downloadPercentageProperty().get() == 1 && wallet == null && !isShutDownStarted) { + maybeInitMainWallet(true); + if (walletInitListener != null) xmrConnectionService.downloadPercentageProperty().removeListener(walletInitListener); + } + } + }, THREAD_ID); } private void maybeInitMainWallet(boolean sync) {