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 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<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()
.describedAs("ip[,...]")
.defaultsTo("");
ArgumentAcceptingOptionSpec<Boolean> useTorForXmrOpt =
parser.accepts(USE_TOR_FOR_XMR, "If set to true BitcoinJ is routed over tor (socks 5 proxy).")
//noinspection rawtypes
ArgumentAcceptingOptionSpec<Enum> 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<String> 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);

View File

@ -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() {

View File

@ -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<UseTorForXmr> getUseTorForXmr() {
return walletAppSetup.getUseTorForXmr();
}
// P2P

View File

@ -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> rejectedTxException = new SimpleObjectProperty<>();
@Getter
private final BooleanProperty useTorForXMR = new SimpleBooleanProperty();
private final ObjectProperty<UseTorForXmr> useTorForXmr = new SimpleObjectProperty<UseTorForXmr>();
@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<String> 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 = "";

View File

@ -838,7 +838,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> extends Sup
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
return payoutTx;
} catch (Exception e) {
trade.syncWallet();
trade.syncAndPollWallet();
if (!trade.isPayoutPublished()) throw e;
}
}

View File

@ -249,7 +249,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// sync and save wallet
if (!trade.isPayoutPublished()) {
trade.syncWallet();
trade.syncAndPollWallet();
trade.saveWallet();
}
@ -266,7 +266,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
if (disputeClosedMessage.isDeferPublishPayout()) {
log.info("Deferring signing and publishing dispute payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
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
@ -277,7 +277,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
} catch (Exception e) {
// check if payout published again
trade.syncWallet();
trade.syncAndPollWallet();
if (trade.isPayoutPublished()) {
log.info("Dispute payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
} 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 MoneroWallet wallet; // trade wallet
private Object walletLock = new Object();
boolean wasWalletSynced = false;
///////////////////////////////////////////////////////////////////////////////////////////
// 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<TradePeriodState> 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,45 +1692,77 @@ 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
}
if (isPolling()) {
stopPolling();
}
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,13 +1794,19 @@ 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) {

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -67,6 +67,16 @@ import static com.google.common.base.Preconditions.checkNotNull;
@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<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 Tor V3", "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/tx/", "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/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) {
@ -664,6 +685,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
persistenceManager.forcePersistNow();
}
public void setUseTorForXmrOrdinal(int useTorForXmrOrdinal) {
prefPayload.setUseTorForXmrOrdinal(useTorForXmrOrdinal);
requestPersistence();
}
public void setMoneroNodesOptionOrdinal(int bitcoinNodesOptionOrdinal) {
prefPayload.setMoneroNodesOptionOrdinal(bitcoinNodesOptionOrdinal);
requestPersistence();
@ -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);
@ -928,6 +956,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setCustomBridges(String customBridges);
void setUseTorForXmrOrdinal(int useTorForXmrOrdinal);
void setMoneroNodesOptionOrdinal(int bitcoinNodesOption);
void setReferralId(String referralId);

View File

@ -58,7 +58,6 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean autoSelectArbitrators = true;
private Map<String, Boolean> 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(),

View File

@ -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") {

View File

@ -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<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
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;
@ -213,6 +217,10 @@ public class XmrWalletService {
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,10 +697,14 @@ 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 {

View File

@ -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

View File

@ -574,7 +574,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
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);

View File

@ -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<UseTorForXmr> getUseTorForXmr() {
return havenoSetup.getUseTorForXmr();
}
// P2P

View File

@ -73,7 +73,14 @@
<AutoTooltipLabel fx:id="localhostXmrNodeInfoLabel" styleClass="small-text"/>
</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">
<AutoTooltipLabel fx:id="moneroNodesLabel" styleClass="small-text"/>

View File

@ -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<GridPane, Void> {
@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<GridPane, Void> {
@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<GridPane, Void> {
private Subscription moneroBlockHeightSubscription;
private Subscription nodeAddressSubscription;
private ChangeListener<Boolean> xmrNodesInputTextFieldFocusListener;
private ToggleGroup useTorForXmrToggleGroup;
private ToggleGroup moneroPeersToggleGroup;
private Preferences.UseTorForXmr selectedUseTorForXmr;
private XmrNodes.MoneroNodesOption selectedMoneroNodesOption;
private ChangeListener<Toggle> useTorForXmrToggleGroupListener;
private ChangeListener<Toggle> moneroPeersToggleGroupListener;
private ChangeListener<Filter> filterPropertyListener;
@ -156,7 +158,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
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");
@ -209,6 +214,31 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
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);
useCustomNodesRadio.setToggleGroup(moneroPeersToggleGroup);
@ -264,6 +294,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@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<GridPane, Void> {
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<GridPane, Void> {
@Override
public void deactivate() {
useTorForXmrToggleGroup.selectedToggleProperty().removeListener(useTorForXmrToggleGroupListener);
moneroPeersToggleGroup.selectedToggleProperty().removeListener(moneroPeersToggleGroupListener);
filterManager.filterProperty().removeListener(filterPropertyListener);
useTorForXmrJCheckBox.setOnAction(null);
if (nodeAddressSubscription != null)
nodeAddressSubscription.unsubscribe();
@ -361,6 +375,21 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
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) {
case CUSTOM:
@ -384,9 +413,29 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
.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);

View File

@ -1654,7 +1654,7 @@ message PreferencesPayload {
bool auto_select_arbitrators = 8;
map<string, bool> 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;