diff --git a/common/src/main/java/haveno/common/config/Config.java b/common/src/main/java/haveno/common/config/Config.java index 789042c6..a1d702b3 100644 --- a/common/src/main/java/haveno/common/config/Config.java +++ b/common/src/main/java/haveno/common/config/Config.java @@ -137,6 +137,12 @@ public class Config { public final boolean helpRequested; public final File configFile; + public enum UseTorForXmr { + AFTER_SYNC, + OFF, + ON + } + // Options supported on cmd line and in the config file public final String appName; public final File userDataDir; @@ -180,7 +186,7 @@ public class Config { public final String xmrNodeUsername; public final String xmrNodePassword; public final String xmrNodes; - public final boolean useTorForXmr; + public final UseTorForXmr useTorForXmr; public final boolean useTorForXmrOptionSetExplicitly; public final String socks5DiscoverMode; public final boolean useAllProvidedNodes; @@ -514,16 +520,18 @@ public class Config { .defaultsTo(""); ArgumentAcceptingOptionSpec xmrNodesOpt = - parser.accepts(XMR_NODES, "Custom nodes used for BitcoinJ as comma separated IP addresses.") + parser.accepts(XMR_NODES, "Custom nodes used for Monero as comma separated IP addresses.") .withRequiredArg() .describedAs("ip[,...]") .defaultsTo(""); - ArgumentAcceptingOptionSpec useTorForXmrOpt = - parser.accepts(USE_TOR_FOR_XMR, "If set to true BitcoinJ is routed over tor (socks 5 proxy).") + //noinspection rawtypes + ArgumentAcceptingOptionSpec useTorForXmrOpt = + parser.accepts(USE_TOR_FOR_XMR, "Configure TOR for Monero connections, one of: after_sync, off, or on.") .withRequiredArg() - .ofType(Boolean.class) - .defaultsTo(false); + .ofType(UseTorForXmr.class) + .withValuesConvertedBy(new EnumValueConverter(UseTorForXmr.class)) + .defaultsTo(UseTorForXmr.AFTER_SYNC); ArgumentAcceptingOptionSpec socks5DiscoverModeOpt = parser.accepts(SOCKS5_DISCOVER_MODE, "Specify discovery mode for Bitcoin nodes. " + @@ -686,7 +694,7 @@ public class Config { this.xmrNodeUsername = options.valueOf(xmrNodeUsernameOpt); this.xmrNodePassword = options.valueOf(xmrNodePasswordOpt); this.xmrNodes = options.valueOf(xmrNodesOpt); - this.useTorForXmr = options.valueOf(useTorForXmrOpt); + this.useTorForXmr = (UseTorForXmr) options.valueOf(useTorForXmrOpt); this.useTorForXmrOptionSetExplicitly = options.has(useTorForXmrOpt); this.socks5DiscoverMode = options.valueOf(socks5DiscoverModeOpt); this.useAllProvidedNodes = options.valueOf(useAllProvidedNodesOpt); diff --git a/core/src/main/java/haveno/core/api/CoreMoneroConnectionsService.java b/core/src/main/java/haveno/core/api/CoreMoneroConnectionsService.java index 0b10b138..99ae4664 100644 --- a/core/src/main/java/haveno/core/api/CoreMoneroConnectionsService.java +++ b/core/src/main/java/haveno/core/api/CoreMoneroConnectionsService.java @@ -333,7 +333,7 @@ public final class CoreMoneroConnectionsService { } private boolean useProxy(MoneroRpcConnection connection) { - return connection.isOnion() || (preferences.isUseTorForMonero() && !HavenoUtils.isLocalHost(connection.getUri())); + return connection.isOnion() || (preferences.getUseTorForXmr().isUseTorForXmr() && !HavenoUtils.isLocalHost(connection.getUri())); } private void initialize() { diff --git a/core/src/main/java/haveno/core/app/HavenoSetup.java b/core/src/main/java/haveno/core/app/HavenoSetup.java index 13ec9e90..830952a8 100644 --- a/core/src/main/java/haveno/core/app/HavenoSetup.java +++ b/core/src/main/java/haveno/core/app/HavenoSetup.java @@ -49,6 +49,7 @@ import haveno.core.trade.TradeManager; import haveno.core.trade.TradeTxException; import haveno.core.user.Preferences; import haveno.core.user.User; +import haveno.core.user.Preferences.UseTorForXmr; import haveno.core.util.FormattingUtils; import haveno.core.util.coin.CoinFormatter; import haveno.core.xmr.model.AddressEntry; @@ -63,6 +64,7 @@ import haveno.network.p2p.storage.payload.PersistableNetworkPayload; import haveno.network.utils.Utils; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ChangeListener; @@ -725,8 +727,8 @@ public class HavenoSetup { return walletAppSetup.getXmrSplashSyncIconId(); } - public BooleanProperty getUseTorForXMR() { - return walletAppSetup.getUseTorForXMR(); + public ObjectProperty getUseTorForXmr() { + return walletAppSetup.getUseTorForXmr(); } // P2P diff --git a/core/src/main/java/haveno/core/app/WalletAppSetup.java b/core/src/main/java/haveno/core/app/WalletAppSetup.java index 4434e83a..cd2efe34 100644 --- a/core/src/main/java/haveno/core/app/WalletAppSetup.java +++ b/core/src/main/java/haveno/core/app/WalletAppSetup.java @@ -25,15 +25,14 @@ import haveno.core.locale.Res; import haveno.core.offer.OpenOfferManager; import haveno.core.trade.TradeManager; import haveno.core.user.Preferences; +import haveno.core.user.Preferences.UseTorForXmr; import haveno.core.util.FormattingUtils; import haveno.core.xmr.exceptions.InvalidHostException; import haveno.core.xmr.exceptions.RejectedTxException; import haveno.core.xmr.setup.WalletsSetup; import haveno.core.xmr.wallet.WalletsManager; -import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; @@ -80,7 +79,7 @@ public class WalletAppSetup { @Getter private final ObjectProperty rejectedTxException = new SimpleObjectProperty<>(); @Getter - private final BooleanProperty useTorForXMR = new SimpleBooleanProperty(); + private final ObjectProperty useTorForXmr = new SimpleObjectProperty(); @Inject public WalletAppSetup(CoreContext coreContext, @@ -95,7 +94,7 @@ public class WalletAppSetup { this.connectionService = connectionService; this.config = config; this.preferences = preferences; - this.useTorForXMR.set(preferences.getUseTorForMonero()); + this.useTorForXmr.set(preferences.getUseTorForXmr()); } void init(@Nullable Consumer chainFileLockedExceptionHandler, @@ -239,7 +238,7 @@ public class WalletAppSetup { String postFix; if (config.ignoreLocalXmrNode) postFix = " " + Res.get("mainView.footer.localhostBitcoinNode"); - else if (preferences.getUseTorForMonero()) + else if (preferences.getUseTorForXmr().isUseTorForXmr()) postFix = " " + Res.get("mainView.footer.usingTor"); else postFix = ""; diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java index b9948530..d3c0cb29 100644 --- a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java +++ b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java @@ -838,7 +838,7 @@ public abstract class DisputeManager> extends Sup trade.importMultisigHex(); // sync and save wallet - trade.syncWallet(); + trade.syncAndPollWallet(); trade.saveWallet(); // create unsigned dispute payout tx if not already published @@ -887,7 +887,7 @@ public abstract class DisputeManager> extends Sup trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex()); return payoutTx; } catch (Exception e) { - trade.syncWallet(); + trade.syncAndPollWallet(); if (!trade.isPayoutPublished()) throw e; } } diff --git a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java index ee8c41c2..f2ff76ff 100644 --- a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java @@ -249,7 +249,7 @@ public final class ArbitrationManager extends DisputeManager disputeStateProperty = new SimpleObjectProperty<>(disputeState); transient final private ObjectProperty tradePeriodStateProperty = new SimpleObjectProperty<>(periodState); transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); + transient private Subscription tradeStateSubscription; transient private Subscription tradePhaseSubscription; transient private Subscription payoutStateSubscription; transient private TaskLooper txPollLooper; @@ -602,6 +604,14 @@ public abstract class Trade implements Tradable, Model { setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG); } + // handle trade state events + tradeStateSubscription = EasyBind.subscribe(stateProperty, newValue -> { + if (newValue == Trade.State.MULTISIG_COMPLETED) { + updateWalletRefreshPeriod(); + startPolling(); + } + }); + // handle trade phase events tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> { if (isDepositsPublished() && !isPayoutUnlocked()) updateWalletRefreshPeriod(); @@ -615,7 +625,7 @@ public abstract class Trade implements Tradable, Model { } }); - // handle payout state events + // handle payout events payoutStateSubscription = EasyBind.subscribe(payoutStateProperty, newValue -> { if (isPayoutPublished()) updateWalletRefreshPeriod(); @@ -657,15 +667,13 @@ public abstract class Trade implements Tradable, Model { xmrWalletService.addWalletListener(idlePayoutSyncer); } - // reprocess pending payout messages - this.getProtocol().maybeReprocessPaymentReceivedMessage(false); - HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false); - - // trade is initialized but not synced + // trade is initialized isInitialized = true; - // sync wallet if applicable + // done if payout unlocked or deposit not requested if (!isDepositRequested() || isPayoutUnlocked()) return; + + // done if wallet does not exist if (!walletExists()) { MoneroTx payoutTx = getPayoutTx(); if (payoutTx != null && payoutTx.getNumConfirmations() >= 10) { @@ -676,8 +684,9 @@ public abstract class Trade implements Tradable, Model { throw new IllegalStateException("Missing trade wallet for " + getClass().getSimpleName() + " " + getId()); } } - if (xmrWalletService.getConnectionsService().getConnection() == null || Boolean.FALSE.equals(xmrWalletService.getConnectionsService().isConnected())) return; - updateSyncing(); + + // initialize syncing and polling + initSyncing(); } } @@ -726,7 +735,7 @@ public abstract class Trade implements Tradable, Model { if (wallet != null) return wallet; if (!walletExists()) return null; if (isShutDownStarted) throw new RuntimeException("Cannot open wallet for " + getClass().getSimpleName() + " " + getId() + " because shut down is started"); - else wallet = xmrWalletService.openWallet(getWalletName()); + else wallet = xmrWalletService.openWallet(getWalletName(), xmrWalletService.isProxyApplied(wasWalletSynced)); return wallet; } } @@ -755,23 +764,8 @@ public abstract class Trade implements Tradable, Model { return this instanceof ArbitratorTrade && isDepositsConfirmed() && walletExists(); // arbitrator idles trade after deposits confirm } - public void syncWallet() { - if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId()); - if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId()); - log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getId()); - xmrWalletService.syncWallet(getWallet()); - pollWallet(); - log.info("Done syncing wallet for {} {}", getClass().getSimpleName(), getId()); - } - - private void trySyncWallet() { - try { - syncWallet(); - } catch (Exception e) { - if (!isShutDown && walletExists()) { - log.warn("Error syncing trade wallet for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage()); - } - } + public void syncAndPollWallet() { + syncWallet(true); } public void syncWalletNormallyForMs(long syncNormalDuration) { @@ -1148,6 +1142,7 @@ public abstract class Trade implements Tradable, Model { stopWallet(); } } + if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe(); if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe(); if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe(); idlePayoutSyncer = null; // main wallet removes listener itself @@ -1697,44 +1692,76 @@ public abstract class Trade implements Tradable, Model { // set daemon connection (must restart monero-wallet-rpc if proxy uri changed) String oldProxyUri = wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri(); String newProxyUri = connection == null ? null : connection.getProxyUri(); - log.info("Setting daemon connection for trade wallet {}: {}", getId() , connection == null ? null : connection.getUri()); - if (wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) { - log.info("Restarting monero-wallet-rpc for trade wallet to set proxy URI {}: {}", getId() , connection == null ? null : connection.getUri()); + log.info("Setting daemon connection for trade wallet {}: uri={}, proxyUri={}", getId() , connection == null ? null : connection.getUri(), newProxyUri); + if (xmrWalletService.isProxyApplied(wasWalletSynced) && wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) { + log.info("Restarting trade wallet {} because proxy URI has changed, old={}, new={}", getId(), oldProxyUri, newProxyUri); closeWallet(); wallet = getWallet(); } else { wallet.setDaemonConnection(connection); } - updateWalletRefreshPeriod(); // sync and reprocess messages on new thread - if (connection != null && !Boolean.FALSE.equals(connection.isConnected())) { + if (isInitialized && connection != null && !Boolean.FALSE.equals(connection.isConnected())) { HavenoUtils.submitTask(() -> { - updateSyncing(); - - // reprocess pending payout messages - this.getProtocol().maybeReprocessPaymentReceivedMessage(false); - HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false); + initSyncing(); }); } } } - private void updateSyncing() { + private void initSyncing() { if (isShutDownStarted) return; if (!isIdling()) { - updateWalletRefreshPeriod(); - trySyncWallet(); + initSyncingAux(); } else { long startSyncingInMs = ThreadLocalRandom.current().nextLong(0, getWalletRefreshPeriod()); // random time to start syncing UserThread.runAfter(() -> { if (!isShutDownStarted) { - updateWalletRefreshPeriod(); - trySyncWallet(); + initSyncingAux(); } }, startSyncingInMs / 1000l); } } + + private void initSyncingAux() { + if (!wasWalletSynced) trySyncWallet(false); + updateWalletRefreshPeriod(); + + // reprocess pending payout messages + this.getProtocol().maybeReprocessPaymentReceivedMessage(false); + HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false); + + startPolling(); + } + + private void trySyncWallet(boolean pollWallet) { + try { + syncWallet(pollWallet); + } catch (Exception e) { + if (!isShutDown && walletExists()) { + log.warn("Error syncing trade wallet for {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage()); + } + } + } + + private void syncWallet(boolean pollWallet) { + if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId()); + if (getWallet().getDaemonConnection() == null) throw new RuntimeException("Cannot sync trade wallet because it's not connected to a Monero daemon for " + getClass().getSimpleName() + ", " + getId()); + log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getId()); + xmrWalletService.syncWallet(getWallet()); + log.info("Done syncing wallet for {} {}", getClass().getSimpleName(), getId()); + + // apply tor after wallet synced depending on configuration + if (!wasWalletSynced) { + wasWalletSynced = true; + if (xmrWalletService.isProxyApplied(wasWalletSynced)) { + onConnectionChanged(xmrWalletService.getConnectionsService().getConnection()); + } + } + + if (pollWallet) pollWallet(); + } public void updateWalletRefreshPeriod() { setWalletRefreshPeriod(getWalletRefreshPeriod()); @@ -1749,14 +1776,16 @@ public abstract class Trade implements Tradable, Model { log.info("Setting wallet refresh rate for {} {} to {}", getClass().getSimpleName(), getId(), walletRefreshPeriod); getWallet().startSyncing(getWalletRefreshPeriod()); // TODO (monero-project): wallet rpc waits until last sync period finishes before starting new sync period } - stopPolling(); + if (isPolling()) { + stopPolling(); + startPolling(); + } } - startPolling(); } private void startPolling() { synchronized (walletLock) { - if (txPollLooper != null) return; + if (isPolling()) return; log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId()); txPollLooper = new TaskLooper(() -> pollWallet()); txPollLooper.start(walletRefreshPeriod); @@ -1765,12 +1794,18 @@ public abstract class Trade implements Tradable, Model { private void stopPolling() { synchronized (walletLock) { - if (txPollLooper != null) { + if (isPolling()) { txPollLooper.stop(); txPollLooper = null; } } } + + private boolean isPolling() { + synchronized (walletLock) { + return txPollLooper != null; + } + } private void pollWallet() { try { @@ -1855,7 +1890,6 @@ public abstract class Trade implements Tradable, Model { } } - private long getWalletRefreshPeriod() { if (isIdling()) return IDLE_SYNC_PERIOD_MS; return xmrWalletService.getConnectionsService().getRefreshPeriodMs(); @@ -1924,7 +1958,7 @@ public abstract class Trade implements Tradable, Model { long currentHeight = xmrWalletService.getDaemon().getHeight(); if (!isPayoutConfirmed() || (payoutHeight != null && currentHeight >= payoutHeight + XmrWalletService.NUM_BLOCKS_UNLOCK)) { log.info("Syncing idle trade wallet to update payout tx, tradeId={}", getId()); - syncWallet(); + syncAndPollWallet(); } processing = false; } catch (Exception e) { diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java index c0210b41..62aeaf7b 100644 --- a/core/src/main/java/haveno/core/trade/TradeManager.java +++ b/core/src/main/java/haveno/core/trade/TradeManager.java @@ -449,7 +449,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi // sync idle trades once in background after active trades for (Trade trade : trades) { - if (trade.isIdling()) HavenoUtils.submitTask(() -> trade.syncWallet()); + if (trade.isIdling()) HavenoUtils.submitTask(() -> trade.syncAndPollWallet()); } // process after all wallets initialized diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java index d7585956..c8df4dd9 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java @@ -112,7 +112,6 @@ public class ProcessInitMultisigRequest extends TradeTask { processModel.setMultisigAddress(result.getAddress()); new Thread(() -> trade.saveWallet()).start(); // save multisig wallet off thread on completion trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED); - trade.updateWalletRefreshPeriod(); // starts syncing } // update multisig participants if new state to communicate diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java index 343dcdec..5e67d2b9 100644 --- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java +++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java @@ -105,7 +105,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask { // update wallet trade.importMultisigHex(); - trade.syncWallet(); + trade.syncAndPollWallet(); trade.saveWallet(); // handle if payout tx not published @@ -117,7 +117,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask { if (deferSignAndPublish) { log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId()); GenUtils.waitFor(Trade.DEFER_PUBLISH_MS); - if (!trade.isPayoutUnlocked()) trade.syncWallet(); + if (!trade.isPayoutUnlocked()) trade.syncAndPollWallet(); } // verify and publish payout tx @@ -136,7 +136,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask { trade.verifyPayoutTx(trade.getPayoutTxHex(), false, true); } } catch (Exception e) { - trade.syncWallet(); + trade.syncAndPollWallet(); if (trade.isPayoutPublished()) log.info("Payout tx already published for {} {}", trade.getClass().getName(), trade.getId()); else throw e; } diff --git a/core/src/main/java/haveno/core/user/Preferences.java b/core/src/main/java/haveno/core/user/Preferences.java index 452e8a73..a3924615 100644 --- a/core/src/main/java/haveno/core/user/Preferences.java +++ b/core/src/main/java/haveno/core/user/Preferences.java @@ -66,6 +66,16 @@ import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @Singleton public final class Preferences implements PersistedDataHost, BridgeAddressProvider { + + public enum UseTorForXmr { + AFTER_SYNC, + OFF, + ON; + + public boolean isUseTorForXmr() { + return this != UseTorForXmr.OFF; + } + } private static final ArrayList BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList( new BlockChainExplorer("mempool.space (@wiz)", "https://mempool.space/tx/", "https://mempool.space/address/"), @@ -300,7 +310,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid // Override settings with options if set if (config.useTorForXmrOptionSetExplicitly) - setUseTorForMonero(config.useTorForXmr); + setUseTorForXmr(config.useTorForXmr); if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) { if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) { @@ -488,9 +498,20 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid } } - public void setUseTorForMonero(boolean useTorForMonero) { - prefPayload.setUseTorForMonero(useTorForMonero); - requestPersistence(); + public void setUseTorForXmr(Config.UseTorForXmr useTorForXmr) { + switch (useTorForXmr) { + case AFTER_SYNC: + setUseTorForXmrOrdinal(Preferences.UseTorForXmr.AFTER_SYNC.ordinal()); + break; + case OFF: + setUseTorForXmrOrdinal(Preferences.UseTorForXmr.OFF.ordinal()); + break; + case ON: + setUseTorForXmrOrdinal(Preferences.UseTorForXmr.ON.ordinal()); + break; + default: + throw new IllegalArgumentException("Unexpected case: " + useTorForXmr); + } } public void setSplitOfferOutput(boolean splitOfferOutput) { @@ -663,6 +684,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid prefPayload.setCustomBridges(customBridges); persistenceManager.forcePersistNow(); } + + public void setUseTorForXmrOrdinal(int useTorForXmrOrdinal) { + prefPayload.setUseTorForXmrOrdinal(useTorForXmrOrdinal); + requestPersistence(); + } public void setMoneroNodesOptionOrdinal(int bitcoinNodesOptionOrdinal) { prefPayload.setMoneroNodesOptionOrdinal(bitcoinNodesOptionOrdinal); @@ -794,8 +820,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid return !prefPayload.getDontShowAgainMap().containsKey(key) || !prefPayload.getDontShowAgainMap().get(key); } - public boolean getUseTorForMonero() { - return prefPayload.isUseTorForMonero(); + public UseTorForXmr getUseTorForXmr() { + return UseTorForXmr.class.getEnumConstants()[prefPayload.getUseTorForXmrOrdinal()]; + } + + public boolean isProxyApplied(boolean wasWalletSynced) { + return getUseTorForXmr() == UseTorForXmr.ON || (getUseTorForXmr() == UseTorForXmr.AFTER_SYNC && wasWalletSynced); } public boolean getSplitOfferOutput() { @@ -864,8 +894,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid void setPreferredTradeCurrency(TradeCurrency preferredTradeCurrency); - void setUseTorForMonero(boolean useTorForMonero); - void setSplitOfferOutput(boolean splitOfferOutput); void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook); @@ -927,6 +955,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid void setTorTransportOrdinal(int torTransportOrdinal); void setCustomBridges(String customBridges); + + void setUseTorForXmrOrdinal(int useTorForXmrOrdinal); void setMoneroNodesOptionOrdinal(int bitcoinNodesOption); diff --git a/core/src/main/java/haveno/core/user/PreferencesPayload.java b/core/src/main/java/haveno/core/user/PreferencesPayload.java index 6520caa9..f82034b5 100644 --- a/core/src/main/java/haveno/core/user/PreferencesPayload.java +++ b/core/src/main/java/haveno/core/user/PreferencesPayload.java @@ -58,7 +58,6 @@ public final class PreferencesPayload implements PersistableEnvelope { private boolean autoSelectArbitrators = true; private Map dontShowAgainMap = new HashMap<>(); private boolean tacAccepted; - private boolean useTorForMonero = false; private boolean splitOfferOutput = false; private boolean showOwnOffersInOfferBook = true; @Nullable @@ -98,6 +97,7 @@ public final class PreferencesPayload implements PersistableEnvelope { private int torTransportOrdinal; @Nullable private String customBridges; + private int useTorForXmrOrdinal; private int moneroNodesOptionOrdinal; @Nullable private String referralId; @@ -161,7 +161,6 @@ public final class PreferencesPayload implements PersistableEnvelope { .setAutoSelectArbitrators(autoSelectArbitrators) .putAllDontShowAgainMap(dontShowAgainMap) .setTacAccepted(tacAccepted) - .setUseTorForMonero(useTorForMonero) .setSplitOfferOutput(splitOfferOutput) .setShowOwnOffersInOfferBook(showOwnOffersInOfferBook) .setWithdrawalTxFeeInVbytes(withdrawalTxFeeInVbytes) @@ -179,6 +178,7 @@ public final class PreferencesPayload implements PersistableEnvelope { .setCssTheme(cssTheme) .setBridgeOptionOrdinal(bridgeOptionOrdinal) .setTorTransportOrdinal(torTransportOrdinal) + .setUseTorForXmrOrdinal(useTorForXmrOrdinal) .setMoneroNodesOptionOrdinal(moneroNodesOptionOrdinal) .setUseSoundForMobileNotifications(useSoundForMobileNotifications) .setUseTradeNotifications(useTradeNotifications) @@ -244,7 +244,6 @@ public final class PreferencesPayload implements PersistableEnvelope { proto.getAutoSelectArbitrators(), Maps.newHashMap(proto.getDontShowAgainMapMap()), proto.getTacAccepted(), - proto.getUseTorForMonero(), proto.getSplitOfferOutput(), proto.getShowOwnOffersInOfferBook(), proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, @@ -272,6 +271,7 @@ public final class PreferencesPayload implements PersistableEnvelope { proto.getBridgeOptionOrdinal(), proto.getTorTransportOrdinal(), ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()), + proto.getUseTorForXmrOrdinal(), proto.getMoneroNodesOptionOrdinal(), proto.getReferralId().isEmpty() ? null : proto.getReferralId(), proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(), diff --git a/core/src/main/java/haveno/core/xmr/setup/WalletsSetup.java b/core/src/main/java/haveno/core/xmr/setup/WalletsSetup.java index 3cbd49c3..d89f75b2 100644 --- a/core/src/main/java/haveno/core/xmr/setup/WalletsSetup.java +++ b/core/src/main/java/haveno/core/xmr/setup/WalletsSetup.java @@ -171,7 +171,7 @@ public class WalletsSetup { backupWallets(); - final Socks5Proxy socks5Proxy = preferences.getUseTorForMonero() ? socks5ProxyProvider.getSocks5Proxy() : null; + final Socks5Proxy socks5Proxy = socks5ProxyProvider.getSocks5Proxy(); log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy); walletConfig = new WalletConfig(params, walletDir, "haveno") { 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 f2b44317..1bd7d535 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -19,6 +19,7 @@ import haveno.core.trade.HavenoUtils; import haveno.core.trade.MakerTrade; import haveno.core.trade.Trade; import haveno.core.trade.TradeManager; +import haveno.core.user.Preferences; import haveno.core.xmr.listeners.XmrBalanceListener; import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.model.XmrAddressEntryList; @@ -54,7 +55,6 @@ import monero.wallet.model.MoneroWalletListenerI; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import javax.inject.Inject; import java.io.File; import java.math.BigInteger; @@ -100,6 +100,7 @@ public class XmrWalletService { private static final int MONERO_LOG_LEVEL = 0; private static final boolean PRINT_STACK_TRACE = false; + private final Preferences preferences; private final CoreAccountService accountService; private final CoreMoneroConnectionsService connectionsService; private final XmrAddressEntryList xmrAddressEntryList; @@ -114,17 +115,20 @@ public class XmrWalletService { private TradeManager tradeManager; private MoneroWalletRpc wallet; private Object walletLock = new Object(); + private boolean wasWalletSynced = false; private final Map> txCache = new HashMap>(); private boolean isShutDownStarted = false; private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type @Inject - XmrWalletService(CoreAccountService accountService, + XmrWalletService(Preferences preferences, + CoreAccountService accountService, CoreMoneroConnectionsService connectionsService, WalletsSetup walletsSetup, XmrAddressEntryList xmrAddressEntryList, @Named(Config.WALLET_DIR) File walletDir, @Named(Config.WALLET_RPC_BIND_PORT) int rpcBindPort) { + this.preferences = preferences; this.accountService = accountService; this.connectionsService = connectionsService; this.walletsSetup = walletsSetup; @@ -212,6 +216,10 @@ public class XmrWalletService { public CoreMoneroConnectionsService getConnectionsService() { return connectionsService; } + + public boolean isProxyApplied(boolean wasWalletSynced) { + return preferences.isProxyApplied(wasWalletSynced); + } public String getWalletPassword() { return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword(); @@ -231,13 +239,14 @@ public class XmrWalletService { null); } - public MoneroWalletRpc openWallet(String walletName) { + public MoneroWalletRpc openWallet(String walletName, boolean applyProxyUri) { log.info("{}.openWallet({})", getClass().getSimpleName(), walletName); if (isShutDownStarted) throw new IllegalStateException("Cannot open wallet because shutting down"); return openWalletRpc(new MoneroWalletConfig() .setPath(walletName) .setPassword(getWalletPassword()), - null); + null, + applyProxyUri); } /** @@ -670,7 +679,7 @@ public class XmrWalletService { log.info("Initializing main wallet with monerod=" + (daemon == null ? "null" : daemon.getRpcConnection().getUri())); MoneroWalletConfig walletConfig = new MoneroWalletConfig().setPath(MONERO_WALLET_NAME).setPassword(getWalletPassword()); if (MoneroUtils.walletExists(xmrWalletFile.getPath())) { - wallet = openWalletRpc(walletConfig, rpcBindPort); + wallet = openWalletRpc(walletConfig, rpcBindPort, isProxyApplied(wasWalletSynced)); } else if (connectionsService.getConnection() != null && Boolean.TRUE.equals(connectionsService.getConnection().isConnected())) { wallet = createWalletRpc(walletConfig, rpcBindPort); } @@ -688,9 +697,13 @@ public class XmrWalletService { log.info("Syncing main wallet"); long time = System.currentTimeMillis(); wallet.sync(); // blocking + wasWalletSynced = true; log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms"); wallet.startSyncing(connectionsService.getRefreshPeriodMs()); if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) log.info("Monero wallet balance={}, unlocked balance={}", wallet.getBalance(0), wallet.getUnlockedBalance(0)); + + // reapply connection after wallet synced + onConnectionChanged(connectionsService.getConnection()); // TODO: using this to signify both daemon and wallet synced, use separate sync handlers? connectionsService.doneDownload(); @@ -742,7 +755,7 @@ public class XmrWalletService { try { // start monero-wallet-rpc instance - walletRpc = startWalletRpcInstance(port); + walletRpc = startWalletRpcInstance(port, isProxyApplied(false)); walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE); // prevent wallet rpc from syncing @@ -762,20 +775,23 @@ public class XmrWalletService { } } - private MoneroWalletRpc openWalletRpc(MoneroWalletConfig config, Integer port) { + private MoneroWalletRpc openWalletRpc(MoneroWalletConfig config, Integer port, boolean applyProxyUri) { MoneroWalletRpc walletRpc = null; try { // start monero-wallet-rpc instance - walletRpc = startWalletRpcInstance(port); + walletRpc = startWalletRpcInstance(port, applyProxyUri); walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE); // prevent wallet rpc from syncing walletRpc.stopSyncing(); + + // configure connection + MoneroRpcConnection connection = new MoneroRpcConnection(connectionsService.getConnection()); + if (!applyProxyUri) connection.setProxyUri(null); // open wallet - log.info("Opening wallet " + config.getPath()); - walletRpc.openWallet(config.setServer(connectionsService.getConnection())); + walletRpc.openWallet(config.setServer(connection)); if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE); log.info("Done opening wallet " + config.getPath()); return walletRpc; @@ -786,7 +802,7 @@ public class XmrWalletService { } } - private MoneroWalletRpc startWalletRpcInstance(Integer port) { + private MoneroWalletRpc startWalletRpcInstance(Integer port, boolean applyProxyUri) { // check if monero-wallet-rpc exists if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH @@ -808,7 +824,7 @@ public class XmrWalletService { if (connection != null) { cmd.add("--daemon-address"); cmd.add(connection.getUri()); - if (connection.getProxyUri() != null) { + if (applyProxyUri && connection.getProxyUri() != null) { // TODO: only apply proxy if wallet is already synced, so we need a flag passed here cmd.add("--proxy"); cmd.add(connection.getProxyUri()); if (!connection.isOnion()) cmd.add("--daemon-ssl-allow-any-cert"); // necessary to use proxy with clearnet mmonerod @@ -831,13 +847,12 @@ public class XmrWalletService { synchronized (walletLock) { if (isShutDownStarted) return; if (wallet != null && HavenoUtils.connectionConfigsEqual(connection, wallet.getDaemonConnection())) return; - - log.info("Setting main wallet daemon connection: " + (connection == null ? null : connection.getUri())); String oldProxyUri = wallet == null || wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri(); String newProxyUri = connection == null ? null : connection.getProxyUri(); + log.info("Setting daemon connection for main wallet: uri={}, proxyUri={}", connection == null ? null : connection.getUri(), newProxyUri); if (wallet == null) maybeInitMainWallet(false); else if (wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) { - log.info("Restarting main wallet because proxy URI has changed"); + log.info("Restarting main wallet because proxy URI has changed, old={}, new={}", oldProxyUri, newProxyUri); closeMainWallet(true); maybeInitMainWallet(false); } else { diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index ddf4dea6..ce733cf8 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1254,7 +1254,7 @@ setting.preferences.prefCurrency=Preferred currency setting.preferences.displayTraditional=Display traditional currencies setting.preferences.noTraditional=There are no traditional currencies selected setting.preferences.cannotRemovePrefCurrency=You cannot remove your selected preferred display currency -setting.preferences.displayCryptos=Display cryptose +setting.preferences.displayCryptos=Display cryptos setting.preferences.noCryptos=There are no cryptos selected setting.preferences.addTraditional=Add traditional currency setting.preferences.addCrypto=Add cryptocurrency @@ -1284,6 +1284,9 @@ settings.net.onionAddressLabel=My onion address settings.net.xmrNodesLabel=Use custom Monero nodes settings.net.moneroPeersLabel=Connected peers settings.net.useTorForXmrJLabel=Use Tor for Monero network +settings.net.useTorForXmrAfterSyncRadio=After wallet is synchronized +settings.net.useTorForXmrOffRadio=Never +settings.net.useTorForXmrOnRadio=Always settings.net.moneroNodesLabel=Monero nodes to connect to settings.net.useProvidedNodesRadio=Use provided Monero nodes settings.net.usePublicNodesRadio=Use public Monero network diff --git a/desktop/src/main/java/haveno/desktop/main/MainView.java b/desktop/src/main/java/haveno/desktop/main/MainView.java index 5c0ce6be..a3966492 100644 --- a/desktop/src/main/java/haveno/desktop/main/MainView.java +++ b/desktop/src/main/java/haveno/desktop/main/MainView.java @@ -574,7 +574,7 @@ public class MainView extends InitializableView { splashP2PNetworkBusyAnimation.stop(); showTorNetworkSettingsButton.setVisible(true); showTorNetworkSettingsButton.setManaged(true); - if (model.getUseTorForXMR().get()) { + if (model.getUseTorForXmr().get().isUseTorForXmr()) { // If using tor for XMR, hide the XMR status since tor is not working xmrSyncIndicator.setVisible(false); xmrSplashInfo.setVisible(false); diff --git a/desktop/src/main/java/haveno/desktop/main/MainViewModel.java b/desktop/src/main/java/haveno/desktop/main/MainViewModel.java index 1f949e4f..3af48666 100644 --- a/desktop/src/main/java/haveno/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/haveno/desktop/main/MainViewModel.java @@ -47,6 +47,7 @@ import haveno.core.provider.price.PriceFeedService; import haveno.core.trade.TradeManager; import haveno.core.user.DontShowAgainLookup; import haveno.core.user.Preferences; +import haveno.core.user.Preferences.UseTorForXmr; import haveno.core.user.User; import haveno.core.xmr.wallet.XmrWalletService; import haveno.desktop.Navigation; @@ -628,8 +629,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener return havenoSetup.getXmrSplashSyncIconId(); } - BooleanProperty getUseTorForXMR() { - return havenoSetup.getUseTorForXMR(); + ObjectProperty getUseTorForXmr() { + return havenoSetup.getUseTorForXmr(); } // P2P diff --git a/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.fxml b/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.fxml index eb890b5a..3e9d2542 100644 --- a/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.fxml +++ b/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.fxml @@ -73,7 +73,14 @@ - + + + + + + + + diff --git a/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.java b/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.java index ae5f23a6..9d46dfca 100644 --- a/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.java +++ b/desktop/src/main/java/haveno/desktop/main/settings/network/NetworkSettingsView.java @@ -49,7 +49,6 @@ import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.geometry.VPos; -import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.TableColumn; @@ -75,7 +74,7 @@ public class NetworkSettingsView extends ActivatableView { @FXML TitledGroupBg p2pHeader, btcHeader; @FXML - Label xmrNodesLabel, moneroNodesLabel, localhostXmrNodeInfoLabel; + Label useTorForXmrLabel, xmrNodesLabel, moneroNodesLabel, localhostXmrNodeInfoLabel; @FXML InputTextField xmrNodesInputTextField; @FXML @@ -83,7 +82,7 @@ public class NetworkSettingsView extends ActivatableView { @FXML Label p2PPeersLabel, moneroPeersLabel; @FXML - CheckBox useTorForXmrJCheckBox; + RadioButton useTorForXmrAfterSyncRadio, useTorForXmrOffRadio, useTorForXmrOnRadio; @FXML RadioButton useProvidedNodesRadio, useCustomNodesRadio, usePublicNodesRadio; @FXML @@ -122,8 +121,11 @@ public class NetworkSettingsView extends ActivatableView { private Subscription moneroBlockHeightSubscription; private Subscription nodeAddressSubscription; private ChangeListener xmrNodesInputTextFieldFocusListener; + private ToggleGroup useTorForXmrToggleGroup; private ToggleGroup moneroPeersToggleGroup; + private Preferences.UseTorForXmr selectedUseTorForXmr; private XmrNodes.MoneroNodesOption selectedMoneroNodesOption; + private ChangeListener useTorForXmrToggleGroupListener; private ChangeListener moneroPeersToggleGroupListener; private ChangeListener filterPropertyListener; @@ -156,7 +158,10 @@ public class NetworkSettingsView extends ActivatableView { onionAddress.setPromptText(Res.get("settings.net.onionAddressLabel")); xmrNodesLabel.setText(Res.get("settings.net.xmrNodesLabel")); moneroPeersLabel.setText(Res.get("settings.net.moneroPeersLabel")); - useTorForXmrJCheckBox.setText(Res.get("settings.net.useTorForXmrJLabel")); + useTorForXmrLabel.setText(Res.get("settings.net.useTorForXmrJLabel")); + useTorForXmrAfterSyncRadio.setText(Res.get("settings.net.useTorForXmrAfterSyncRadio")); + useTorForXmrOffRadio.setText(Res.get("settings.net.useTorForXmrOffRadio")); + useTorForXmrOnRadio.setText(Res.get("settings.net.useTorForXmrOnRadio")); moneroNodesLabel.setText(Res.get("settings.net.moneroNodesLabel")); moneroPeerAddressColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.onionAddressColumn"))); moneroPeerAddressColumn.getStyleClass().add("first-column"); @@ -208,6 +213,31 @@ public class NetworkSettingsView extends ActivatableView { p2pPeersTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData"))); p2pPeersTableView.getSortOrder().add(creationDateColumn); creationDateColumn.setSortType(TableColumn.SortType.ASCENDING); + + // use tor for xmr radio buttons + + useTorForXmrToggleGroup = new ToggleGroup(); + useTorForXmrAfterSyncRadio.setToggleGroup(useTorForXmrToggleGroup); + useTorForXmrOffRadio.setToggleGroup(useTorForXmrToggleGroup); + useTorForXmrOnRadio.setToggleGroup(useTorForXmrToggleGroup); + + useTorForXmrAfterSyncRadio.setUserData(Preferences.UseTorForXmr.AFTER_SYNC); + useTorForXmrOffRadio.setUserData(Preferences.UseTorForXmr.OFF); + useTorForXmrOnRadio.setUserData(Preferences.UseTorForXmr.ON); + + selectedUseTorForXmr = Preferences.UseTorForXmr.values()[preferences.getUseTorForXmrOrdinal()]; + + selectUseTorForXmrToggle(); + onUseTorForXmrToggleSelected(false); + + useTorForXmrToggleGroupListener = (observable, oldValue, newValue) -> { + if (newValue != null) { + selectedUseTorForXmr = (Preferences.UseTorForXmr) newValue.getUserData(); + onUseTorForXmrToggleSelected(true); + } + }; + + // monero nodes radio buttons moneroPeersToggleGroup = new ToggleGroup(); useProvidedNodesRadio.setToggleGroup(moneroPeersToggleGroup); @@ -264,6 +294,7 @@ public class NetworkSettingsView extends ActivatableView { @Override public void activate() { + useTorForXmrToggleGroup.selectedToggleProperty().addListener(useTorForXmrToggleGroupListener); moneroPeersToggleGroup.selectedToggleProperty().addListener(moneroPeersToggleGroupListener); if (filterManager.getFilter() != null) @@ -271,22 +302,6 @@ public class NetworkSettingsView extends ActivatableView { filterManager.filterProperty().addListener(filterPropertyListener); - useTorForXmrJCheckBox.setSelected(preferences.getUseTorForMonero()); - useTorForXmrJCheckBox.setOnAction(event -> { - boolean selected = useTorForXmrJCheckBox.isSelected(); - if (selected != preferences.getUseTorForMonero()) { - new Popup().information(Res.get("settings.net.needRestart")) - .actionButtonText(Res.get("shared.applyAndShutDown")) - .onAction(() -> { - preferences.setUseTorForMonero(selected); - UserThread.runAfter(HavenoApp.getShutDownHandler(), 500, TimeUnit.MILLISECONDS); - }) - .closeButtonText(Res.get("shared.cancel")) - .onClose(() -> useTorForXmrJCheckBox.setSelected(!selected)) - .show(); - } - }); - rescanOutputsButton.setOnAction(event -> GUIUtil.rescanOutputs(preferences)); moneroPeersSubscription = EasyBind.subscribe(connectionManager.peerConnectionsProperty(), @@ -328,11 +343,10 @@ public class NetworkSettingsView extends ActivatableView { @Override public void deactivate() { + useTorForXmrToggleGroup.selectedToggleProperty().removeListener(useTorForXmrToggleGroupListener); moneroPeersToggleGroup.selectedToggleProperty().removeListener(moneroPeersToggleGroupListener); filterManager.filterProperty().removeListener(filterPropertyListener); - useTorForXmrJCheckBox.setOnAction(null); - if (nodeAddressSubscription != null) nodeAddressSubscription.unsubscribe(); @@ -360,6 +374,21 @@ public class NetworkSettingsView extends ActivatableView { return filterManager.getFilter() != null && filterManager.getFilter().isPreventPublicXmrNetwork(); } + + private void selectUseTorForXmrToggle() { + switch (selectedUseTorForXmr) { + case OFF: + useTorForXmrToggleGroup.selectToggle(useTorForXmrOffRadio); + break; + case ON: + useTorForXmrToggleGroup.selectToggle(useTorForXmrOnRadio); + break; + default: + case AFTER_SYNC: + useTorForXmrToggleGroup.selectToggle(useTorForXmrAfterSyncRadio); + break; + } + } private void selectMoneroPeersToggle() { switch (selectedMoneroNodesOption) { @@ -383,10 +412,30 @@ public class NetworkSettingsView extends ActivatableView { .useShutDownButton() .show(); } + + private void onUseTorForXmrToggleSelected(boolean calledFromUser) { + Preferences.UseTorForXmr currentUseTorForXmr = Preferences.UseTorForXmr.values()[preferences.getUseTorForXmrOrdinal()]; + if (currentUseTorForXmr != selectedUseTorForXmr) { + if (calledFromUser) { + new Popup().information(Res.get("settings.net.needRestart")) + .actionButtonText(Res.get("shared.applyAndShutDown")) + .onAction(() -> { + preferences.setUseTorForXmrOrdinal(selectedUseTorForXmr.ordinal()); + UserThread.runAfter(HavenoApp.getShutDownHandler(), 500, TimeUnit.MILLISECONDS); + }) + .closeButtonText(Res.get("shared.cancel")) + .onClose(() -> { + selectedUseTorForXmr = currentUseTorForXmr; + selectUseTorForXmrToggle(); + }) + .show(); + } + } + } private void onMoneroPeersToggleSelected(boolean calledFromUser) { boolean localMoneroNodeShouldBeUsed = localMoneroNode.shouldBeUsed(); - useTorForXmrJCheckBox.setDisable(localMoneroNodeShouldBeUsed); + useTorForXmrLabel.setDisable(localMoneroNodeShouldBeUsed); moneroNodesLabel.setDisable(localMoneroNodeShouldBeUsed); xmrNodesLabel.setDisable(localMoneroNodeShouldBeUsed); xmrNodesInputTextField.setDisable(localMoneroNodeShouldBeUsed); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 2ec38e8a..7ec85347 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1654,7 +1654,7 @@ message PreferencesPayload { bool auto_select_arbitrators = 8; map dont_show_again_map = 9; bool tac_accepted = 10; - bool use_tor_for_monero = 11; + int32 use_tor_for_xmr_ordinal = 11; bool show_own_offers_in_offer_book = 12; TradeCurrency preferred_trade_currency = 13; int64 withdrawal_tx_fee_in_vbytes = 14;