monero wallets switch to tor after initial sync by default

This commit is contained in:
woodser 2023-09-01 10:12:21 -04:00
parent 0620bf260e
commit f19bc2ad4b
20 changed files with 276 additions and 129 deletions

View File

@ -137,6 +137,12 @@ public class Config {
public final boolean helpRequested; public final boolean helpRequested;
public final File configFile; public final File configFile;
public enum UseTorForXmr {
AFTER_SYNC,
OFF,
ON
}
// Options supported on cmd line and in the config file // Options supported on cmd line and in the config file
public final String appName; public final String appName;
public final File userDataDir; public final File userDataDir;
@ -180,7 +186,7 @@ public class Config {
public final String xmrNodeUsername; public final String xmrNodeUsername;
public final String xmrNodePassword; public final String xmrNodePassword;
public final String xmrNodes; public final String xmrNodes;
public final boolean useTorForXmr; public final UseTorForXmr useTorForXmr;
public final boolean useTorForXmrOptionSetExplicitly; public final boolean useTorForXmrOptionSetExplicitly;
public final String socks5DiscoverMode; public final String socks5DiscoverMode;
public final boolean useAllProvidedNodes; public final boolean useAllProvidedNodes;
@ -514,16 +520,18 @@ public class Config {
.defaultsTo(""); .defaultsTo("");
ArgumentAcceptingOptionSpec<String> xmrNodesOpt = ArgumentAcceptingOptionSpec<String> 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() .withRequiredArg()
.describedAs("ip[,...]") .describedAs("ip[,...]")
.defaultsTo(""); .defaultsTo("");
ArgumentAcceptingOptionSpec<Boolean> useTorForXmrOpt = //noinspection rawtypes
parser.accepts(USE_TOR_FOR_XMR, "If set to true BitcoinJ is routed over tor (socks 5 proxy).") ArgumentAcceptingOptionSpec<Enum> useTorForXmrOpt =
parser.accepts(USE_TOR_FOR_XMR, "Configure TOR for Monero connections, one of: after_sync, off, or on.")
.withRequiredArg() .withRequiredArg()
.ofType(Boolean.class) .ofType(UseTorForXmr.class)
.defaultsTo(false); .withValuesConvertedBy(new EnumValueConverter(UseTorForXmr.class))
.defaultsTo(UseTorForXmr.AFTER_SYNC);
ArgumentAcceptingOptionSpec<String> socks5DiscoverModeOpt = ArgumentAcceptingOptionSpec<String> socks5DiscoverModeOpt =
parser.accepts(SOCKS5_DISCOVER_MODE, "Specify discovery mode for Bitcoin nodes. " + parser.accepts(SOCKS5_DISCOVER_MODE, "Specify discovery mode for Bitcoin nodes. " +
@ -686,7 +694,7 @@ public class Config {
this.xmrNodeUsername = options.valueOf(xmrNodeUsernameOpt); this.xmrNodeUsername = options.valueOf(xmrNodeUsernameOpt);
this.xmrNodePassword = options.valueOf(xmrNodePasswordOpt); this.xmrNodePassword = options.valueOf(xmrNodePasswordOpt);
this.xmrNodes = options.valueOf(xmrNodesOpt); this.xmrNodes = options.valueOf(xmrNodesOpt);
this.useTorForXmr = options.valueOf(useTorForXmrOpt); this.useTorForXmr = (UseTorForXmr) options.valueOf(useTorForXmrOpt);
this.useTorForXmrOptionSetExplicitly = options.has(useTorForXmrOpt); this.useTorForXmrOptionSetExplicitly = options.has(useTorForXmrOpt);
this.socks5DiscoverMode = options.valueOf(socks5DiscoverModeOpt); this.socks5DiscoverMode = options.valueOf(socks5DiscoverModeOpt);
this.useAllProvidedNodes = options.valueOf(useAllProvidedNodesOpt); this.useAllProvidedNodes = options.valueOf(useAllProvidedNodesOpt);

View File

@ -333,7 +333,7 @@ public final class CoreMoneroConnectionsService {
} }
private boolean useProxy(MoneroRpcConnection connection) { 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() { private void initialize() {

View File

@ -49,6 +49,7 @@ import haveno.core.trade.TradeManager;
import haveno.core.trade.TradeTxException; import haveno.core.trade.TradeTxException;
import haveno.core.user.Preferences; import haveno.core.user.Preferences;
import haveno.core.user.User; import haveno.core.user.User;
import haveno.core.user.Preferences.UseTorForXmr;
import haveno.core.util.FormattingUtils; import haveno.core.util.FormattingUtils;
import haveno.core.util.coin.CoinFormatter; import haveno.core.util.coin.CoinFormatter;
import haveno.core.xmr.model.AddressEntry; import haveno.core.xmr.model.AddressEntry;
@ -63,6 +64,7 @@ import haveno.network.p2p.storage.payload.PersistableNetworkPayload;
import haveno.network.utils.Utils; import haveno.network.utils.Utils;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty; import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
@ -725,8 +727,8 @@ public class HavenoSetup {
return walletAppSetup.getXmrSplashSyncIconId(); return walletAppSetup.getXmrSplashSyncIconId();
} }
public BooleanProperty getUseTorForXMR() { public ObjectProperty<UseTorForXmr> getUseTorForXmr() {
return walletAppSetup.getUseTorForXMR(); return walletAppSetup.getUseTorForXmr();
} }
// P2P // P2P

View File

@ -25,15 +25,14 @@ import haveno.core.locale.Res;
import haveno.core.offer.OpenOfferManager; import haveno.core.offer.OpenOfferManager;
import haveno.core.trade.TradeManager; import haveno.core.trade.TradeManager;
import haveno.core.user.Preferences; import haveno.core.user.Preferences;
import haveno.core.user.Preferences.UseTorForXmr;
import haveno.core.util.FormattingUtils; import haveno.core.util.FormattingUtils;
import haveno.core.xmr.exceptions.InvalidHostException; import haveno.core.xmr.exceptions.InvalidHostException;
import haveno.core.xmr.exceptions.RejectedTxException; import haveno.core.xmr.exceptions.RejectedTxException;
import haveno.core.xmr.setup.WalletsSetup; import haveno.core.xmr.setup.WalletsSetup;
import haveno.core.xmr.wallet.WalletsManager; import haveno.core.xmr.wallet.WalletsManager;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty; import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
@ -80,7 +79,7 @@ public class WalletAppSetup {
@Getter @Getter
private final ObjectProperty<RejectedTxException> rejectedTxException = new SimpleObjectProperty<>(); private final ObjectProperty<RejectedTxException> rejectedTxException = new SimpleObjectProperty<>();
@Getter @Getter
private final BooleanProperty useTorForXMR = new SimpleBooleanProperty(); private final ObjectProperty<UseTorForXmr> useTorForXmr = new SimpleObjectProperty<UseTorForXmr>();
@Inject @Inject
public WalletAppSetup(CoreContext coreContext, public WalletAppSetup(CoreContext coreContext,
@ -95,7 +94,7 @@ public class WalletAppSetup {
this.connectionService = connectionService; this.connectionService = connectionService;
this.config = config; this.config = config;
this.preferences = preferences; this.preferences = preferences;
this.useTorForXMR.set(preferences.getUseTorForMonero()); this.useTorForXmr.set(preferences.getUseTorForXmr());
} }
void init(@Nullable Consumer<String> chainFileLockedExceptionHandler, void init(@Nullable Consumer<String> chainFileLockedExceptionHandler,
@ -239,7 +238,7 @@ public class WalletAppSetup {
String postFix; String postFix;
if (config.ignoreLocalXmrNode) if (config.ignoreLocalXmrNode)
postFix = " " + Res.get("mainView.footer.localhostBitcoinNode"); postFix = " " + Res.get("mainView.footer.localhostBitcoinNode");
else if (preferences.getUseTorForMonero()) else if (preferences.getUseTorForXmr().isUseTorForXmr())
postFix = " " + Res.get("mainView.footer.usingTor"); postFix = " " + Res.get("mainView.footer.usingTor");
else else
postFix = ""; postFix = "";

View File

@ -838,7 +838,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
trade.importMultisigHex(); trade.importMultisigHex();
// sync and save wallet // sync and save wallet
trade.syncWallet(); trade.syncAndPollWallet();
trade.saveWallet(); trade.saveWallet();
// create unsigned dispute payout tx if not already published // create unsigned dispute payout tx if not already published
@ -887,7 +887,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex()); trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
return payoutTx; return payoutTx;
} catch (Exception e) { } catch (Exception e) {
trade.syncWallet(); trade.syncAndPollWallet();
if (!trade.isPayoutPublished()) throw e; if (!trade.isPayoutPublished()) throw e;
} }
} }

View File

@ -249,7 +249,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// sync and save wallet // sync and save wallet
if (!trade.isPayoutPublished()) { if (!trade.isPayoutPublished()) {
trade.syncWallet(); trade.syncAndPollWallet();
trade.saveWallet(); trade.saveWallet();
} }
@ -266,7 +266,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
if (disputeClosedMessage.isDeferPublishPayout()) { if (disputeClosedMessage.isDeferPublishPayout()) {
log.info("Deferring signing and publishing dispute payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId()); log.info("Deferring signing and publishing dispute payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
GenUtils.waitFor(Trade.DEFER_PUBLISH_MS); GenUtils.waitFor(Trade.DEFER_PUBLISH_MS);
if (!trade.isPayoutUnlocked()) trade.syncWallet(); if (!trade.isPayoutUnlocked()) trade.syncAndPollWallet();
} }
// sign and publish dispute payout tx if peer still has not published // sign and publish dispute payout tx if peer still has not published
@ -277,7 +277,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
} catch (Exception e) { } catch (Exception e) {
// check if payout published again // check if payout published again
trade.syncWallet(); trade.syncAndPollWallet();
if (trade.isPayoutPublished()) { if (trade.isPayoutPublished()) {
log.info("Dispute payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId()); log.info("Dispute payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
} else { } else {

View File

@ -111,6 +111,7 @@ public abstract class Trade implements Tradable, Model {
private static final String MONERO_TRADE_WALLET_PREFIX = "xmr_trade_"; private static final String MONERO_TRADE_WALLET_PREFIX = "xmr_trade_";
private MoneroWallet wallet; // trade wallet private MoneroWallet wallet; // trade wallet
private Object walletLock = new Object(); private Object walletLock = new Object();
boolean wasWalletSynced = false;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Enums // Enums
@ -382,6 +383,7 @@ public abstract class Trade implements Tradable, Model {
transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState); transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState);
transient final private ObjectProperty<TradePeriodState> tradePeriodStateProperty = new SimpleObjectProperty<>(periodState); transient final private ObjectProperty<TradePeriodState> tradePeriodStateProperty = new SimpleObjectProperty<>(periodState);
transient final private StringProperty errorMessageProperty = new SimpleStringProperty(); transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
transient private Subscription tradeStateSubscription;
transient private Subscription tradePhaseSubscription; transient private Subscription tradePhaseSubscription;
transient private Subscription payoutStateSubscription; transient private Subscription payoutStateSubscription;
transient private TaskLooper txPollLooper; transient private TaskLooper txPollLooper;
@ -602,6 +604,14 @@ public abstract class Trade implements Tradable, Model {
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG); 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 // handle trade phase events
tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> { tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> {
if (isDepositsPublished() && !isPayoutUnlocked()) updateWalletRefreshPeriod(); 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 -> { payoutStateSubscription = EasyBind.subscribe(payoutStateProperty, newValue -> {
if (isPayoutPublished()) updateWalletRefreshPeriod(); if (isPayoutPublished()) updateWalletRefreshPeriod();
@ -657,15 +667,13 @@ public abstract class Trade implements Tradable, Model {
xmrWalletService.addWalletListener(idlePayoutSyncer); xmrWalletService.addWalletListener(idlePayoutSyncer);
} }
// reprocess pending payout messages // trade is initialized
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
// trade is initialized but not synced
isInitialized = true; isInitialized = true;
// sync wallet if applicable // done if payout unlocked or deposit not requested
if (!isDepositRequested() || isPayoutUnlocked()) return; if (!isDepositRequested() || isPayoutUnlocked()) return;
// done if wallet does not exist
if (!walletExists()) { if (!walletExists()) {
MoneroTx payoutTx = getPayoutTx(); MoneroTx payoutTx = getPayoutTx();
if (payoutTx != null && payoutTx.getNumConfirmations() >= 10) { 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()); 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 (wallet != null) return wallet;
if (!walletExists()) return null; if (!walletExists()) return null;
if (isShutDownStarted) throw new RuntimeException("Cannot open wallet for " + getClass().getSimpleName() + " " + getId() + " because shut down is started"); 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; 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 return this instanceof ArbitratorTrade && isDepositsConfirmed() && walletExists(); // arbitrator idles trade after deposits confirm
} }
public void syncWallet() { public void syncAndPollWallet() {
if (getWallet() == null) throw new RuntimeException("Cannot sync trade wallet because it doesn't exist for " + getClass().getSimpleName() + ", " + getId()); syncWallet(true);
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 syncWalletNormallyForMs(long syncNormalDuration) { public void syncWalletNormallyForMs(long syncNormalDuration) {
@ -1148,6 +1142,7 @@ public abstract class Trade implements Tradable, Model {
stopWallet(); stopWallet();
} }
} }
if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe();
if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe(); if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe();
if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe(); if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe();
idlePayoutSyncer = null; // main wallet removes listener itself 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) // set daemon connection (must restart monero-wallet-rpc if proxy uri changed)
String oldProxyUri = wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri(); String oldProxyUri = wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri();
String newProxyUri = connection == null ? null : connection.getProxyUri(); String newProxyUri = connection == null ? null : connection.getProxyUri();
log.info("Setting daemon connection for trade wallet {}: {}", getId() , connection == null ? null : connection.getUri()); log.info("Setting daemon connection for trade wallet {}: uri={}, proxyUri={}", getId() , connection == null ? null : connection.getUri(), newProxyUri);
if (wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) { if (xmrWalletService.isProxyApplied(wasWalletSynced) && 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("Restarting trade wallet {} because proxy URI has changed, old={}, new={}", getId(), oldProxyUri, newProxyUri);
closeWallet(); closeWallet();
wallet = getWallet(); wallet = getWallet();
} else { } else {
wallet.setDaemonConnection(connection); wallet.setDaemonConnection(connection);
} }
updateWalletRefreshPeriod();
// sync and reprocess messages on new thread // 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(() -> { HavenoUtils.submitTask(() -> {
updateSyncing(); initSyncing();
// reprocess pending payout messages
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
}); });
} }
} }
} }
private void updateSyncing() { private void initSyncing() {
if (isShutDownStarted) return; if (isShutDownStarted) return;
if (!isIdling()) { if (!isIdling()) {
updateWalletRefreshPeriod(); initSyncingAux();
trySyncWallet();
} else { } else {
long startSyncingInMs = ThreadLocalRandom.current().nextLong(0, getWalletRefreshPeriod()); // random time to start syncing long startSyncingInMs = ThreadLocalRandom.current().nextLong(0, getWalletRefreshPeriod()); // random time to start syncing
UserThread.runAfter(() -> { UserThread.runAfter(() -> {
if (!isShutDownStarted) { if (!isShutDownStarted) {
updateWalletRefreshPeriod(); initSyncingAux();
trySyncWallet();
} }
}, startSyncingInMs / 1000l); }, 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() { public void updateWalletRefreshPeriod() {
setWalletRefreshPeriod(getWalletRefreshPeriod()); 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); 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 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() { private void startPolling() {
synchronized (walletLock) { synchronized (walletLock) {
if (txPollLooper != null) return; if (isPolling()) return;
log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId()); log.info("Starting to poll wallet for {} {}", getClass().getSimpleName(), getId());
txPollLooper = new TaskLooper(() -> pollWallet()); txPollLooper = new TaskLooper(() -> pollWallet());
txPollLooper.start(walletRefreshPeriod); txPollLooper.start(walletRefreshPeriod);
@ -1765,12 +1794,18 @@ public abstract class Trade implements Tradable, Model {
private void stopPolling() { private void stopPolling() {
synchronized (walletLock) { synchronized (walletLock) {
if (txPollLooper != null) { if (isPolling()) {
txPollLooper.stop(); txPollLooper.stop();
txPollLooper = null; txPollLooper = null;
} }
} }
} }
private boolean isPolling() {
synchronized (walletLock) {
return txPollLooper != null;
}
}
private void pollWallet() { private void pollWallet() {
try { try {
@ -1855,7 +1890,6 @@ public abstract class Trade implements Tradable, Model {
} }
} }
private long getWalletRefreshPeriod() { private long getWalletRefreshPeriod() {
if (isIdling()) return IDLE_SYNC_PERIOD_MS; if (isIdling()) return IDLE_SYNC_PERIOD_MS;
return xmrWalletService.getConnectionsService().getRefreshPeriodMs(); return xmrWalletService.getConnectionsService().getRefreshPeriodMs();
@ -1924,7 +1958,7 @@ public abstract class Trade implements Tradable, Model {
long currentHeight = xmrWalletService.getDaemon().getHeight(); long currentHeight = xmrWalletService.getDaemon().getHeight();
if (!isPayoutConfirmed() || (payoutHeight != null && currentHeight >= payoutHeight + XmrWalletService.NUM_BLOCKS_UNLOCK)) { if (!isPayoutConfirmed() || (payoutHeight != null && currentHeight >= payoutHeight + XmrWalletService.NUM_BLOCKS_UNLOCK)) {
log.info("Syncing idle trade wallet to update payout tx, tradeId={}", getId()); log.info("Syncing idle trade wallet to update payout tx, tradeId={}", getId());
syncWallet(); syncAndPollWallet();
} }
processing = false; processing = false;
} catch (Exception e) { } catch (Exception e) {

View File

@ -449,7 +449,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// sync idle trades once in background after active trades // sync idle trades once in background after active trades
for (Trade trade : trades) { for (Trade trade : trades) {
if (trade.isIdling()) HavenoUtils.submitTask(() -> trade.syncWallet()); if (trade.isIdling()) HavenoUtils.submitTask(() -> trade.syncAndPollWallet());
} }
// process after all wallets initialized // process after all wallets initialized

View File

@ -112,7 +112,6 @@ public class ProcessInitMultisigRequest extends TradeTask {
processModel.setMultisigAddress(result.getAddress()); processModel.setMultisigAddress(result.getAddress());
new Thread(() -> trade.saveWallet()).start(); // save multisig wallet off thread on completion new Thread(() -> trade.saveWallet()).start(); // save multisig wallet off thread on completion
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED); trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
trade.updateWalletRefreshPeriod(); // starts syncing
} }
// update multisig participants if new state to communicate // update multisig participants if new state to communicate

View File

@ -105,7 +105,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
// update wallet // update wallet
trade.importMultisigHex(); trade.importMultisigHex();
trade.syncWallet(); trade.syncAndPollWallet();
trade.saveWallet(); trade.saveWallet();
// handle if payout tx not published // handle if payout tx not published
@ -117,7 +117,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
if (deferSignAndPublish) { if (deferSignAndPublish) {
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId()); log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
GenUtils.waitFor(Trade.DEFER_PUBLISH_MS); GenUtils.waitFor(Trade.DEFER_PUBLISH_MS);
if (!trade.isPayoutUnlocked()) trade.syncWallet(); if (!trade.isPayoutUnlocked()) trade.syncAndPollWallet();
} }
// verify and publish payout tx // verify and publish payout tx
@ -136,7 +136,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
trade.verifyPayoutTx(trade.getPayoutTxHex(), false, true); trade.verifyPayoutTx(trade.getPayoutTxHex(), false, true);
} }
} catch (Exception e) { } catch (Exception e) {
trade.syncWallet(); trade.syncAndPollWallet();
if (trade.isPayoutPublished()) log.info("Payout tx already published for {} {}", trade.getClass().getName(), trade.getId()); if (trade.isPayoutPublished()) log.info("Payout tx already published for {} {}", trade.getClass().getName(), trade.getId());
else throw e; else throw e;
} }

View File

@ -66,6 +66,16 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
@Singleton @Singleton
public final class Preferences implements PersistedDataHost, BridgeAddressProvider { 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<BlockChainExplorer> BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList( private static final ArrayList<BlockChainExplorer> BTC_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList(
new BlockChainExplorer("mempool.space (@wiz)", "https://mempool.space/tx/", "https://mempool.space/address/"), 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 // Override settings with options if set
if (config.useTorForXmrOptionSetExplicitly) if (config.useTorForXmrOptionSetExplicitly)
setUseTorForMonero(config.useTorForXmr); setUseTorForXmr(config.useTorForXmr);
if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) { if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) {
if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) { if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) {
@ -488,9 +498,20 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
} }
} }
public void setUseTorForMonero(boolean useTorForMonero) { public void setUseTorForXmr(Config.UseTorForXmr useTorForXmr) {
prefPayload.setUseTorForMonero(useTorForMonero); switch (useTorForXmr) {
requestPersistence(); 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) { public void setSplitOfferOutput(boolean splitOfferOutput) {
@ -663,6 +684,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
prefPayload.setCustomBridges(customBridges); prefPayload.setCustomBridges(customBridges);
persistenceManager.forcePersistNow(); persistenceManager.forcePersistNow();
} }
public void setUseTorForXmrOrdinal(int useTorForXmrOrdinal) {
prefPayload.setUseTorForXmrOrdinal(useTorForXmrOrdinal);
requestPersistence();
}
public void setMoneroNodesOptionOrdinal(int bitcoinNodesOptionOrdinal) { public void setMoneroNodesOptionOrdinal(int bitcoinNodesOptionOrdinal) {
prefPayload.setMoneroNodesOptionOrdinal(bitcoinNodesOptionOrdinal); prefPayload.setMoneroNodesOptionOrdinal(bitcoinNodesOptionOrdinal);
@ -794,8 +820,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
return !prefPayload.getDontShowAgainMap().containsKey(key) || !prefPayload.getDontShowAgainMap().get(key); return !prefPayload.getDontShowAgainMap().containsKey(key) || !prefPayload.getDontShowAgainMap().get(key);
} }
public boolean getUseTorForMonero() { public UseTorForXmr getUseTorForXmr() {
return prefPayload.isUseTorForMonero(); return UseTorForXmr.class.getEnumConstants()[prefPayload.getUseTorForXmrOrdinal()];
}
public boolean isProxyApplied(boolean wasWalletSynced) {
return getUseTorForXmr() == UseTorForXmr.ON || (getUseTorForXmr() == UseTorForXmr.AFTER_SYNC && wasWalletSynced);
} }
public boolean getSplitOfferOutput() { public boolean getSplitOfferOutput() {
@ -864,8 +894,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setPreferredTradeCurrency(TradeCurrency preferredTradeCurrency); void setPreferredTradeCurrency(TradeCurrency preferredTradeCurrency);
void setUseTorForMonero(boolean useTorForMonero);
void setSplitOfferOutput(boolean splitOfferOutput); void setSplitOfferOutput(boolean splitOfferOutput);
void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook); void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook);
@ -927,6 +955,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setTorTransportOrdinal(int torTransportOrdinal); void setTorTransportOrdinal(int torTransportOrdinal);
void setCustomBridges(String customBridges); void setCustomBridges(String customBridges);
void setUseTorForXmrOrdinal(int useTorForXmrOrdinal);
void setMoneroNodesOptionOrdinal(int bitcoinNodesOption); void setMoneroNodesOptionOrdinal(int bitcoinNodesOption);

View File

@ -58,7 +58,6 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean autoSelectArbitrators = true; private boolean autoSelectArbitrators = true;
private Map<String, Boolean> dontShowAgainMap = new HashMap<>(); private Map<String, Boolean> dontShowAgainMap = new HashMap<>();
private boolean tacAccepted; private boolean tacAccepted;
private boolean useTorForMonero = false;
private boolean splitOfferOutput = false; private boolean splitOfferOutput = false;
private boolean showOwnOffersInOfferBook = true; private boolean showOwnOffersInOfferBook = true;
@Nullable @Nullable
@ -98,6 +97,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private int torTransportOrdinal; private int torTransportOrdinal;
@Nullable @Nullable
private String customBridges; private String customBridges;
private int useTorForXmrOrdinal;
private int moneroNodesOptionOrdinal; private int moneroNodesOptionOrdinal;
@Nullable @Nullable
private String referralId; private String referralId;
@ -161,7 +161,6 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setAutoSelectArbitrators(autoSelectArbitrators) .setAutoSelectArbitrators(autoSelectArbitrators)
.putAllDontShowAgainMap(dontShowAgainMap) .putAllDontShowAgainMap(dontShowAgainMap)
.setTacAccepted(tacAccepted) .setTacAccepted(tacAccepted)
.setUseTorForMonero(useTorForMonero)
.setSplitOfferOutput(splitOfferOutput) .setSplitOfferOutput(splitOfferOutput)
.setShowOwnOffersInOfferBook(showOwnOffersInOfferBook) .setShowOwnOffersInOfferBook(showOwnOffersInOfferBook)
.setWithdrawalTxFeeInVbytes(withdrawalTxFeeInVbytes) .setWithdrawalTxFeeInVbytes(withdrawalTxFeeInVbytes)
@ -179,6 +178,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setCssTheme(cssTheme) .setCssTheme(cssTheme)
.setBridgeOptionOrdinal(bridgeOptionOrdinal) .setBridgeOptionOrdinal(bridgeOptionOrdinal)
.setTorTransportOrdinal(torTransportOrdinal) .setTorTransportOrdinal(torTransportOrdinal)
.setUseTorForXmrOrdinal(useTorForXmrOrdinal)
.setMoneroNodesOptionOrdinal(moneroNodesOptionOrdinal) .setMoneroNodesOptionOrdinal(moneroNodesOptionOrdinal)
.setUseSoundForMobileNotifications(useSoundForMobileNotifications) .setUseSoundForMobileNotifications(useSoundForMobileNotifications)
.setUseTradeNotifications(useTradeNotifications) .setUseTradeNotifications(useTradeNotifications)
@ -244,7 +244,6 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getAutoSelectArbitrators(), proto.getAutoSelectArbitrators(),
Maps.newHashMap(proto.getDontShowAgainMapMap()), Maps.newHashMap(proto.getDontShowAgainMapMap()),
proto.getTacAccepted(), proto.getTacAccepted(),
proto.getUseTorForMonero(),
proto.getSplitOfferOutput(), proto.getSplitOfferOutput(),
proto.getShowOwnOffersInOfferBook(), proto.getShowOwnOffersInOfferBook(),
proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null,
@ -272,6 +271,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getBridgeOptionOrdinal(), proto.getBridgeOptionOrdinal(),
proto.getTorTransportOrdinal(), proto.getTorTransportOrdinal(),
ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()), ProtoUtil.stringOrNullFromProto(proto.getCustomBridges()),
proto.getUseTorForXmrOrdinal(),
proto.getMoneroNodesOptionOrdinal(), proto.getMoneroNodesOptionOrdinal(),
proto.getReferralId().isEmpty() ? null : proto.getReferralId(), proto.getReferralId().isEmpty() ? null : proto.getReferralId(),
proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(), proto.getPhoneKeyAndToken().isEmpty() ? null : proto.getPhoneKeyAndToken(),

View File

@ -171,7 +171,7 @@ public class WalletsSetup {
backupWallets(); backupWallets();
final Socks5Proxy socks5Proxy = preferences.getUseTorForMonero() ? socks5ProxyProvider.getSocks5Proxy() : null; final Socks5Proxy socks5Proxy = socks5ProxyProvider.getSocks5Proxy();
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy); log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
walletConfig = new WalletConfig(params, walletDir, "haveno") { walletConfig = new WalletConfig(params, walletDir, "haveno") {

View File

@ -19,6 +19,7 @@ import haveno.core.trade.HavenoUtils;
import haveno.core.trade.MakerTrade; import haveno.core.trade.MakerTrade;
import haveno.core.trade.Trade; import haveno.core.trade.Trade;
import haveno.core.trade.TradeManager; import haveno.core.trade.TradeManager;
import haveno.core.user.Preferences;
import haveno.core.xmr.listeners.XmrBalanceListener; import haveno.core.xmr.listeners.XmrBalanceListener;
import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.model.XmrAddressEntry;
import haveno.core.xmr.model.XmrAddressEntryList; import haveno.core.xmr.model.XmrAddressEntryList;
@ -54,7 +55,6 @@ import monero.wallet.model.MoneroWalletListenerI;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.File; import java.io.File;
import java.math.BigInteger; import java.math.BigInteger;
@ -100,6 +100,7 @@ public class XmrWalletService {
private static final int MONERO_LOG_LEVEL = 0; private static final int MONERO_LOG_LEVEL = 0;
private static final boolean PRINT_STACK_TRACE = false; private static final boolean PRINT_STACK_TRACE = false;
private final Preferences preferences;
private final CoreAccountService accountService; private final CoreAccountService accountService;
private final CoreMoneroConnectionsService connectionsService; private final CoreMoneroConnectionsService connectionsService;
private final XmrAddressEntryList xmrAddressEntryList; private final XmrAddressEntryList xmrAddressEntryList;
@ -114,17 +115,20 @@ public class XmrWalletService {
private TradeManager tradeManager; private TradeManager tradeManager;
private MoneroWalletRpc wallet; private MoneroWalletRpc wallet;
private Object walletLock = new Object(); private Object walletLock = new Object();
private boolean wasWalletSynced = false;
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>(); private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
private boolean isShutDownStarted = false; private boolean isShutDownStarted = false;
private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type private ExecutorService syncWalletThreadPool = Executors.newFixedThreadPool(10); // TODO: adjust based on connection type
@Inject @Inject
XmrWalletService(CoreAccountService accountService, XmrWalletService(Preferences preferences,
CoreAccountService accountService,
CoreMoneroConnectionsService connectionsService, CoreMoneroConnectionsService connectionsService,
WalletsSetup walletsSetup, WalletsSetup walletsSetup,
XmrAddressEntryList xmrAddressEntryList, XmrAddressEntryList xmrAddressEntryList,
@Named(Config.WALLET_DIR) File walletDir, @Named(Config.WALLET_DIR) File walletDir,
@Named(Config.WALLET_RPC_BIND_PORT) int rpcBindPort) { @Named(Config.WALLET_RPC_BIND_PORT) int rpcBindPort) {
this.preferences = preferences;
this.accountService = accountService; this.accountService = accountService;
this.connectionsService = connectionsService; this.connectionsService = connectionsService;
this.walletsSetup = walletsSetup; this.walletsSetup = walletsSetup;
@ -212,6 +216,10 @@ public class XmrWalletService {
public CoreMoneroConnectionsService getConnectionsService() { public CoreMoneroConnectionsService getConnectionsService() {
return connectionsService; return connectionsService;
} }
public boolean isProxyApplied(boolean wasWalletSynced) {
return preferences.isProxyApplied(wasWalletSynced);
}
public String getWalletPassword() { public String getWalletPassword() {
return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword(); return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword();
@ -231,13 +239,14 @@ public class XmrWalletService {
null); null);
} }
public MoneroWalletRpc openWallet(String walletName) { public MoneroWalletRpc openWallet(String walletName, boolean applyProxyUri) {
log.info("{}.openWallet({})", getClass().getSimpleName(), walletName); log.info("{}.openWallet({})", getClass().getSimpleName(), walletName);
if (isShutDownStarted) throw new IllegalStateException("Cannot open wallet because shutting down"); if (isShutDownStarted) throw new IllegalStateException("Cannot open wallet because shutting down");
return openWalletRpc(new MoneroWalletConfig() return openWalletRpc(new MoneroWalletConfig()
.setPath(walletName) .setPath(walletName)
.setPassword(getWalletPassword()), .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())); log.info("Initializing main wallet with monerod=" + (daemon == null ? "null" : daemon.getRpcConnection().getUri()));
MoneroWalletConfig walletConfig = new MoneroWalletConfig().setPath(MONERO_WALLET_NAME).setPassword(getWalletPassword()); MoneroWalletConfig walletConfig = new MoneroWalletConfig().setPath(MONERO_WALLET_NAME).setPassword(getWalletPassword());
if (MoneroUtils.walletExists(xmrWalletFile.getPath())) { 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())) { } else if (connectionsService.getConnection() != null && Boolean.TRUE.equals(connectionsService.getConnection().isConnected())) {
wallet = createWalletRpc(walletConfig, rpcBindPort); wallet = createWalletRpc(walletConfig, rpcBindPort);
} }
@ -688,9 +697,13 @@ public class XmrWalletService {
log.info("Syncing main wallet"); log.info("Syncing main wallet");
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
wallet.sync(); // blocking wallet.sync(); // blocking
wasWalletSynced = true;
log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms"); log.info("Done syncing main wallet in " + (System.currentTimeMillis() - time) + " ms");
wallet.startSyncing(connectionsService.getRefreshPeriodMs()); wallet.startSyncing(connectionsService.getRefreshPeriodMs());
if (getMoneroNetworkType() != MoneroNetworkType.MAINNET) log.info("Monero wallet balance={}, unlocked balance={}", wallet.getBalance(0), wallet.getUnlockedBalance(0)); 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? // TODO: using this to signify both daemon and wallet synced, use separate sync handlers?
connectionsService.doneDownload(); connectionsService.doneDownload();
@ -742,7 +755,7 @@ public class XmrWalletService {
try { try {
// start monero-wallet-rpc instance // start monero-wallet-rpc instance
walletRpc = startWalletRpcInstance(port); walletRpc = startWalletRpcInstance(port, isProxyApplied(false));
walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE); walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE);
// prevent wallet rpc from syncing // 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; MoneroWalletRpc walletRpc = null;
try { try {
// start monero-wallet-rpc instance // start monero-wallet-rpc instance
walletRpc = startWalletRpcInstance(port); walletRpc = startWalletRpcInstance(port, applyProxyUri);
walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE); walletRpc.getRpcConnection().setPrintStackTrace(PRINT_STACK_TRACE);
// prevent wallet rpc from syncing // prevent wallet rpc from syncing
walletRpc.stopSyncing(); walletRpc.stopSyncing();
// configure connection
MoneroRpcConnection connection = new MoneroRpcConnection(connectionsService.getConnection());
if (!applyProxyUri) connection.setProxyUri(null);
// open wallet // open wallet
log.info("Opening wallet " + config.getPath()); walletRpc.openWallet(config.setServer(connection));
walletRpc.openWallet(config.setServer(connectionsService.getConnection()));
if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE); if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_STACK_TRACE);
log.info("Done opening wallet " + config.getPath()); log.info("Done opening wallet " + config.getPath());
return walletRpc; 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 // 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 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) { if (connection != null) {
cmd.add("--daemon-address"); cmd.add("--daemon-address");
cmd.add(connection.getUri()); 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("--proxy");
cmd.add(connection.getProxyUri()); cmd.add(connection.getProxyUri());
if (!connection.isOnion()) cmd.add("--daemon-ssl-allow-any-cert"); // necessary to use proxy with clearnet mmonerod 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) { synchronized (walletLock) {
if (isShutDownStarted) return; if (isShutDownStarted) return;
if (wallet != null && HavenoUtils.connectionConfigsEqual(connection, wallet.getDaemonConnection())) 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 oldProxyUri = wallet == null || wallet.getDaemonConnection() == null ? null : wallet.getDaemonConnection().getProxyUri();
String newProxyUri = connection == null ? null : connection.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); if (wallet == null) maybeInitMainWallet(false);
else if (wallet instanceof MoneroWalletRpc && !StringUtils.equals(oldProxyUri, newProxyUri)) { 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); closeMainWallet(true);
maybeInitMainWallet(false); maybeInitMainWallet(false);
} else { } else {

View File

@ -1254,7 +1254,7 @@ setting.preferences.prefCurrency=Preferred currency
setting.preferences.displayTraditional=Display traditional currencies setting.preferences.displayTraditional=Display traditional currencies
setting.preferences.noTraditional=There are no traditional currencies selected setting.preferences.noTraditional=There are no traditional currencies selected
setting.preferences.cannotRemovePrefCurrency=You cannot remove your selected preferred display currency 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.noCryptos=There are no cryptos selected
setting.preferences.addTraditional=Add traditional currency setting.preferences.addTraditional=Add traditional currency
setting.preferences.addCrypto=Add cryptocurrency setting.preferences.addCrypto=Add cryptocurrency
@ -1284,6 +1284,9 @@ settings.net.onionAddressLabel=My onion address
settings.net.xmrNodesLabel=Use custom Monero nodes settings.net.xmrNodesLabel=Use custom Monero nodes
settings.net.moneroPeersLabel=Connected peers settings.net.moneroPeersLabel=Connected peers
settings.net.useTorForXmrJLabel=Use Tor for Monero network 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.moneroNodesLabel=Monero nodes to connect to
settings.net.useProvidedNodesRadio=Use provided Monero nodes settings.net.useProvidedNodesRadio=Use provided Monero nodes
settings.net.usePublicNodesRadio=Use public Monero network settings.net.usePublicNodesRadio=Use public Monero network

View File

@ -574,7 +574,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
splashP2PNetworkBusyAnimation.stop(); splashP2PNetworkBusyAnimation.stop();
showTorNetworkSettingsButton.setVisible(true); showTorNetworkSettingsButton.setVisible(true);
showTorNetworkSettingsButton.setManaged(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 // If using tor for XMR, hide the XMR status since tor is not working
xmrSyncIndicator.setVisible(false); xmrSyncIndicator.setVisible(false);
xmrSplashInfo.setVisible(false); xmrSplashInfo.setVisible(false);

View File

@ -47,6 +47,7 @@ import haveno.core.provider.price.PriceFeedService;
import haveno.core.trade.TradeManager; import haveno.core.trade.TradeManager;
import haveno.core.user.DontShowAgainLookup; import haveno.core.user.DontShowAgainLookup;
import haveno.core.user.Preferences; import haveno.core.user.Preferences;
import haveno.core.user.Preferences.UseTorForXmr;
import haveno.core.user.User; import haveno.core.user.User;
import haveno.core.xmr.wallet.XmrWalletService; import haveno.core.xmr.wallet.XmrWalletService;
import haveno.desktop.Navigation; import haveno.desktop.Navigation;
@ -628,8 +629,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
return havenoSetup.getXmrSplashSyncIconId(); return havenoSetup.getXmrSplashSyncIconId();
} }
BooleanProperty getUseTorForXMR() { ObjectProperty<UseTorForXmr> getUseTorForXmr() {
return havenoSetup.getUseTorForXMR(); return havenoSetup.getUseTorForXmr();
} }
// P2P // P2P

View File

@ -73,7 +73,14 @@
<AutoTooltipLabel fx:id="localhostXmrNodeInfoLabel" styleClass="small-text"/> <AutoTooltipLabel fx:id="localhostXmrNodeInfoLabel" styleClass="small-text"/>
</VBox> </VBox>
<AutoTooltipCheckBox fx:id="useTorForXmrJCheckBox" GridPane.rowIndex="1"/> <VBox GridPane.rowIndex="1">
<AutoTooltipLabel fx:id="useTorForXmrLabel" styleClass="small-text"/>
<HBox spacing="10">
<AutoTooltipRadioButton fx:id="useTorForXmrAfterSyncRadio"/>
<AutoTooltipRadioButton fx:id="useTorForXmrOnRadio"/>
<AutoTooltipRadioButton fx:id="useTorForXmrOffRadio"/>
</HBox>
</VBox>
<VBox GridPane.rowIndex="2"> <VBox GridPane.rowIndex="2">
<AutoTooltipLabel fx:id="moneroNodesLabel" styleClass="small-text"/> <AutoTooltipLabel fx:id="moneroNodesLabel" styleClass="small-text"/>

View File

@ -49,7 +49,6 @@ import javafx.collections.transformation.SortedList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.VPos; import javafx.geometry.VPos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.RadioButton; import javafx.scene.control.RadioButton;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
@ -75,7 +74,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@FXML @FXML
TitledGroupBg p2pHeader, btcHeader; TitledGroupBg p2pHeader, btcHeader;
@FXML @FXML
Label xmrNodesLabel, moneroNodesLabel, localhostXmrNodeInfoLabel; Label useTorForXmrLabel, xmrNodesLabel, moneroNodesLabel, localhostXmrNodeInfoLabel;
@FXML @FXML
InputTextField xmrNodesInputTextField; InputTextField xmrNodesInputTextField;
@FXML @FXML
@ -83,7 +82,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@FXML @FXML
Label p2PPeersLabel, moneroPeersLabel; Label p2PPeersLabel, moneroPeersLabel;
@FXML @FXML
CheckBox useTorForXmrJCheckBox; RadioButton useTorForXmrAfterSyncRadio, useTorForXmrOffRadio, useTorForXmrOnRadio;
@FXML @FXML
RadioButton useProvidedNodesRadio, useCustomNodesRadio, usePublicNodesRadio; RadioButton useProvidedNodesRadio, useCustomNodesRadio, usePublicNodesRadio;
@FXML @FXML
@ -122,8 +121,11 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
private Subscription moneroBlockHeightSubscription; private Subscription moneroBlockHeightSubscription;
private Subscription nodeAddressSubscription; private Subscription nodeAddressSubscription;
private ChangeListener<Boolean> xmrNodesInputTextFieldFocusListener; private ChangeListener<Boolean> xmrNodesInputTextFieldFocusListener;
private ToggleGroup useTorForXmrToggleGroup;
private ToggleGroup moneroPeersToggleGroup; private ToggleGroup moneroPeersToggleGroup;
private Preferences.UseTorForXmr selectedUseTorForXmr;
private XmrNodes.MoneroNodesOption selectedMoneroNodesOption; private XmrNodes.MoneroNodesOption selectedMoneroNodesOption;
private ChangeListener<Toggle> useTorForXmrToggleGroupListener;
private ChangeListener<Toggle> moneroPeersToggleGroupListener; private ChangeListener<Toggle> moneroPeersToggleGroupListener;
private ChangeListener<Filter> filterPropertyListener; private ChangeListener<Filter> filterPropertyListener;
@ -156,7 +158,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
onionAddress.setPromptText(Res.get("settings.net.onionAddressLabel")); onionAddress.setPromptText(Res.get("settings.net.onionAddressLabel"));
xmrNodesLabel.setText(Res.get("settings.net.xmrNodesLabel")); xmrNodesLabel.setText(Res.get("settings.net.xmrNodesLabel"));
moneroPeersLabel.setText(Res.get("settings.net.moneroPeersLabel")); 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")); moneroNodesLabel.setText(Res.get("settings.net.moneroNodesLabel"));
moneroPeerAddressColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.onionAddressColumn"))); moneroPeerAddressColumn.setGraphic(new AutoTooltipLabel(Res.get("settings.net.onionAddressColumn")));
moneroPeerAddressColumn.getStyleClass().add("first-column"); moneroPeerAddressColumn.getStyleClass().add("first-column");
@ -208,6 +213,31 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
p2pPeersTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData"))); p2pPeersTableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData")));
p2pPeersTableView.getSortOrder().add(creationDateColumn); p2pPeersTableView.getSortOrder().add(creationDateColumn);
creationDateColumn.setSortType(TableColumn.SortType.ASCENDING); 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(); moneroPeersToggleGroup = new ToggleGroup();
useProvidedNodesRadio.setToggleGroup(moneroPeersToggleGroup); useProvidedNodesRadio.setToggleGroup(moneroPeersToggleGroup);
@ -264,6 +294,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@Override @Override
public void activate() { public void activate() {
useTorForXmrToggleGroup.selectedToggleProperty().addListener(useTorForXmrToggleGroupListener);
moneroPeersToggleGroup.selectedToggleProperty().addListener(moneroPeersToggleGroupListener); moneroPeersToggleGroup.selectedToggleProperty().addListener(moneroPeersToggleGroupListener);
if (filterManager.getFilter() != null) if (filterManager.getFilter() != null)
@ -271,22 +302,6 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
filterManager.filterProperty().addListener(filterPropertyListener); 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)); rescanOutputsButton.setOnAction(event -> GUIUtil.rescanOutputs(preferences));
moneroPeersSubscription = EasyBind.subscribe(connectionManager.peerConnectionsProperty(), moneroPeersSubscription = EasyBind.subscribe(connectionManager.peerConnectionsProperty(),
@ -328,11 +343,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@Override @Override
public void deactivate() { public void deactivate() {
useTorForXmrToggleGroup.selectedToggleProperty().removeListener(useTorForXmrToggleGroupListener);
moneroPeersToggleGroup.selectedToggleProperty().removeListener(moneroPeersToggleGroupListener); moneroPeersToggleGroup.selectedToggleProperty().removeListener(moneroPeersToggleGroupListener);
filterManager.filterProperty().removeListener(filterPropertyListener); filterManager.filterProperty().removeListener(filterPropertyListener);
useTorForXmrJCheckBox.setOnAction(null);
if (nodeAddressSubscription != null) if (nodeAddressSubscription != null)
nodeAddressSubscription.unsubscribe(); nodeAddressSubscription.unsubscribe();
@ -360,6 +374,21 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
return filterManager.getFilter() != null && return filterManager.getFilter() != null &&
filterManager.getFilter().isPreventPublicXmrNetwork(); 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() { private void selectMoneroPeersToggle() {
switch (selectedMoneroNodesOption) { switch (selectedMoneroNodesOption) {
@ -383,10 +412,30 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
.useShutDownButton() .useShutDownButton()
.show(); .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) { private void onMoneroPeersToggleSelected(boolean calledFromUser) {
boolean localMoneroNodeShouldBeUsed = localMoneroNode.shouldBeUsed(); boolean localMoneroNodeShouldBeUsed = localMoneroNode.shouldBeUsed();
useTorForXmrJCheckBox.setDisable(localMoneroNodeShouldBeUsed); useTorForXmrLabel.setDisable(localMoneroNodeShouldBeUsed);
moneroNodesLabel.setDisable(localMoneroNodeShouldBeUsed); moneroNodesLabel.setDisable(localMoneroNodeShouldBeUsed);
xmrNodesLabel.setDisable(localMoneroNodeShouldBeUsed); xmrNodesLabel.setDisable(localMoneroNodeShouldBeUsed);
xmrNodesInputTextField.setDisable(localMoneroNodeShouldBeUsed); xmrNodesInputTextField.setDisable(localMoneroNodeShouldBeUsed);

View File

@ -1654,7 +1654,7 @@ message PreferencesPayload {
bool auto_select_arbitrators = 8; bool auto_select_arbitrators = 8;
map<string, bool> dont_show_again_map = 9; map<string, bool> dont_show_again_map = 9;
bool tac_accepted = 10; 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; bool show_own_offers_in_offer_book = 12;
TradeCurrency preferred_trade_currency = 13; TradeCurrency preferred_trade_currency = 13;
int64 withdrawal_tx_fee_in_vbytes = 14; int64 withdrawal_tx_fee_in_vbytes = 14;